Различные результаты при добавлении одинаковых удвоений в разном порядке

Почему при добавлении одинаковых номеров выход отличается?

public class Test {

    public static void main(String a[]) {

        double[] x = new double[]{3.9, 4.3, 3.6, 1.3, 2.6};
        System.out.println(">>>>>>> " + sum(x));
    }

    public static double sum(double[] d) {

        double sum = 0;
        for (int i = 0; i < d.length; i++) {
            sum += d[i];
        }
        return sum;
    }
}

Выход: 15.7

и если я меняю значения

double[] x = new double[] {2.6, 3.9, 4.3, 3.6, 1.3};

Я получаю вывод как: 15.700000000000001

Как получить тот же результат?

Ответ 1

Числа с плавающей запятой теряют точность, поскольку вы выполняете больше операций. Как правило, вы получаете максимальную точность, сначала добавляя наименьшие числа. (Таким образом, результат зависит от порядка операций)

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

Или еще лучше, не используйте плавающие точки: вместо этого используйте BigDecimal.

Ответ 2

На каждом шаге в последовательности арифметических операций с плавающей запятой система должна производить результат, который представляется в формате с плавающей запятой. Это может привести к ошибке округления, потере некоторой информации.

При добавлении двух чисел разных величин, более крупный имеет тенденцию контролировать, какие биты нужно отбрасывать. Если вы добавите большое и небольшое число, многие биты небольшого числа будут потеряны до ошибки округления из-за большой величины результата. Этот эффект уменьшается при добавлении числа одинаковой величины. Сначала добавив несколько небольших чисел, оставив большие числа величин до конца, позволяя накапливать эффект малых чисел.

Например, рассмотрим { 1e17, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, -1e17 }. Точный ответ без какого-либо округления будет 147. Добавление в порядке, показанном выше, дает 112. Каждое добавление "21.0" должно быть округлено, чтобы соответствовать числу с величиной около 1e17. Добавление в порядке возрастания абсолютной величины дает 144, намного ближе к точному ответу. Частичный результат добавления 7 маленьких чисел - это точно 147, которые затем должны быть округлены, чтобы соответствовать числу около 1e17.

Ответ 3

потому что двойные и другие типы данных с плавающей запятой должны иметь дело с проблемами округления при выполнении операций. Точность не бесконечна. Если вы разделите 10/3, результат будет 3.33333333... но компьютер сохранит только часть этого числа.

проверить http://floating-point-gui.de/

Ответ 4

Простое сложение всех значений приведет к сравнительно большой ошибке для более длинных массивов во всяком случае (точнее: ошибка будет "большой", когда сумма уже "большая", и следует добавить дополнительные "маленькие" цифры).

Как одна из возможностей уменьшения числовой ошибки, вы можете рассмотреть http://en.wikipedia.org/wiki/Kahan_summation_algorithm:

public static double kahanSum(double d[])
{
    double sum = 0.0;
    double c = 0.0;
    for (int i=0; i<d.length; i++)
    {
        double y = d[i] - c;
        double t = sum + y;
        c = (t - sum) - y;
        sum = t;
    }
    return sum;        
}