1. Обзор
В этой быстрой статье мы рассмотрим, почему изменение порядка суммирования возвращает другой результат.
2. Проблема
Глядя на следующий код, мы можем легко предсказать правильный ответ (13,22 + 4,88 + 21,45 = 39,55). То, что легко для нас, может быть иначе интерпретировано компилятором Java:
double a = 13.22;
double b = 4.88;
double c = 21.45;
double abc = a + b + c;
System.out.println("a + b + c = " + abc); // Outputs: a + b + c = 39.55
double acb = a + c + b;
System.out.println("a + c + b = " + acb); // Outputs: a + c + b = 39.550000000000004
С математической точки зрения изменение порядка суммы всегда должно давать один и тот же результат:
(А + В) + С = (А + С) + В
Это верно и хорошо работает в Java (и других языках программирования) для целых чисел. Однако почти все процессоры используют для нецелых чисел стандарт IEEE 754 с плавающей запятой , который вносит неточность, когда десятичное число хранится как двоичное значение. Компьютеры не могут точно представить все действительные числа.
Когда мы меняем порядок, мы также меняем промежуточное значение, которое хранится в памяти, поэтому результат может отличаться. В следующем примере мы просто начинаем либо с суммы A+B, либо с суммы A+C:
double ab = 18.1; // = 13.22 + 4.88
double ac = 34.67; // = 13.22 + 21.45
double sum_ab_c = ab + c;
double sum_ac_b = ac + b;
System.out.println("ab + c = " + sum_ab_c); // Outputs: 39.55
System.out.println("ac + b = " + sum_ac_b); // Outputs: 39.550000000000004
3. Решение
Из-за печально известной неточности чисел с плавающей запятой никогда не следует использовать double для точных значений. Это включает в себя валюту. Для точных значений мы можем использовать класс BigDecimal :
BigDecimal d = new BigDecimal(String.valueOf(a));
BigDecimal e = new BigDecimal(String.valueOf(b));
BigDecimal f = new BigDecimal(String.valueOf(c));
BigDecimal def = d.add(e).add(f);
BigDecimal dfe = d.add(f).add(e);
System.out.println("d + e + f = " + def); // Outputs: 39.55
System.out.println("d + f + e = " + dfe); // Outputs: 39.55
Теперь мы видим, что в обоих случаях результаты были одинаковыми.
4. Вывод
При работе с десятичными значениями нам всегда нужно помнить, что числа с плавающей запятой представляются неправильно, и это может привести к неожиданным и нежелательным результатам. Когда требуется точность, мы должны использовать класс BigDecimal .
Как всегда, код, использованный в статье, можно найти на GitHub .