Равновесие и допуски с плавающей точкой

Сравнение двух чисел с плавающей запятой чем-то вроде 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;
}