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

Странное поведение 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?

4b9b3361

Ответ 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, ваши рассуждения были бы полностью точными, если бы вы принимали бинарные места вместо десятичных знаков, так как число с двумя бинарными местами может быть точно, представленное двоичным значением с плавающей запятой.