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

StackOverflow https://stackoverflow.com/questions/485175

Вопрос

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

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

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

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

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

Это было полезно?

Решение

Это так безопасный ожидать, что сравнение вернет true тогда и только тогда, когда переменная double имеет значение ровно 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 следует использовать с осторожностью, потому что два явно эквивалентных значения могут быть неравными из-за разной точности двух значений.В следующем примере сообщается что значение Double .3333 и Значение Double, возвращаемое путем деления 1 на 3, неравны.

...

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

Кроме того, смотрите Двойной.Эпсилон.

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

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

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

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

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

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

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

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

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

На самом деле, я думаю, что лучше использовать следующие коды для сравнения двойного значения с равным 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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top