"-Все", уступая "Сравнение плавающей запятой с == или!= Небезопасно"

У меня есть строка, в которой я конвертирую в double следующим образом:

double d = [string doubleValue];

В документации для doubleValue указывается, что при переполнении этот метод возвращает либо HUGE_VAL, либо -HUGE_VAL. Вот как я проверил это раньше:

if (d == HUGE_VAL || d == -HUGE_VAL)
   //overflow

Теперь, добавив новый предупреждающий флаг "-Всего", компилятор теперь жалуется, что

Comparing floating point with == or != is unsafe

Как я могу решить эту проблему? Как должен выполнять эти сравнения?


У меня также возникает вопрос о сравнении двух "нормальных" чисел с плавающей запятой (т.е. не "HUGE_VAL" ). Например,

double a, b;
//...
if (a != b) //this will now yield the same warning
  //...

Как это разрешить?

Ответы

Ответ 1

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

Документация doubleValue не говорит о том, что она возвращает что-то достаточно близко к HUGE_VAL или -HUGE_VAL при переполнении. В нем говорится, что он возвращает именно эти значения в случае переполнения.

Другими словами, значение, возвращаемое методом в случае переполнения, сравнивает == с HUGE_VAL или -HUGE_VAL.

Почему это предупреждение существует в первую очередь?

Рассмотрим пример 0.3 + 0.4 == 0.7. Этот пример оценивается как false. Люди, в том числе авторы предупреждений, которые вы встретили, считают, что точка с плавающей запятой == неточна и что неожиданный результат исходит из этой неточности.

Они все ошибаются.

Добавление с плавающей запятой является "неточным", для некоторого значения неточности: оно возвращает ближайший представимый номер с плавающей запятой для запрошенной операции. В приведенном выше примере конверсии (от десятичной до плавающей) и добавление с плавающей запятой являются причинами странного поведения.

Справедливость с плавающей запятой, с другой стороны, работает практически так же, как и для других дискретных типов. Правило с плавающей точкой является точным: за исключением незначительных исключений (значение NaN и случай +0. И -0.), Равенство оценивается как истинное тогда и только тогда, когда два рассматриваемых числа с плавающей запятой имеют одинаковое представление.

Вам не нужен epsilon для проверки, если два значения с плавающей запятой равны. И, как Дьюар говорит по существу, предупреждение в примере 0.3 + 0.4 == 0.7 должно быть на +, а не на ==, чтобы предупреждение имело смысл.

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

Ответ 2

В этом случае попробуйте использовать >= и <=.

Ответ 3

Если вы уверены в своем сравнении и хотите сказать, чтобы он говорил, окружите свой код:

#pragma clang diagnostic ignored "-Wfloat-equal"
/* My code triggering the warnings */
#pragma clang diagnostic pop

Ответ 4

Поплавки не следует сравнивать с == или!= из-за неточности типа float, что может привести к непредвиденным ошибкам при использовании этих операторов. Вы должны проверить, находятся ли поплавки на расстоянии друг от друга (чаще всего называется "Эпсилон" ).

Он может выглядеть так:

const float EPSILON = 1.0f; // use a really small number instead of this

bool closeEnough( float f1, float f2)
{
    return fabs(f1-f2)<EPSILON; 
    // test if the floats are so close together that they can be considered equal
}