Почему новый BigDecimal ( "0.015" ). CompareTo (новый BigDecimal (0.015)) возвращает -1?
Почему new BigDecimal("0.015").compareTo(new BigDecimal(0.015))
возвращает -1?
Если я ожидаю, что эти два будут равны, есть ли альтернативный способ их сравнения?
Ответы
Ответ 1
A double
не может точно представить значение 0.015
. Ближайшим значением, которое он может представлять в своих 64 двоичных битах, является 0.01499999999999999944488848768742172978818416595458984375
. Конструктор new BigDecimal(double)
предназначен для сохранения точного значения аргумента double
, который никогда не может быть точно 0.015
. Отсюда результат вашего сравнения.
Однако, если вы показываете это значение double
, например:
System.out.println(0.01499999999999999944488848768742172978818416595458984375);
выводит 0.015
- который намекает на обходной путь. Преобразование double
в String
выбирает кратчайшее десятичное представление, необходимое для того, чтобы отличить его от других возможных значений double
.
Таким образом, если вы создадите BigDecimal
из представления double
String
, оно будет иметь значение больше, как вы ожидаете. Это сравнение верно:
new BigDecimal(Double.toString(0.015)).equals(new BigDecimal("0.015"))
Фактически для этой цели существует метод BigDecimal.valueOf(double)
, поэтому вы можете сократить это до
BigDecimal.valueOf(0.015).equals(new BigDecimal("0.015"))
Вы должны использовать конструктор new BigDecimal(double)
, только если ваша цель - сохранить точное двоичное значение аргумента. В противном случае вызовите BigDecimal.valueOf(double)
, чья документация говорит:
Это, как правило, предпочтительный способ преобразования double
(или float
) в BigDecimal
.
Или, используйте String
, если вы можете и полностью избегаете тонкостей double
.
Ответ 2
Из-за неточного характера арифметики с плавающей запятой они не совсем равны
System.out.println(new BigDecimal(0.015));
отображает
0.01499999999999999944488848768742172978818416595458984375
Ответ 3
Чтобы расширить ответ от @Reimeus, различные конструкторы для BigDecimal принимают разные типы ввода. Конструкторы с плавающей запятой принимают в качестве входных данных значение с плавающей запятой, и из-за ограничений способа хранения плавающих/удвоенных значений они могут хранить только точные значения, которые имеют мощность 2.
Так, например, 2⁻², или 0.25, могут быть представлены точно. 0,875 (2⁻¹ + 2⁻² + 2⁻³), поэтому его можно также точно представить. Пока число может быть представлено суммой степеней, где верхняя и нижняя мощность различаются не более чем на 53, тогда число может быть представлено точно. Подавляющее большинство чисел не соответствует этой схеме!
В частности, 0,15 не является степенью двух, равно как и не является суммой мощности двух, поэтому представление неточно.
Конструктор строк, с другой стороны, сохраняет его точно, используя другой формат для хранения номера. Следовательно, когда вы сравниваете их, они сравниваются как разные.
Ответ 4
Что на самом деле происходит здесь:
-
0.015
является примитивным двойником. Это означает, что как только вы его напишете, это уже не 0,015, а скорее 0.0149...
. Компилятор сохраняет его как двоичное представление в байт-коде.
-
BigDecimal
создается для хранения точно, что ему дано. В этом случае 0.0149...
-
BigDecimal
также может разбирать строки в точные представления. В этом случае "0.015"
анализируется ровно на 0.015
. Даже если double
не может представлять это число, BigDecimal
может
- Наконец, когда вы их сравниваете, вы можете видеть, что они не равны. Что имеет смысл.
При использовании BigDecimal
будьте осторожны с ранее используемым типом. String
, int
, long
останутся точными. float
и double
имеют обычную точность.