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


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

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

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

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

будет ли это всегда возвращаться правда?

9 80

9 ответов:

это безопасное ожидать, что сравнение вернется true если и только если двойная переменная имеет значение именно 0.0 (что в исходном фрагменте кода, конечно, имеет место). Это согласуется с семантикой == оператора. 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 для двойной.Равно:

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

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

...

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

Также см. двойной.Эпсилон.

проблема возникает, когда вы сравниваете различные типы реализации значения с плавающей запятой, например, сравнивая 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 битах для double или 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;