Почему существует разница между тем же значением, хранящимся как число с плавающей точкой, и двойным в Java? - PullRequest
5 голосов
/ 01 октября 2009

Я ожидал, что выдаст следующий код: "Both are equal", но получил "Both are NOT equal":

float a=1.3f;
double b=1.3;
if(a==b)
{
 System.out.println("Both are equal");
}
else{
 System.out.println("Both are NOT equal");
}

В чем причина?

Ответы [ 7 ]

26 голосов
/ 01 октября 2009

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

Чтобы по-разному понять, почему это происходит, предположим, что у нас было два десятичных типа с плавающей запятой - decimal5 и decimal10, где число представляет количество значащих цифр. Теперь предположим, что мы попытались присвоить значение «треть» им обоим. Вы бы в конечном итоге с

decimal5 oneThird = 0.33333
decimal10 oneThird = 0.3333333333

Очевидно, что эти значения не равны. Здесь то же самое, только с разными базами.

Однако, если вы ограничите значения менее точным типом, вы обнаружите, что они равны равны в данном конкретном случае :

double d = 1.3d;
float f = 1.3f;
System.out.println((float) d == f); // Prints true

Однако это не гарантируется. Иногда приближение от десятичного литерала к двойному представлению, а затем приближение этого значения к представлению с плавающей запятой оказывается менее точным, чем приближение с прямой десятичной запятой к плавающему. Один пример этого 1.0000001788139343 (спасибо Stephentyrone за поиск этого примера).

Чуть безопаснее, вы можете сделать сравнение между двойными числами, но используйте литерал float в исходном назначении:

double d = 1.3f;
float f = 1.3f;
System.out.println(d == f); // Prints true

В последнем случае это немного похоже на высказывание:

decimal10 oneThird = 0.3333300000

Однако , как указано в комментариях, вы почти наверняка не должны сравнивать значения с плавающей запятой с ==. Это почти никогда не то, что нужно делать, именно из-за такого рода вещей. Обычно, если вы хотите сравнить два значения, вы делаете это с неким «нечетким» сравнением на равенство, проверяя, достаточно ли эти два числа «достаточно близки» для ваших целей. Для получения дополнительной информации см. Страницу Java Traps: double .

Если вам действительно нужно проверить на абсолютное равенство, это обычно означает, что вы должны использовать другой числовой формат в первую очередь - например, для финансовых данных вы, вероятно, должны использовать BigDecimal.

11 голосов
/ 01 октября 2009

Число с плавающей запятой - это число с плавающей запятой одинарной точности. Double - это число с плавающей запятой двойной точности. Подробнее здесь: http://www.concentric.net/~Ttwang/tech/javafloat.htm

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

Например:

float a = 1.3f;
double b = 1.3;
float delta = 0.000001f;
if (Math.abs(a - b) < delta)
{
    System.out.println("Close enough!");
}
else
{
    System.out.println("Not very close!");
}

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

2 голосов
/ 01 октября 2009
float a=1.3f;
double b=1.3; 

На данный момент у вас есть две переменные, содержащие двоичные приближения к вещественному числу 1.3. Первое приближение с точностью до 7 десятичных знаков, а второе с точностью до 15 десятичных знаков.

if(a==b) { 

Выражение a==b оценивается в два этапа. Сначала значение a преобразуется из float в double путем заполнения двоичного представления. Результат по-прежнему точен только до 7 десятичных цифр как представление Real 1.3. Далее вы сравниваете два разных приближения. Поскольку они разные, результат a==b равен false.

Есть два урока:

  1. Литералы с плавающей точкой (и двойные) почти всегда являются приближениями; например фактическое число, соответствующее литералу 1.3f, не совсем равно действительному числу 1.3.

  2. Каждый раз, когда вы выполняете вычисления с плавающей запятой, появляются ошибки. Эти ошибки имеют тенденцию накапливаться. Поэтому, когда вы сравниваете числа с плавающей запятой / двойные числа, обычно ошибочно использовать простые "==", "<" и так далее. Вместо этого вы должны использовать <code>|a - b| < delta, где delta выбрано соответствующим образом. (И выяснить, что является подходящим delta, тоже не всегда просто.)

  3. Вы должны были пройти этот курс в численном анализе: -)

2 голосов
/ 01 октября 2009

Читать эту статью .

Приведенная выше статья ясно иллюстрирует примеры вашего сценария при использовании типов double и float.

2 голосов
/ 01 октября 2009

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

0 голосов
/ 09 апреля 2017

На самом деле ни float, ни double не могут хранить 1.3. Я не шучу. Смотрите это видео внимательно.

https://www.youtube.com/watch?v=RtHKwsXuRkk&index=50&list=PL6pxHmHF3F5JPdnEqKALRMgogwYc2szp1

0 голосов
/ 07 июня 2013

Проблема в том, что Java (и, увы, также .NET) не согласуется с тем, представляет ли значение float единственную точную числовую величину или диапазон величин. Если считается, что float представляет точное числовое количество вида Mant * 2^Exp, где Mant - это целое число от 0 до 2 ^ 25, а Exp - это целое число), то попытка привести любое число, не являющееся эта форма для float должна вызвать исключение. Если считается, что он представляет собой «локус чисел, для которого определенное представление в приведенной выше форме считается наиболее вероятным», то приведение типа «двойное к плавающему» будет правильным даже для значений double, не указанных выше. форма [приведение double, которое лучше всего представляет количество к float, почти всегда дает float, который лучше всего представляет это количество, хотя в некоторых угловых случаях (например, числовые количества в диапазоне от 8888888.500000000001 до 8888888.500000000932) float выбранное значение может быть на несколько частей на триллион хуже, чем наилучшее из возможных float представление действительного числового количества].

Чтобы использовать аналогию, предположим, что у двух человек есть объект длиной десять сантиметров, и они измеряют его. Боб использует дорогой набор калибров и определяет, что его объект имеет длину 3.937008 ". Джо использует рулетку и определяет, что его объект имеет длину 3 15/16". Объекты одинакового размера? Если кто-то преобразует измерение Джо в миллионные доли дюйма (3,937500 "), измерения будут выглядеть по-другому, но вместо этого один из них преобразует измерение Боба в ближайшую дробь 1/256", они будут выглядеть равными. Хотя первое сравнение может показаться более «точным», последнее может быть более значимым. Измерение Джо, если 3 15/16 «на самом деле не означает 3,937500» - это означает «расстояние, которое с помощью рулетки неотличимо от 3 15/16». И 3.937008 ", как и размер объекта Джо, расстояние, которое с помощью рулетки было бы неотличимо от 3 15 / 16.

К сожалению, хотя было бы более целесообразно сравнивать измерения с использованием более низкой точности, правила сравнения Java с плавающей точкой предполагают, что float представляет единственную точную числовую величину, и выполняет сравнения на этой основе. Хотя в некоторых случаях это полезно (например, для определения того, будет ли конкретное double, полученное путем приведения некоторого значения к float и обратно к double, соответствовать начальному значению), в общем случае прямые сравнения на равенство между float и double не имеют смысла. Хотя Java этого не требует, всегда следует приводить операнды сравнения с плавающей точкой к одному и тому же типу. Семантика, возникающая в результате приведения double к float перед сравнением, отличается от семантики приведения float к double, и поведение Java выбирается по умолчанию (приведение float к double) часто семантически неверен.

...