Странное поведение Java: почему добавление удваивается с ТОЧНО двумя десятичными знаками приводит к удвоению с БОЛЕЕ ЧЕМ два десятичных знака?
Если у меня есть массив двойников, каждый из которых имеет ТОЧНО два десятичных знака, добавьте их полностью через цикл и распечатайте общее количество, то, что выходит, - это число с БОЛЕЕ двумя десятичными знаками. Что странно, потому что теоретически добавление двух чисел, каждое из которых имеет 2 и только 2 десятичных разряда, НИКОГДА не приведет к числу, которое имеет ненулевую цифру за пределами сотых мест.
Попробуйте выполнить этот код:
double[] d = new double[2000];
for (int i = 0; i < d.length; i++) {
d[i] = 9.99;
}
double total = 0,00;
for (int i = 0; i < d.length; i++) {
total += d[i];
if (("" + total).matches("[0-9]+\\.[0-9]{3,}")) { // if there are 3 or more decimal places in the total
System.out.println("total: " + total + ", " + i); // print the total and the iteration when it occured
}
}
На моем компьютере это печатает:
total: 59.940000000000005, 5
Если я округлю итог до двух знаков после запятой, тогда я бы получил тот же номер, что и я, если бы вручную добавил 9.99 шесть раз в калькулятор. Но как это происходит, и откуда появляются дополнительные десятичные знаки? Я делаю что-то неправильно или (я сомневаюсь, что это вероятно), это ошибка Java?
Ответы
Ответ 1
Вы знакомы с преобразованием базы 10 в базовое 2 (десятичное в двоичное) для фракций? Если нет, посмотрите.
Тогда вы увидите, что хотя 9.99 выглядит довольно нормально в базе 10, на самом деле это не выглядит красивым в двоичном формате; Это похоже на повторяющееся десятичное число, но в двоичном. Я уверен, что вы видели повторяющийся десятичный знак, верно? Это не конец. Но Java (или любой язык в этом отношении) должен сохранять эту бесконечную последовательность цифр в ограниченном числе байтов. И это, когда появляются дополнительные цифры. Когда вы конвертируете этот усеченный двоичный код обратно в десятичный, вы действительно имеете дело с другим числом. Число, хранящееся в переменной, не равно 9.99, это что-то вроде 9.9999999991 (просто пример, я не выработал математику).
Но вы, вероятно, заинтересованы в том, как решить это, не так ли? Посмотрите на класс BigDecimal. Это то, что вы хотите использовать для своих расчетов, особенно при работе с валютой. Кроме того, найдите DecimalFormat, который является классом для записи числа как правильно отформатированной строки. Я думаю, что это округляет вас, если вы хотите показать только 2 десятичных цифры, и ваш номер, например, намного больше.
Ответ 2
Если у меня есть массив удвоений, каждый из которых имеет ТОЧНО два десятичных знака
Позвольте остановиться там, потому что я подозреваю, что вы этого не делаете. Например, вы даете 9.99 в своем примере кода. Это не совсем 9.99. То, что "ближайший двойной до 9.99", как и сам 9.99, не может быть точно представлен в двоичной плавающей точке.
В этот момент остальная часть ваших рассуждений выходит из окна.
Если вам нужны значения с точным числом десятичных цифр, вы должны использовать тип, который хранит значения десятичным способом, например BigDecimal
. В качестве альтернативы, храните все как целые числа и "знайте", что вы на самом деле помните "значение * 100".
Ответ 3
Двойные пары представлены в двоичном формате на компьютере(). Это означает, что некоторые цифры не могут быть представлены точно, поэтому компьютер будет использовать самое близкое число, которое может быть представлено.
например. 10.5 = 2 ^ 3 + 2 + 2 ^ (- 1) = 1.0101 * 2 ^ 3 (здесь мантисса находится в двоичном виде)
но 10.1 = 2 ^ 3 + 2 + 2 ^ (- 4) +2 ^ (- 5) + (здесь бесконечный ряд) = 1.0100001... * 2 ^ 3
9.99 - такое число с бесконечным представлением. Таким образом, когда вы добавляете их вместе, в вычислении используется конечное представление, используемое компьютером, и результат будет еще более далеким от математической суммы, чем оригиналы от их истинного представления. Вот почему вы видите больше цифр, чем указано в исходных номерах.
Ответ 4
это из-за арифметики с плавающей запятой.
удваивает, а floats - не точно действительные числа, существует конечное количество бит для их представления, в то время как существует бесконечное число действительных чисел [в любом диапазоне], поэтому вы не можете представлять все реальные числа - Вы получаете самое близкое число, которое вы можете иметь с представлением с плавающей запятой.
Всякий раз, когда вы имеете дело с плавающими точками, помните, что они всего лишь приблизительное число, которое вы ищете. Вы можете использовать BigDecimal, если хотите точное число [или, по крайней мере, контролировать ошибку].
Более подробную информацию можно найти на в этой статье
Ответ 5
Используйте BigDecimal
для выполнения вычислений с плавающей запятой с точностью. Это необходимо, когда речь идет о деньгах.
Это известная проблема, которая связана с тем, что двоичные вычисления не позволяют выполнять операции с плавающей запятой. Посмотрите "арифметика с плавающей запятой" для более подробной информации.
Ответ 6
Это связано с неточностями, когда речь идет о представлении десятичных чисел с использованием двоичного значения с плавающей запятой. Другими словами, двойной литерал 0.99
фактически не представляет математическое значение 9.99.
Чтобы точно показать, какое число значение, например 9.99
, означает, что вы можете позволить BigDecimal
распечатать значение.
Код, чтобы показать точное значение:
System.out.println(new BigDecimal(9.99));
Вывод:
9.9900000000000002131628207280300557613372802734375
Btw, ваши рассуждения были бы полностью точными, если бы вы принимали бинарные места вместо десятичных знаков, так как число с двумя бинарными местами может быть точно, представленное двоичным значением с плавающей запятой.
Ответ 7
Я не знаю, как отмечать как дубликат, но короткий поиск приведет меня к этой странице:
Как решить проблему с двойным округлением Java
Это то, что вы ищете?