Равновесие и допуски с плавающей точкой
Сравнение двух чисел с плавающей запятой чем-то вроде 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 или С++.
Ответы
Ответ 1
В этом блоге содержится пример, довольно надежная реализация и подробная теория
http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
это также одна из серии, поэтому вы всегда можете прочитать больше.
Короче: используйте ULP для большинства чисел, используйте epsilon для чисел около нуля, но есть все еще оговорки. Если вы хотите быть уверенным в своей математике с плавающей запятой, я рекомендую читать целую серию.
Ответ 2
Насколько я знаю, это не так.
Нет общего "правильного ответа", поскольку он может зависеть от требования приложения к точности.
Например, 2D-физическое моделирование, работающее в пикселях экрана, может решить, что 1/4 пикселя достаточно хорош, в то время как 3D-система САПР, используемая для проектирования внутренних объектов атомной станции, может и не быть.
Я не вижу способа программно решить это извне.
Ответ 3
Заголовочный файл C <float.h>
предоставляет константы FLT_EPSILON
и DBL_EPSILON
, что является разницей между 1.0 и наименьшим числом, большим, чем 1.0, которое может представлять float/double. Вы можете масштабировать это по размеру ваших номеров и ошибку округления, которую вы хотите терпеть:
#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);
}
Ответ 4
Добро пожаловать в мир ловушек, ловушек и лазеек. Как упоминалось в другом месте, решение общего назначения для равенства и допусков с плавающей точкой имеет не. Учитывая это, есть инструменты и аксиомы, которые программист может использовать в отдельных случаях.
fabs(a_float - b_float) < tol
имеет упомянутый недостаток OP: "не подходит для общего случая, когда a_float может быть очень маленьким или может быть очень большим". fabs(a_float - ref_float) <= fabs(ref_float * tol)
гораздо лучше справляется с вариантами.
OP "число одинарной точности с плавающей запятой используется tol = 10E-6" является немного тревожным для 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
или "допускаемость" часто является основным вопросом. Чаще всего (ИМХО) важна относительная толерантность. е. г. "Являются ли x и y в пределах 0.0001%"? Иногда требуется абсолютная толерантность. например "Являются ли x и y в пределах 0.0001"?
Значение допуска часто является спорным, поскольку наилучшее значение часто зависит от ситуации. Сравнение в пределах 0,01 может работать для финансового приложения для долларов, но не для йены. (Подсказка: обязательно используйте стиль кодирования, который позволяет легко обновлять.)
Ответ 5
Ошибка округления зависит от значений, используемых для операций.
Вместо фиксированного допуска вы, вероятно, можете использовать коэффициент epsilon, например:
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;
}
Ответ 6
Хотя значение допуска зависит от ситуации, если вы ищете точное сравнение, вы можете использовать в качестве допуска машинное значение 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 умножьте эпсилон на максимальное абсолютное значение x и y.
Приведенный выше результат может быть умножен на ulps (единицы на последнем месте), что позволяет вам играть с точностью.
#include <iostream>
#include <cmath>
#include <limits>
template<class T> bool are_almost_equal(T x, T y, int ulp)
{
return std::abs(x-y) <= std::numeric_limits<T>::epsilon() * std::max(std::abs(x), std::abs(y)) * ulp
}
Ответ 7
Когда мне нужно сравнивать поплавки, я использую такой код
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;
}