Сравнение числа с плавающей точкой с нолем

C ++ FAQ lite “[29.17] Почему мое сравнение с плавающей запятой не работает?” рекомендует этот тест равенства:

#include  /* for std::abs(double) */ inline bool isEqual(double x, double y) { const double epsilon = /* some small number such as 1e-5 */; return std::abs(x - y) <= epsilon * std::abs(x); // see Knuth section 4.2.2 pages 217-218 } 
  1. Правильно ли это, что это означает, что единственные числа, равные нулю, равны +0 и -0 ?
  2. Если использовать эту функцию также при тестировании на нуль или, скорее, на тест, такой как |x| < epsilon |x| < epsilon ?

Обновить

Как отметил Даниэль Даранас, функцию следует, вероятно, лучше назвать isNearlyEqual (в этом я и забочусь).

Кто-то указал на эту ссылку , которую я хочу разделить более заметно.

Вы верны своим наблюдениям.

Если x == 0.0 , то abs(x) * epsilon равно нулю, и вы проверяете, будет ли abs(y) <= 0.0 .

Если y == 0.0 то вы тестируете abs(x) <= abs(x) * epsilon что означает либо epsilon >= 1 (это не так), либо x == 0.0 .

Таким образом, либо is_equal(val, 0.0) либо is_equal(0.0, val) будет бессмысленным, и вы можете просто сказать val == 0.0 . Если вы хотите принять ровно +0.0 и -0.0 .

Рекомендация FAQ в этом случае имеет ограниченную полезность. Сравнение с плавающей запятой «один размер подходит всем». Вы должны думать о семантике ваших переменных, приемлемом диапазоне значений и величине ошибки, введенной вашими вычислениями. Даже в FAQ упоминается оговорка, говоря, что эта функция обычно не является проблемой «когда величины x и y значительно больше, чем epsilon, но ваш пробег может меняться».

Нет.

Равенство – это равенство.

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

Если вы действительно хотите проверить два двойника на равенство, используйте этот:

 inline bool isEqual(double x, double y) { return x == y; } 

Стандарты кодирования обычно рекомендуют не сравнивать два двойника для точного равенства. Но это другой вопрос. Если вы действительно хотите сравнить два удвоения для точного равенства, x == y – код, который вы хотите.

10.00000000000000001 не равно 10.0, независимо от того, что они вам говорят.

Примером использования точного равенства является то, что конкретное значение double используется как синоним какого-либо особого состояния, например «ожидающая калибровка» или «отсутствие данных». Это возможно только в том случае, если фактические числовые значения после этого ожидающего вычисления являются лишь подмножеством возможных значений double. Наиболее типичным частным случаем является то, что это значение является неотрицательным, и вы используете -1.0 в качестве (точного) представления «ожидающего вычисления» или «отсутствия данных». Вы можете представить это с константой:

 const double NO_DATA = -1.0; double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters); if (myData != NO_DATA) { ... } 

Вы можете использовать std::nextafter с фиксированным factor epsilon значения, такого как:

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

2 + 2 = 5 (*)

( для некоторых значений с плавающей запятой 2 )

Эта проблема часто возникает, когда мы думаем о «плавающей точке» как способе повышения точности. Затем мы сталкиваемся с «плавающей» частью, что означает отсутствие гарантии того, какие числа могут быть представлены.

Поэтому, хотя мы можем легко представить «1.0, -1.0, 0.1, -0.1», поскольку мы получаем большие числа, мы начинаем видеть приближения – или мы должны, за исключением того, что мы часто скрываем их, обрезая числа для отображения.

В результате мы могли бы подумать, что компьютер хранит «0.003», но вместо этого он может хранить «0.0033333333334».

Что произойдет, если вы выполните «0.0003 – 0.0002»? Мы ожидаем .0001, но фактические значения, которые хранятся, могут быть больше похожими на «0.00033» – «0.00029», который дает «0,000004» или самое близкое представляемое значение , которое может быть 0, или оно может быть «0.000006».

С текущими математическими операциями с плавающей запятой не гарантируется, что (a / b) * b == a .

 #include  // defeat inline optimizations of 'a / b * b' to 'a' extern double bodge(int base, int divisor) { return static_cast(base) / static_cast(divisor); } int main() { int errors = 0; for (int b = 1; b < 100; ++b) { for (int d = 1; d < 100; ++d) { // b / d * d ... should == b double res = bodge(b, d) * static_cast(d); // but it doesn't always if (res != static_cast(b)) ++errors; } } printf("errors: %d\n", errors); } 

ideone сообщает 599 экземпляров, где (b * d) / d! = b, используя только 10 000 комбинаций 1 <= b <= 100 и 1 <= d <= 100.

Решение, описанное в часто задаваемых вопросах, заключается в применении ограничения гранулярности – для проверки, if (a == b +/- epsilon) .

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

C ++ 11 ввел std :: ratio в качестве основы для конверсий с фиксированной точкой между разными единицами времени.

Если вас интересуют только +0.0 и -0.0 , вы можете использовать fpclassify из . Например:

if( FP_ZERO == fpclassify(x) ) do_something;

Как отметил @Exceptyon, эта функция является «относительной» к значениям, которые вы сравниваете. Мера Epsilon * abs(x) будет масштабироваться на основе значения x, так что вы получите результат сравнения точно так же, как и epsilon , независимо от диапазона значений в x или y.

Если вы сравниваете нуль ( y ) с другим действительно маленьким значением ( x ), скажем, 1e-8, abs(xy) = 1e-8 будет по-прежнему намного больше, чем epsilon *abs(x) = 1e-13 . Поэтому, если вы не имеете дело с чрезвычайно маленьким числом, которое не может быть представлено в двойном типе, эта функция должна выполнять задание и будет соответствовать нулю только против +0 и -0 .

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

ps: Это аккуратная функция. Спасибо, что указали на это.

Простое сравнение номеров FP имеет свою специфику, и ключевым моментом является понимание формата FP (см. https://en.wikipedia.org/wiki/IEEE_floating_point )

Когда числа FP вычисляются по-другому, одно через sin (), другое, хотя exp (), строгое равенство не будет работать, хотя математически числа могут быть равными. Точно так же не будет работать равенство с константой. Фактически, во многих ситуациях номера FP не должны сравниваться с использованием строгого равенства (==)

В таких случаях следует использовать константу DBL_EPSIPON, которая является минимальным значением , не меняя представления 1.0, добавляемого к числу более 1.0. Для чисел с плавающей запятой не существует всего 2.0 DBL_EPSIPON. Между тем, DBL_EPSILON имеет показатель -16, что означает, что все числа, скажем, с показателем -34, будут абсолютно равны по сравнению с DBL_EPSILON.

Также см. Пример , почему 10.0 == 10.0000000000000001

Сравнение чисел с плавающей запятой dwo зависит от характера этих чисел, мы должны рассчитать DBL_EPSILON для них, что было бы значимым для сравнения. Просто мы должны умножить DBL_EPSILON на одно из этих чисел. Какой из них? Максимально, конечно

 bool close_enough(double a, double b){ if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b))) { return true; } return false; } 

Все остальные способы дадут вам ошибки с неравенством, которые могут быть очень трудно поймать

обратите внимание, что код:

 std::abs((x - y)/x) <= epsilon 

вы требуете, чтобы «относительная ошибка» на var была <= epsilon, а не абсолютная разница

  • Как отобразить двоичное представление float или double?
  • Проблемы сравнения с плавающей запятой MySQL
  • Двойной против BigDecimal?
  • сравнение значений float / double с использованием оператора ==
  • Как компьютер выполняет арифметику с плавающей запятой?
  • Как использовать деление с плавающей запятой в bash?
  • Почему деление двух целых чисел возвращает 0.0 в Java?
  • Почему (360/24) / 60 = 0 ... в Java
  • Функции сравнения с плавающей запятой для C #
  • Какова максимальная длина в символах, необходимых для представления любого двойного значения?
  • Сохранять точность с двойным в Java
  • Давайте будем гением компьютера.