Перейти к основному содержимому

Изменение порядка в суммирующей операции может привести к разным результатам?

· 3 мин. чтения

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 .