2013-07-01 3 views
11

Сравнение двух чисел с плавающей запятой на что-то вроде a_float == b_float ищет проблемы с a_float/3.0 * 3.0 может быть не равно a_float из-за ошибки округления.Равновесие с плавающей точкой и допуски

Что обычно делается, это что-то вроде fabs(a_float - b_float) < tol.

Как рассчитать tol?

Идеальный допуск должен быть больше, чем значение одной или двух наименее значимых цифр. Поэтому, если число с плавающей запятой с одиночной точностью используется, tol = 10E-6 должно быть примерно вправо. Однако это не подходит для общего случая, когда a_float может быть очень маленьким или может быть очень большим.

Как правильно рассчитать tol для всех общих случаев? Меня интересуют случаи C или C++.

+1

Вы читали [это] (http://en.wikibooks.org/wiki/Floating_Point/Epsilon)? – NINCOMPOOP

+0

Если вам нужна совершенная точность, вы можете использовать одну из библиотек рациональных номеров. –

+3

Вопрос: Как правильно вычислять 'tol' для всех общих случаев? A: Никто этого не делает. Такое сравнение не подходит для всех случаев, независимо от допустимого значения (и FWIW, вы не знаете, что лучше всего подходит для того, что вы тестируете *?) –

ответ

15

Это Блогпост содержит пример, справедливо ошибкоустойчивое реализацию и детальную теорию за ней http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ это также один из серии, так что вы всегда можете узнать больше. Вкратце: используйте ULP для большинства чисел, используйте epsilon для чисел около нуля, но есть все еще оговорки. Если вы хотите быть уверенным в своей математике с плавающей запятой, я рекомендую читать целую серию.

+0

причина для downvote? – aryjczyk

+0

Это хорошая статья. Не соглашайтесь с нисходящим. – chux

+2

В то время как точное сравнение с плавающей точкой * обычно * ищет проблемы, это не всегда так. Бывают случаи, когда что-то меньшее, чем точное сравнение, неаккуратно, поскольку я документирую здесь: https://randomascii.wordpress.com/2014/01/27/theres-only-four-billion-floatsso-test-them-all/ Все зависит от контекста. Другими словами, иногда правильное значение для 'tol' равно нулю. –

9

Насколько я знаю, это не так.

Нет общего «правильного ответа», поскольку он может зависеть от требования приложения к точности.

Например, 2D-физическое моделирование, работающее в пикселях экрана, может решить, что 1/4 пикселя достаточно хорош, в то время как 3D-система САПР, используемая для проектирования внутренних элементов АЭС, может и не быть.

Я не вижу способа программно решить это извне.

+0

Мне нужна такая точность, насколько это возможно. Это полное количество значимых цифр для типа хранилища минус один или два, чтобы разрешить ошибку округления. – doron

+0

Ошибка с плавающей запятой не ограничена, если вы не знаете, какие именно операции выполняются на ней. Если у вас есть 'a == 3e10000' и ​​вы вычитаете' 1' 100000 раз, вы можете не получить 'a == 2 ...'. – urzeit

+2

@urzeit Я уверен, что '3e10000 - 100000' не' 2', независимо от ошибки с плавающей запятой. –

-2

Когда мне нужно сравнить поплавки, я использую код, как этот

bool same(double a, double b, double error) { 
    double x; 
    if(a == 0) { 
     x = b; 
    } else if(b == 0) { 
     x = a; 
    } else { 
     x = (a-b)/a; 
    } 
    return fabs(x) < error; 
} 
3

Добро пожаловать в мир ловушек, капканов и бойницами. Как упоминалось в другом месте, решение общего назначения для равенства и допусков с плавающей точкой составляет , а не. Учитывая это, есть инструменты и аксиомы, которые программист может использовать в отдельных случаях.

fabs(a_float - b_float) < tol имеет недостаток ОП, упомянутый: «не подходит для общего случая, когда a_float может быть очень маленьким или может быть очень большим». fabs(a_float - ref_float) <= fabs(ref_float * tol) намного лучше справляется с вариантами.

OP «Число одинарной точности с плавающей точкой является использование тол = 10E-6» немного радужными C и C++, так легко продвигать float арифметику double, а затем это «толерантность» в double, не float, который входит в играть. Рассмотрим float f = 1.0; printf("%.20f\n", f/7.0); Так много новых программистов не понимают, что 7.0 вызвал точный расчет double. Рекомендуйте использовать double, хотя ваш код за исключением случаев, когда для больших объемов данных требуется меньший размер float.

C99 предоставляет nextafter(), который может быть полезен для определения «толерантности». Используя его, можно определить следующее представимое число. Это поможет с OP "...полное число значащих цифр для типа хранения минус один ... для обеспечения ошибки округления.»if ((nextafter(x, -INF) <= y && (y <= nextafter(x, +INF))) ...

вида из tol или„терпимость“используется часто суть дела. Чаще всего (ИМХО) а относительной толерантность важна., например, «Являются ли х и у в пределах 0,0001%»? Иногда нужна абсолютной толерантности., например, «Являются ли х и у в 0,0001»?

значение от допуска часто бывает спорным, поскольку наилучшее значение часто зависит от ситуации. Сравнение в пределах 0,01 может работать для финансовой заявки на доллары, но не иены. (Подсказка:. Обязательно использовать стиль кодирования, который позволяет легко обновления)

5

Файл заголовка C <float.h> дает константы FLT_EPSILON и DBL_EPSILON, что разница между 1,0 и наименьшее число больше, чем 1,0, что поплавок/двойной может представлять. Вы можете масштабировать, что по размеру вашего номера и ошибки округления вы хотите терпеть:

#include <float.h> 
#ifndef DBL_TRUE_MIN 
/* DBL_TRUE_MIN is a common non-standard extension for the minimum denorm value 
* DBL_MIN is the minimum non-denorm value -- use that if TRUE_MIN is not defined */ 
#define DBL_TRUE_MIN DBL_MIN 
#endif 

/* return the difference between |x| and the next larger representable double */ 
double dbl_epsilon(double x) { 
    int exp; 
    if (frexp(x, &exp) == 0.0) 
     return DBL_TRUE_MIN; 
    return ldexp(DBL_EPSILON, exp-1); 
} 
+0

Этот метод прочный, однако он не подходит для чисел вокруг нуля. Это фундаментальная проблема, поскольку относительные ошибки бессмысленны в нуле. Например, sin (M_PI) 'должно быть нулевым, но это не так. Относительная ошибка бесконечна. См. Эту статью: http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ –

0

Хотя значение толерантности зависит от ситуации, если вы ищете для точного comparasion вы могли бы использовать в качестве допуска к значение epsilon машины, numeric_limits :: epsilon() (пределы библиотеки). Функция возвращает разность между 1 и наименьшим значением больше 1, которое представляется для типа данных. http://msdn.microsoft.com/en-us/library/6x7575x3.aspx

Значение epsilon отличается, если вы сравниваете поплавки или парные. Например, на моем компьютере при сравнении поплавков значение epsilon равно 1.1920929e-007, а при сравнении удваивается значение epsilon равно 2.2204460492503131e-016.

Для относительного сравнения между x и y умножьте epsilon на максимальное абсолютное значение x и y.

Результат, полученный выше, может быть умножен на ulps (единицы в последнем месте), что позволяет вам играть с точностью.

#include <iostream> 
#include <cmath> 
#include <limits> 

template<class T> bool are_almost_equal(T x, T y, int ulp) 
{ 
    if(std::abs(x-y) <= std::numeric_limits<T>::epsilon() * std::max(std::abs(x), std::abs(y)) * ulp){ 
     return true; 
    }else{ 
     return false; 
    }   
} 
+0

Это дубликат предыдущего ответа. –

+0

Я добавил немного информации плюс код @BruceDawson – MartaF

+0

Нет необходимости писать 'if (cond) return true; else return false'. Почему бы вам не написать 'return cond'? – 0xbadf00d

1

Ошибка округления зависит от значений, используемых для операций.

Вместо фиксированной толерантности, вероятно, можно использовать коэффициент эпсилон как:

bool nearly_equal(double a, double b, int factor /* a factor of epsilon */) 
{ 
    double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor; 
    double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor; 

    return min_a <= b && max_a >= b; 
} 
+0

Я думаю, что хорошим обобщением концепции было бы то, что «почти равный» субъективен: почти равен по отношению к другому оператору/операции. В конце концов, epsilon используется для категоризации по близости, обратному расстоянию, и вам нужно определить метрику для «расстояния», а также операцию для получения/определения «обратного». Если эпсилон «размахивает руками», то эпсилон без рамки отсчета является прямым обманом джедая. –