Вот что происходит в двоичном формате. Как мы знаем, некоторые значения с плавающей точкой не могут быть представлены точно в двоичном виде, даже если они могут быть представлены точно в десятичном виде. Эти 3 числа являются лишь примерами этого факта.
С помощью этой программы я вывожу шестнадцатеричные представления каждого числа и результаты каждого сложения.
public class Main{
public static void main(String args[]) {
double x = 23.53; // Inexact representation
double y = 5.88; // Inexact representation
double z = 17.64; // Inexact representation
double s = 47.05; // What math tells us the sum should be; still inexact
printValueAndInHex(x);
printValueAndInHex(y);
printValueAndInHex(z);
printValueAndInHex(s);
System.out.println("--------");
double t1 = x + y;
printValueAndInHex(t1);
t1 = t1 + z;
printValueAndInHex(t1);
System.out.println("--------");
double t2 = x + z;
printValueAndInHex(t2);
t2 = t2 + y;
printValueAndInHex(t2);
}
private static void printValueAndInHex(double d)
{
System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
}
}
printValueAndInHex
Метод просто помощник шестигранного принтера.
Вывод следующий:
403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004
Первые 4 цифры x
, y
, z
, и s
«ы шестнадцатеричные представления. В представлении IEEE с плавающей запятой биты 2-12 представляют двоичный показатель , то есть масштаб числа. (Первый бит является знаковым битом, а остальные биты для мантиссы .) Представленная экспонента фактически является двоичным числом минус 1023.
Экспоненты для первых 4 чисел извлекаются:
sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5
Первый набор дополнений
Второе число ( y
) имеет меньшую величину. При сложении этих двух чисел x + y
последние 2 бита второго числа ( 01
) сдвигаются за пределы диапазона и не учитываются при расчете.
Второе дополнение добавляет x + y
и z
добавляет два числа одного масштаба.
Второй набор дополнений
Здесь x + z
происходит первое. Они имеют одинаковый масштаб, но они дают число, которое выше в масштабе:
404 => 0|100 0000 0100| => 1028 - 1023 = 5
Второе дополнение добавляет x + z
и y
, и теперь 3 бита отбрасываются y
для добавления чисел ( 101
). Здесь должен быть округлен вверх, потому что результатом является следующее число с плавающей запятой вверх: 4047866666666666
для первого набора дополнений по сравнению 4047866666666667
со вторым набором дополнений. Эта ошибка достаточно значительна, чтобы показать ее в распечатке.
В заключение, будьте осторожны при выполнении математических операций над числами IEEE. Некоторые представления неточны, и они становятся еще более неточными, когда масштабы различны. Сложите и вычтите числа аналогичного масштаба, если можете.
(2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1)
). Следовательно, да: будьте осторожны при выборе порядка сумм и других операций. Некоторые языки предоставляют встроенные средства для выполнения «высокоточных» сумм (например, pythonmath.fsum
), поэтому вы можете рассмотреть возможность использования этих функций вместо алгоритма наивной суммы.