Проблемы с высокой точностью 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
?
Как я могу это решить?
Ответы
Ответ 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