Деление BigDecimal, возвращающее неверный результат для некоторых вычислений - PullRequest
2 голосов
/ 17 июня 2019

Я прочитал несколько вопросов о числах точек воспламенения и их математике. Мне кажется, что проблемы возникают только в больших масштабах (то есть 10+ десятичных знаков). Теперь моя проблема возникает уже с двумя десятичными знаками, и в некоторых случаях она довольно велика:

следующий код:

System.out.println(
        String.format("%.2f", BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2).doubleValue()));
System.out.println(
        String.format("%.2f", BigDecimal.valueOf(2.41).divide(BigDecimal.valueOf(2.73), 2).doubleValue()));

генерирует этот вывод соответственно:

0.80
0.89

Если я выполняю вычисления вручную (используя вычисления Google), я получаю следующие результаты:

0.72351421188
0.88278388278

для первых расчетов результаты действительно большие (~ 0,08 выкл.), А для вторых - очень низкие (~ 0,01 выкл.).

Есть ли какое-то вменяемое объяснение, почему ваш первый результат такой большой? Или любой способ получить правильный результат, используя BigDecimal?

Обратите внимание, что

System.out.println(2.8/3.87);

фактически возвращает правильный результат (0.7235142118863048).

Обратите также внимание, что String.format используется только для проверки, был ли изменен результат, что не так. BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2).doubleValue() дает точно такой же результат.

1 Ответ

6 голосов
/ 17 июня 2019

Проблема в том, что вы используете неправильный BigDecimal#divide для округления до 2 десятичных знаков. Вот доступные аргументы для методов BigDecimal#divide:

  • divide(BigDecimal divisor)
  • divide(BigDecimal divisor, int roundingMode)
  • divide(BigDecimal divisor, MathContext mc)
  • divide(BigDecimal divisor, RoundingMode roundingMode)
  • divide(BigDecimal divisor, int scale, int roundingMode)
  • divide(BigDecimal divisor, int scale, RoundingMode roundingMode)

Поскольку вы используете divide с аргументами BigDecimal и int, поэтому он использует divide(BigDecimal divisor, int roundingMode), где ваш 2 - режим округления, а NOT шкала , В этом случае 2 на самом деле ROUND_CEILING, а масштаб не указан.

Вместо этого вам придется использовать либо divide(BigDecimal divisor, int scale, int roundingMode), либо divide(BigDecimal divisor, int scale, RoundingMode roundingMode). Поэтому измените ваши звонки на:

System.out.println(BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2, 
  BigDecimal.ROUND_HALF_UP));
System.out.println(BigDecimal.valueOf(2.41).divide(BigDecimal.valueOf(2.73), 2, 
  BigDecimal.ROUND_HALF_UP));

(Не стесняйтесь использовать режим округления, отличный от ROUND_HALF_UP.)

Попробуйте онлайн.

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


Что касается упомянутых комментариев, существует три возможных способа создания BigDecimal с указанными значениями:

  • BigDecimal.valueOf(2.8)
  • new BigDecimal(2.8)
  • new BigDecimal("2.8")

new BigDecimal(2.8) по-прежнему выдает ошибки с плавающей запятой, поэтому я бы посоветовал использовать либо BigDecimal.valueOf(2.8), как вы уже сделали, либо конструктор String new BigDecimal("2.8").

Попробуйте онлайн.

Однако все это не имеет значения для проблем округления, поскольку использование правильного метода divide даст правильные результаты независимо от используемой инициализации BigDecimal:

Попробуйте онлайн.

...