Случаи, когда числа с плавающей точкой сравнимы с использованием равенства

Недавно я вступил в дискуссию о сравнении с плавающей запятой. Моя точка зрения всегда заключалась в том, чтобы не сравнивать два числа с плавающей запятой, используя == непосредственно.

Было отмечено, что это не так, и есть случаи, когда использование == отлично в порядке. Я могу вспомнить типичные случаи, когда я проверяю по литералам IEEE 754, как +-INF или же +-0Но кроме этого, я не могу придумать случай, когда это не приведет к проблемам.

Итак, мой вопрос: в каких случаях сравнение с плавающей запятой с использованием == является действительным?

3 ответа

Решение

Представление с плавающей запятой двойной точности (64-разрядное на число) является точным для целых чисел до -+2**53 (-+ 9 007 199 254 740 992). Если вы используете числа с плавающей запятой, но начинаете с целых чисел и делаете целочисленные вычисления с ними, и вы никогда не превышали этот предел, тогда результат является точным и использует == отлично в порядке.

Числа, которые в общем случае могут быть представлены точно, представляют собой N/M, где N - целое число, а M - степень двойки. Таким образом, если вы просто выполняете вычисления, включающие, например, 1/4, 1/2, 3/4 и их целочисленные кратные, вы тоже будете в порядке, пока не достигнете очень больших множителей.

Когда вместо этого вы имеете дело с числами, которые не могут быть представлены точно (например, 0,1), приближение привело меня к неожиданным результатам. Одним из источников проблем является то, что промежуточные результаты могут храниться во временных данных с более высокой точностью, и, следовательно, результат формулы может отличаться в зависимости от того, явно ли вы храните его в памяти или нет, и он также может меняться в зависимости от уровня оптимизации.

Вот несколько примеров правильного использования равенства с плавающей точкой:

  • когда функция задокументирована как возвращающая HUGE_VAL в некоторых случаях определение того, произошло ли это с result == HUGE_VAL,

  • определение, если двойной d содержит число, представимое в виде float: d == (double)(float)d, Я на самом деле использую это в своей повседневной работе, потому что я использую пары двойников для представления как интервалов двойных значений, так и интервалов значений с плавающей запятой, и есть моменты, в которых приятно иметь возможность утверждать, что границы интервала с плавающей запятой поплавки.

  • определение, является ли число с плавающей точкой y является NaN: y == y,

  • определение, является ли число с плавающей точкой y это бесконечность или NaN с y - y == 0.0 (конечные значения y выполнить условие, NaN и бесконечности делают это ложным).

  • определение, установлен ли бит в значении и числа с плавающей запятой, как в примере ниже, взятом из этой выкладки.

    /* coef is a power of two plus one. */
    double t = coef * f;
    double o = f - t + t - f;
    if (o != 0)
    {
      ...
    

Числа с плавающей запятой представляют собой точные значения с использованием соответствующей базы (обычно 2). Нет ничего плохого в том, чтобы сравнивать их, используя равенство.

Двоичные числа с плавающей запятой не могут точно представлять все десятичные значения, т. Е. Для большинства дробных десятичных значений двоичная с плавающей запятой будет использовать приближение. Пока десятичные числа не превышают std::numeric_limits<F>::digits10 результирующее представление также однозначно идентифицируется внутри системы (для некоторых десятичных значений существует выбор между двумя двоичными представлениями, и в этом случае направление округления должно выбирать правильное).

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

Другие вопросы по тегам