Добавление и вычитание удвоений дает странные результаты

Итак, когда я добавляю или вычитаю в Java с парными, это дает мне странные результаты. Вот некоторые из них:

Если я добавлю 0.0 + 5.1, он даст мне 5.1. Это правильно.

Если я добавлю 5.1 + 0.1, он даст мне 5.199999999999 (число повторений 9 может быть выключено). Это неправильно.

Если я вычитаю 4.8 - 0.4, он дает мне 4.39999999999995 (опять же, повторяющийся 9 может быть выключен). Это неправильно.

Сначала я думал, что это только проблема с добавлением удвоений с десятичными значениями, но я был неправ. Следующие работали нормально:

5.1 + 0.2 = 5.3
5.1 - 0.3 = 4.8

Теперь первое добавленное число - это double, сохраненный как переменная, хотя вторая переменная захватывает текст из JTextField. Например:

//doubleNum = 5.1 RIGHT HERE
//The textfield has only a "0.1" in it.
doubleNum += Double.parseDouble(textField.getText());
//doubleNum = 5.199999999999999

Ответ 1

В Java значения double числа с плавающей точкой IEEE. Если они не являются степенью 2 (или суммой степеней 2, например 1/8 + 1/4 = 3/8), они не могут быть представлены точно, даже если они имеют высокую точность. Некоторые операции с плавающей запятой объединяют ошибку округления, присутствующую в этих числах с плавающей запятой. В описанных выше случаях ошибки с плавающей запятой стали достаточно значительными, чтобы отображаться на выходе.

Не имеет значения, является ли источник номера, анализирует ли строка из JTextField или задает литерал double - проблема наследуется в представлении с плавающей запятой.

Обходные:

  • Если вы знаете, что у вас будет только столько десятичных точек, тогда используйте целое число арифметику, затем преобразовать в десятичную цифру:

    (double) (51 + 1) / 10
    (double) (48 - 4) / 10
    
  • Используйте BigDecimal

  • Если вы должны использовать double, вы можете сократить число ошибок с плавающей запятой с алгоритмом суммирования Кахана.

Ответ 2

В Java удваивает использование арифметики с плавающей запятой IEEE 754 (см. this Статья в Википедии), которая по своей сути неточна. Используйте BigDecimal для идеальной десятичной точности. Чтобы округлить печать, принимая только "довольно хорошую" точность, используйте printf("%.3f", x).