обзор
В предыдущей статье я описал, почему BigDecimal большую часть времени не является ответом. Хотя можно создавать ситуации, в которых double вызывает ошибку, также легко создавать ситуации, когда BigDecimal получает ошибку.
BigDecimal легче понять, но легче ошибиться.
Неподтвержденное свидетельство — то, что у младших разработчиков нет такой большой проблемы с правильным BigDecimal, как они получают двойной с правильным округлением. Тем не менее, я скептически отношусь к этому, потому что в BigDecimal ошибка также намного легче остается незамеченной.
Давайте возьмем этот пример, где double дает неправильный ответ.
|
1
2
3
4
5
6
7
8
9
|
double d = 1.00;d /= 49;d *= 49 * 2;System.out.println("d=" + d);BigDecimal bd = BigDecimal.ONE;bd = bd .divide(BigDecimal.valueOf(49), 2, BigDecimal.ROUND_HALF_UP);bd = bd.multiply(BigDecimal.valueOf(49*2));System.out.println("bd=" + bd); |
печать
|
1
2
|
d=1.9999999999999998bd=1.96 |
В этом случае double выглядит неправильно, ему нужно округление, которое даст правильный ответ 2.0. Однако BigDecimal выглядит правильно, но это не из-за ошибки представления. Мы могли бы изменить деление для большей точности, но вы всегда получите ошибку представления, хотя вы можете контролировать, насколько мала эта ошибка.
Вы должны убедиться, что числа реальны и использовать округление.
Даже с BigDecimal вы должны использовать соответствующее округление. Допустим, у вас есть кредит на 1 000 000 долларов и вы применяете 0,0005% годовых в день. Учетная запись может иметь только целое количество центов, поэтому округление необходимо, чтобы получить реальную сумму денег. Если не делать этого, сколько времени потребуется, чтобы сделать разницу в 1 цент?
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
double interest = 0.0005;BigDecimal interestBD = BigDecimal.valueOf(interest);double amount = 1e6;BigDecimal amountBD = BigDecimal.valueOf(amount);BigDecimal amountBD2 = BigDecimal.valueOf(amount);long i = 0;do { System.out.printf("%,d: BigDecimal: $%s, BigDecimal: $%s%n", i, amountBD, amountBD2); i++; amountBD = amountBD.add(amountBD.multiply(interestBD) .setScale(2, BigDecimal.ROUND_HALF_UP)); amountBD2 = amountBD2.add(amountBD2.multiply(interestBD));} while (amountBD2.subtract(amountBD).abs() .compareTo(BigDecimal.valueOf(0.01)) < 0);System.out.printf("After %,d iterations the error was 1 cent and you owe %s%n", i, amountBD); |
наконец печатает
|
1
2
3
|
8: BigDecimal: $1004007.00, BigDecimal: $1004007.00700437675043756250390625000000000000000After 9 iterations the error was 1 cent and you owe 1004509.00 |
Вы можете округлить результат, но это скрывает тот факт, что вы потеряли цент, даже если вы использовали BigDecimal.
двойной в конечном итоге имеет ошибку представления
Даже если вы используете соответствующее округление, double даст вам неверный результат. Это намного позже, чем в предыдущем примере.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
double interest = 0.0005;BigDecimal interestBD = BigDecimal.valueOf(interest);double amount = 1e6;BigDecimal amountBD = BigDecimal.valueOf(amount);long i = 0;do { System.out.printf("%,d: double: $%.2f, BigDecimal: $%s%n", i, amount, amountBD); i++; amount = round2(amount + amount * interest); amountBD = amountBD.add(amountBD.multiply(interestBD) .setScale(2, BigDecimal.ROUND_HALF_UP));} while (BigDecimal.valueOf(amount).subtract(amountBD).abs() .compareTo(BigDecimal.valueOf(0.01)) < 0);System.out.printf("After %,d iterations the error was 1 cent and you owe %s%n", i, amountBD); |
наконец печатает
|
1
2
|
22,473: double: $75636308370.01, BigDecimal: $75636308370.01After 22,474 iterations the error was 1 cent and you owe 75674126524.20 |
С точки зрения ИТ мы имеем ошибку в один цент, с точки зрения бизнеса у нас есть клиент, который не выплачивал более 9 лет и должен банку 75,6 миллиарда долларов, что достаточно, чтобы обанкротить банк. Если бы только ИТ-специалист использовал BigDecimal !?
Вывод
Моя последняя рекомендация: вам следует использовать то, что вам удобно, не забывать о округлении, использовать реальные числа, а не то, что производит математика, например, могу ли я получить доли цента или я могу торговать долями доли. Не забывайте о перспективах бизнеса. Вы можете обнаружить, что BigDecimal имеет больше смысла для вашей компании, вашего проекта или вашей команды.
Не думайте, что BigDecimal — единственный способ, не думайте, что проблемы с двойными гранями не применимы и к BigDecimal. BigDecimal не является билетом для лучшей практики кодирования, потому что самодовольство — верный способ введения ошибок.
| Ссылка: | Сложная двойная ошибка от нашего партнера JCG Питера Лоури из блога Vanilla Java . |