Подтвердить что ты не робот

Проблемы с высокой точностью 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?

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

4b9b3361

Ответ 1

На самом деле вы хотите

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

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

Ответ 2

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

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

Ответ 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.

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

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

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

Ответ 4

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

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

Ответ 5

Проблема заключается в том, что 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