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

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

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

С математической точки зрения изменение порядка сумм должно всегда давать один и тот же результат:

(A + B) + C = (A + C) + B

Это верно и хорошо работает в Java (и других языках программирования) для целых чисел. Однако почти все процессоры используют для нецелых чиселIEEE 754 binary floating point standard, что приводит к неточности при сохранении десятичного числа как двоичного значения. Компьютеры не могут точно представить все действительные числа.

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

Как всегда, код, использованный в статье, находится вover on GitHub.