Различные результаты при добавлении одинаковых удвоений в разном порядке
Почему при добавлении одинаковых номеров выход отличается?
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;
}