Безопасно ли проверять значения с плавающей запятой на равенство 0? - PullRequest
89 голосов
/ 27 января 2009

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

Хотя я могу понять неточности между 0,00000000000001 и 0,00000000000002, само по себе 0 довольно сложно испортить, поскольку это просто ничто. Если ты ничего неточен, это уже не ничто.

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

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

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

Ответы [ 9 ]

106 голосов
/ 28 января 2009

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

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

47 голосов
/ 28 января 2009

Если вам нужно провести много сравнений на «равенство», было бы неплохо написать небольшую вспомогательную функцию или метод расширения в .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);
15 голосов
/ 27 января 2009

Для вашего простого образца этот тест в порядке. Но как насчет этого:

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 );

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

13 голосов
/ 27 января 2009

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

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

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

...

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

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

6 голосов
/ 08 сентября 2011

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

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

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

3 голосов
/ 28 января 2009

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

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

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

2 голосов
/ 10 апреля 2009

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

0 голосов
/ 19 июня 2018

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

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

0 голосов
/ 15 декабря 2011

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

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

То же самое для поплавка:

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