2012-03-20 5 views
13

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

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?

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

+12

Вы ... не используете 'BigDecimal'. Вы делаете * точно такую ​​же вещь *, как добавление двойников. 'doubleValue()' возвращает ... double. См. Javadoc для 'BigDecimal' о том, как добавить/вычесть/etc –

+0

Повторите вычисления' double', см. [Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой] (http://docs.oracle.com/cd/ E19957-01/806-3568/ncg_goldberg.html). Java решает это, предоставляя параметры формата для вывода. –

+0

Почему вы используете 'doubleValue()' ??? Я думал, вы хотите десятичный тип. –

ответ

26

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

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

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

+0

благодарит всех! Теперь ... Я могу это понять. ' // ЭТО РАБОТАЕТ ДЛЯ МОЕЙ ПРОБЛЕМЫ Двойное значение = 0.1d; BigDecimal total = new BigDecimal ("0.0"); for (int i = 0; i <9; i ++) { \t total = total.add (новый BigDecimal (значение.toString())); } System.out.print (всего); \t // НЕ РАБОТАЕТ System.out.print (0.1d + 0.1d + 0.1d); // НЕ РАБОТАЕТ System.out.println (новый BigDecimal (0.1) .add (новый BigDecimal (0.1)). Add (новый BigDecimal (0.1))); // IT WORKS System.out.println (новый BigDecimal ("0.1"). Add (новый BigDecimal ("0.1")). Add (новый BigDecimal ("0.1"))); // ЭТО РАБОТАЕТ, НО С МЕНЬШЕЙ ТОЧНОСТЬЮ System.out.println (0.1f + 0.1f + 0.1f); ' – Jefferson

+0

Небольшая поправка: _« неточность двойного »_ - это технически не верно. Двойник может отлично представлять «0,1» - с мантиссой 1 и показателем -1. Это преобразование 'double' в' BigDecimal', которое преобразует представление с плавающей запятой в нормальное двоичное представление где-то на этом пути. Вот почему двойной конструктор ошибочен. – theeggman85

+2

Хм, что? Нет, не может. '0,1' не может быть представлен как дробь в двоичном, период, который представляет собой« двойные ». Мантисса 1 и показатель -1 дает вам 0.5. –

3

Попробуйте это:

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

EDIT: На самом деле, глядя на 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); 
+5

НИКОГДА не используйте конструктор 'double' для большого десятичного числа (ну могут быть некоторые редкие ситуации, но на самом деле это плохая идея). Если вы можете, используйте конструктор строк (это будет точно), если у вас уже есть двойное использование 'valueOf', так что мы не получаем дополнительной фиктивной точности ... – Voo

3

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

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

+0

http://en.wikipedia.org/ wiki/Binary-coded_decimal –

8

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

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

2

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

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

печатает

0.1000000000000000055511151231257827021181583404541015625 

Двойник.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 
Смежные вопросы