Проблемы точности 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?

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


person Jefferson    schedule 20.03.2012    source источник
comment
Вы ... не используете BigDecimal. Вы делаете в точности то же самое, как добавляете двойники. doubleValue() возвращает ... дубль. См. Javadoc для BigDecimal о том, как складывать / вычитать / и т. Д.   -  person Brian Roach    schedule 21.03.2012
comment
По поводу double расчетов см. Что должен знать каждый компьютерный ученый о плавающих -Точечная арифметика. Java решает эту проблему, предоставляя параметры формата для вывода.   -  person Andrew Thompson    schedule 21.03.2012
comment
Почему вы используете _1 _ ??? Я думал, тебе нужен десятичный тип.   -  person Kerrek SB    schedule 21.03.2012


Ответы (5)


На самом деле ты хочешь

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

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

person Louis Wasserman    schedule 20.03.2012
comment
Спасибо за все! Теперь ... я могу это понять. ´ // ЭТО РАБОТАЕТ ДЛЯ МОЕЙ ПРОБЛЕМЫ Double value = 0.1d; BigDecimal total = новый BigDecimal (0,0); for (int i = 0; i ‹9; i ++) {total = total.add (новый BigDecimal (value.toString ())); } System.out.print (всего); // НЕ РАБОТАЕТ System.out.print (0.1d + 0.1d + 0.1d); // НЕ РАБОТАЕТ System.out.println (new BigDecimal (0.1) .add (new BigDecimal (0.1)). Add (new BigDecimal (0.1))); // ЭТО РАБОТАЕТ System.out.println (new BigDecimal (0.1) .add (new BigDecimal (0.1)). Add (new BigDecimal (0.1))); // РАБОТАЕТ, НО МЕНЬШЕ ТОЧНОСТИ System.out.println (0.1f + 0.1f + 0.1f); ` - person Jefferson; 21.03.2012
comment
Небольшая поправка: неточность дубля - это технически неверно. Двойник может идеально представлять 0.1 - с мантиссой, равной 1, и показателем степени -1. Это преобразование double в BigDecimal, которое преобразует представление с плавающей запятой в нормальное двоичное представление где-то на этом пути. Вот почему двойной конструктор ошибочен. - person theeggman85; 28.07.2015
comment
Гм, что? Нет, не может. 0.1 не может быть представлен как дробь в двоичном формате, точка, как doubles представлены. Мантисса, равная 1, и показатель степени -1, дают вам 0,5. - person Louis Wasserman; 28.07.2015
comment
Ты прав, моя беда. Я неправильно читал двоичное представление двойника. - person theeggman85; 30.07.2015

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

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

person Voo    schedule 20.03.2012

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

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

person Jakub Zaverka    schedule 20.03.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);
person James Cronen    schedule 20.03.2012
comment
НИКОГДА не используйте конструктор double для больших десятичных чисел (ну, могут быть некоторые редкие ситуации, но на самом деле это плохая идея). Если можете, используйте конструктор строк (это будет точно), если у вас уже есть двойное использование valueOf, так мы не получим дополнительной ложной точности ... - person Voo; 21.03.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
person Peter Lawrey    schedule 21.03.2012