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

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

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

Но я не знаю много об этой теме, так что не для меня.

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

Это всегда вернет истину?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вот ответ от Кворы.

На самом деле, я думаю, что лучше использовать следующие коды для сравнения двойного значения против 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; 
  • Равновесие и допуски с плавающей точкой
  • Давайте будем гением компьютера.