Почему новый 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 имеют обычную точность.