Мы знаем, что использование double
для валюты подвержено ошибкам и не рекомендуется. Тем не менее, я еще не видел пример реалистичный, где BigDecimal
работает, пока double
терпит неудачу и не может быть просто исправлен некоторым округлением.
Заметим, что тривиальные задачи
double total = 0.0;
for (int i = 0; i < 10; i++) total += 0.1;
for (int i = 0; i < 10; i++) total -= 0.1;
assertTrue(total == 0.0);
не учитываются, поскольку они тривиально решаются путем округления (в этом примере все должно быть от нуля до шестнадцати знаков после запятой).
Вычисления с суммированием больших значений могут потребовать некоторого промежуточного рутинга, но при условии, что общая сумма валюты равна USD 1e12
, Java double
( т.е. стандартная двойная точность IEEE) с ее 15 десятичными цифрами по-прежнему является достаточным событием для центов.
Вычисления с делением вообще неточны даже при BigDecimal
. Я могу построить вычисление, которое не может быть выполнено с помощью double
s, но может быть выполнено с помощью BigDecimal
с использованием шкалы 100, но это не то, с чем вы можете столкнуться в действительности.
Я не утверждаю, что такого реалистического примера не существует, просто я этого еще не видел.
Я также уверен, что использование double
более подвержено ошибкам.
Пример
То, что я ищу, - это метод, подобный следующему (на основе ответа Роланда Иллига)
/**
* Given an input which has three decimal places,
* round it to two decimal places using HALF_EVEN.
*/
BigDecimal roundToTwoPlaces(BigDecimal n) {
// To make sure, that the input has three decimal places.
checkArgument(n.scale() == 3);
return n.round(new MathContext(2, RoundingMode.HALF_EVEN));
}
вместе с тестом вроде
public void testRoundToTwoPlaces() {
final BigDecimal n = new BigDecimal("0.615");
final BigDecimal expected = new BigDecimal("0.62");
final BigDecimal actual = roundToTwoPlaces(n);
Assert.assertEquals(expected, actual);
}
Когда это становится наивно переписанным с помощью double
, тогда тест может потерпеть неудачу (он не для данного ввода, но для других). Однако это можно сделать правильно:
static double roundToTwoPlaces(double n) {
final long m = Math.round(1000.0 * n);
final double x = 0.1 * m;
final long r = (long) Math.rint(x);
return r / 100.0;
}
Он уродливый и подверженный ошибкам (и, вероятно, может быть упрощен), но его можно легко инкапсулировать где-то. Вот почему я ищу больше ответов.