Java BigDecimal проблемы точности - PullRequest
14 голосов
/ 21 марта 2012

Я знаю, что следующее поведение - старая проблема, но все же я не понимаю.

System.out.println(0.1 + 0.1 + 0.1);    

Или хотя я использую BigDecimal

System.out.println(new BigDecimal(0.1).doubleValue()
    + new BigDecimal(0.1).doubleValue()
    + new BigDecimal(0.1).doubleValue());

Почему этот результатэто: 0.30000000000000004 вместо: 0.3?

Как я могу решить эту проблему?

Ответы [ 5 ]

27 голосов
/ 21 марта 2012

То, что вы на самом деле хотите, это

new BigDecimal("0.1")
 .add(new BigDecimal("0.1"))
 .add(new BigDecimal("0.1"));

Конструктор new BigDecimal(double) получает всю неточность double, поэтому к тому времени, как вы сказали 0.1, вы уже ввели ошибку округления. Использование конструктора String позволяет избежать ошибки округления, связанной с прохождением через double.

9 голосов
/ 21 марта 2012

Первый никогда , никогда не используйте двойной конструктор BigDecimal.В некоторых ситуациях это может быть правильным, но в большинстве случаев это не

Если вы можете контролировать свой ввод, используйте конструктор BigDecimal String, как уже предлагалось.Таким образом, вы получите именно то, что вы хотите.Если у вас уже есть double (это может произойти в конце концов), не используйте конструктор double, а вместо этого статический метод valueOf.Это имеет то хорошее преимущество, что мы получаем каноническое представление двойного числа, которое, по крайней мере, смягчает проблему ... и результат обычно намного более интуитивен.

4 голосов
/ 21 марта 2012

Это не проблема Java, а проблема компьютеров в целом.Основная проблема заключается в преобразовании из десятичного формата (человеческий формат) в двоичный формат (компьютерный формат).Некоторые числа в десятичном формате не могут быть представлены в двоичном формате без бесконечно повторяющихся десятичных знаков.

Например, десятичное число 0,3 равно 0,01001100 ... двоичное. Но компьютер имеет ограниченные «слоты» (биты) для сохранения числа,поэтому он не может сохранить все бесконечное представление.Сохраняет только 0.01001100110011001100 (например).Но это десятичное число больше не 0.3, а 0.30000000000000004.

3 голосов
/ 21 марта 2012

Проблема, с которой вы столкнулись, состоит в том, что 0,1 представляется с немного большим числом, например,

System.out.println(new BigDecimal(0.1));

печать

0.1000000000000000055511151231257827021181583404541015625

Double.toString () учитывает эту ошибку представления, поэтому вы ее не видите.

Аналогичным образом, 0,3 представлено значением, немного меньшим, чем оно есть на самом деле.

0.299999999999999988897769753748434595763683319091796875

Если вы умножите представленное значение 0,1 на 3, вы не получите представленное значение для 0,3, вместо этого вы получите что-то немного большее

0.3000000000000000166533453693773481063544750213623046875

Это не только ошибка представления, но и ошибка округления, вызванная операциями. Это больше, чем корректирует Double.toString (), поэтому вы видите ошибку округления.

Мораль истории, если вы используете float или double, также округлите решение соответствующим образом.

double d = 0.1 + 0.1 + 0.1;
System.out.println(d);
double d2 = (long)(d * 1e6 + 0.5) / 1e6; // round to 6 decimal places.
System.out.println(d2);

печать

0.30000000000000004
0.3
3 голосов
/ 21 марта 2012

Попробуйте:

BigDecimal sum = new BigDecimal(0.1).add(new BigDecimal(0.1)).add(new BigDecimal(0.1));

РЕДАКТИРОВАТЬ: На самом деле, глядя на Javadoc, это будет иметь ту же проблему, что и оригинал.Конструктор BigDecimal(double) создаст BigDecimal, соответствующий точному представлению с плавающей запятой 0,1, что не совсем равно 0,1.

Это, однако, дает точный результат, поскольку целые числа МОЖНО всегда выражать точнов представлении с плавающей точкой:

BigDecimal one = new BigDecimal(1);
BigDecimal oneTenth = one.divide(new BigDecimal(10));

BigDecimal sum = oneTenth.add(oneTenth).add(oneTenth);
...