Давным-давно я написал статью об использовании двойного за деньги. Тем не менее, многие разработчики все еще боятся, когда решение достаточно простое.
Проблема с использованием двойного за деньги
double имеет два типа ошибок. У него есть ошибка представления. то есть он не может точно представлять все возможные десятичные значения. Даже 0.1 не совсем это значение. Он также имеет ошибку округления из расчетов. т.е. когда вы выполняете вычисления, ошибка увеличивается.
1
2
3
4
5
6
7
8
9
|
double [] ds = { 0.1 , 0.2 , - 0.3 , 0.1 + 0.2 - 0.3 }; for ( double d : ds) { System.out.println(d + " => " + new BigDecimal(d)); } |
печать
1
2
3
4
|
0.1 => 0.1000000000000000055511151231257827021181583404541015625 0.2 => 0.200000000000000011102230246251565404236316680908203125 -0.3 => -0.299999999999999988897769753748434595763683319091796875 5.551115123125783E-17 => 5.5511151231257827021181583404541015625E-17 |
Вы можете видеть, что представление для 0.1 и 0.2 немного выше, чем эти значения, и -0.3 также немного выше. Когда вы печатаете их, вы получите более приятный 0,1 вместо фактического значения, представленного 0,1000000000000000055511151231257827021181583404541015625
Однако, когда вы добавляете эти значения вместе, вы получаете значение, которое немного выше 0.
Важно помнить, что эти ошибки не являются случайными . Они управляемы и ограничены.
Исправление ошибки округления
Как и во многих типах данных, таких как дата, у вас есть внутреннее представление значения и способ представления его в виде строки.
Это верно для двойника . Вы должны контролировать, как значение представляется в виде строки. Это может быть неожиданным, поскольку Java делает небольшое количество округления, поскольку ошибка представления не очевидна, однако, если у вас также есть ошибка округления для операций, это может вызвать шок.
Распространенной реакцией является предположение, что с этим ничего нельзя поделать, ошибка неуправляема, неузнаваема и опасна. Откажитесь от двойного и используйте BigDecimal
Однако ошибка ограничена стандартами IEE-754 и накапливается медленно.
Вокруг результата
И так же, как необходимость использовать TimeZone и Local для дат, вам необходимо определить точность результата перед преобразованием в строку.
Чтобы решить эту проблему, вам необходимо обеспечить соответствующее округление. С деньгами это легко, поскольку вы знаете, сколько подходящих десятичных разрядов, и если у вас нет $ 70 триллионов, вы не получите достаточно большую ошибку округления, которую вы не сможете исправить.
01
02
03
04
05
06
07
08
09
10
11
|
// uses round half up, or bankers' rounding public static double roundToTwoPlaces( double d) { return Math.round(d * 100 ) / 100.0 ; } // OR public static double roundToTwoPlaces( double d) { return (( long ) (d < 0 ? d * 100 - 0.5 : d * 100 + 0.5 )) / 100.0 ; } |
Если вы добавите это в результат, все еще будет небольшая ошибка представления, однако она недостаточно велика, чтобы Double.toString (d) не могла исправить ее.
1
2
3
4
5
6
7
8
9
|
double [] ds = { 0.1 , 0.2 , - 0.3 , 0.1 + 0.2 - 0.3 }; for ( double d : ds) { System.out.println(d + " to two places " + roundToTwoPlaces(d) + " => " + new BigDecimal(roundToTwoPlaces(d))); } |
печать
1
2
3
4
|
0.1 to two places 0.1 => 0.1000000000000000055511151231257827021181583404541015625 0.2 to two places 0.2 => 0.200000000000000011102230246251565404236316680908203125 -0.3 to two places -0.3 => -0.299999999999999988897769753748434595763683319091796875 5.551115123125783E-17 to two places 0.0 => 0 |
Вывод
Если у вас есть стандарт проекта, который говорит, что вы должны использовать BigDecimal или double, это то, что вы должны следовать. Тем не менее, нет веских технических причин бояться использовать двойные деньги.
Справка: удвойте свои деньги снова от нашего партнера JCG Питера Лори на Vanilla Java .