.NET - Сравнение с плавающей точкой - PullRequest
6 голосов
/ 20 августа 2011

Сравнение чисел с плавающей точкой (double, float) в .net напрямую на равенство небезопасно. Двойное значение в переменной может меняться со временем на очень маленькую величину. Например, если вы установите переменную num (double) равной 0,2 объекта, через некоторое время этот объект ожидал в памяти, вы можете обнаружить, что num стал 0,1999999999999. Так что num == 0.2 будет ложным в этом случае. Мое решение этой проблемы - создать свойство для округления числа:

double Num
{
get{ return Math.Round(num, 1); }
}

После вызова get Num и возврата результата может ли это возвращаемое число снова измениться на 0,19 во время сравнения (Num == 0,2)? Это маловероятно, но гарантировано ли это?

Ответы [ 4 ]

6 голосов
/ 20 августа 2011

Нет, это не гарантируется.

Из MSDN - Math.Round:

Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или округлением банкира. Он минимизирует ошибок округления, возникающих в результате последовательного округления значения средней точки в одном направлении.

(акцент мой)

Точка - это минимизирует , а не обеспечивает.


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

Пример адаптирован с здесь :

double dValue = 0.2;

var diff = Math.Abs(num - dValue);
if( diff < 0.0000001 ) // need some min threshold to compare floating points
{
  // treat as equal
}

Рекомендуемое чтение: Что должен знать каждый учёный об арифметике с плавающей запятой .

3 голосов
/ 20 августа 2011

Верите ли вы в это или нет, это намеченное поведение и соответствует некоторому стандарту IEEE.

Невозможно представить аналоговое ежедневное значение, такое как массовое число или небольшая дробь с полнымверность в одном двоичном представлении.Числа с плавающей точкой в ​​.NET, такие как float или double, делают все возможное, чтобы минимизировать ошибку при назначении им чисел, поэтому, когда вы присваиваете 0,2 переменной, язык делал все возможное, чтобы выбрать представление с наименьшей ошибкой.

Дело не в том, что число каким-то образом ухудшается в памяти - это осознанный шаг.Если вы сравниваете числа с плавающей запятой, вы всегда должны разрешать регион с любой стороны вашего сравнения, который является приемлемым.Ваше представление 0.2 близко к очень большому количеству десятичных знаков.Это достаточно хорошо для вашего приложения?Это выглядит ярко на ваших глазах, но на самом деле это очень маленькая ошибка.При сравнении значений типа double и float (с целыми числами или друг с другом) вы всегда должны учитывать допустимую точность и принимать диапазон по обе стороны от ожидаемого результата.

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

2 голосов
/ 20 августа 2011

Переменные не меняются сами по себе.Если a == b в один момент времени, то a == b навсегда, пока вы не измените a или b.

У вас вполне может быть проблема, связанная с представимостью в типах данных с плавающей запятой, но она не яснав чем проблема.Ясно, что ваше текущее решение почти наверняка не очень хорошая идея.

0 голосов
/ 20 августа 2011

Используйте код, подобный этому, чтобы проверить двойность на равенство:

public static bool AreEqual(double d1, double d2, double delta)
{
    return Math.Abs(d1 - d2) < delta;
}
...