Безопасно ли проверять значения с плавающей запятой на равенство 0?

Я знаю, что вы не можете полагаться на равенство между значениями двойного или десятичного типа, но мне интересно, является ли 0 особым случаем.

Пока я могу понять неточности между 0.00000000000001 и 0.00000000000002, 0 сам по себе довольно сложно испортить, так как это просто ничего. Если вы неточенны ни на что, это уже не больше.

Но я не очень разбираюсь в этой теме, поэтому не могу сказать.

double x = 0.0;
return (x == 0.0) ? true : false;

Будет ли это всегда возвращать true?

Ответ 1

Можно с уверенностью ожидать, что сравнение вернет true если и только если переменная типа double имеет значение ровно 0.0 (что, конечно, имеет место в вашем исходном фрагменте кода). Это согласуется с семантикой оператора ==. a == b означает " a равно b ".

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

Ответ 2

Если вам нужно сделать много сравнений "равенства", может быть хорошей идеей написать небольшую вспомогательную функцию или метод расширения в .NET 3.5 для сравнения:

public static bool AlmostEquals(this double double1, double double2, double precision)
{
    return (Math.Abs(double1 - double2) <= precision);
}

Это можно использовать следующим образом:

double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);

Ответ 3

Для вашего простого примера, этот тест в порядке. Но как насчет этого:

bool b = ( 10.0 * .1 - 1.0 == 0.0 );

Помните, что .1 - это повторяющееся десятичное число в двоичном выражении и не может быть представлено точно. Затем сравните это с этим кодом:

double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );

Я оставлю вас для запуска теста, чтобы увидеть фактические результаты: вы, скорее всего, запомните его таким образом.

Ответ 4

Из записи MSDN для Double.Equals:

Точность в сравнении

Метод Equals должен использоваться с осторожно, потому что два, по-видимому, эквивалентные значения могут быть неравными к различной точности двух значения. Следующие примеры отчетов что двойное значение .3333 и Двойной возврат путем деления 1 на 3 неравны.

...

Вместо сравнения для равенства, один рекомендуемый метод включает определяя приемлемый запас разница между двумя значениями (например, 0,01% от одного из значений). Если абсолютная величина разницы между двумя значениями меньше или равный этой границе, разница вероятно, объясняется различиями в точность и, следовательно, значения вероятно, будут равны. Следующие пример использует эту технику для сравнения .33333 и 1/3, два двойных значения что найден предыдущий пример кода быть неравным.

Также см. Double.Epsilon.

Ответ 5

Проблема возникает, когда вы сравниваете различные типы реализации значений с плавающей запятой, например. сравнивая float с double. Но с тем же типом это не должно быть проблемой.

float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true

Проблема в том, что программист иногда забывает, что для сравнения используется неявный тип cast (double to float), и это приводит к ошибке.

Ответ 6

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

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

Вы также можете начать с присвоения целого числа и простыми сравнениями продолжать работать, придерживаясь добавления, вычитания или умножения на целые числа (при условии, что результат меньше 24 бит для float abd 53 бит для double), Таким образом, вы можете обрабатывать поплавки и удваивать как целые числа в определенных контролируемых условиях.

Ответ 7

Нет, это не нормально. Так называемые денормализованные значения (субнормальные) при сравнении равны 0.0 сравнивались бы как ложные (отличные от нуля), но при использовании в уравнении нормализовались бы (становились 0.0). Таким образом, использование этого как механизма, чтобы избежать деления на ноль, небезопасно. Вместо этого добавьте 1.0 и сравните с 1.0. Это обеспечит, чтобы все субнормали рассматривались как ноль.

Ответ 8

Попробуйте это, и вы обнаружите, что == не является надежным для double/float.
double d = 0.1 + 0.2; bool b = d == 0.3;

Вот ответ от Quora.

Ответ 9

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

double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;

То же самое для float:

float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;