Я теряю 2 силы в умножении IEEE754
Я изучаю IEE754, и я немного сбит с толку этими строками кода
double a = 0.2;
double b = 100.0;
double c = a * b;
Я знаю, что 0.2 не может быть идеально представлено степенями 2, а 100 может, но я получаю идеальный результат 20 как результат в c.
Визуализация степеней 2, составляющих эти значения (я использую простой визуализатор js: http://bartaz.github.io/ieee754-visualization/). Я вижу, что 0.2 начинается с
2^-3 + 2^-4 + 2^-7...
и 100 с
2^6 + 2^5 + 2^2
а теперь на мой вопрос: вот что 20, ака c
похоже
2^4 + 2^2
^^^
что? Откуда взялось 2^4? Если бы я математически умножил все слагаемые 0,2 на все слагаемые 100, я получил бы 2^3 как наибольшую силу.
Итак, предполагая, что визуализатор правильный:
- Откуда взялось 2^4?
- Почему нет потери точности при умножении 0,2 на 100, если 0,2 было неточным с самого начала? Почему
c
точный результат?
1 ответ
В арифметических правилах IEEE нет ничего, что препятствовало бы тому, чтобы округление до ближайшего совпадало с точным результатом десятичного вычисления, которое вы хотели сделать.
Точное значение двойного литерала 100.0, конечно, 100.
Точное значение двойного литерала 0.2 равно 0.200000000000000011102230246251565404236316680908203125
Их продукт 20.000000000000001110223024625156540423631668090820312500
Ошибка округления при округлении до 20: 1.110223024625156540423631668090820312500E-15
Ошибка округления при округлении до 20,000000000000003552713678800500929355621337890625, наименьшее двойное значение больше 20, будет 2,442490654175344388931989669799804687500E-15
Поскольку ошибка округления больше, чем ошибка округления до 20, правильный результат округления до ближайшего двойного умножения равен 20,0. Ошибка округления 2^-50 + 2^-52
Ваши "потерянные" полномочия 2.
Я использовал программу Java для выполнения вычислений из-за удобного класса BigDecimal, который может точно представлять все конечные двойные числа, и результатов некоторой арифметики на них, включая умножение. Двойная арифметика Java следует 64-битной двоичной переменной с плавающей запятой IEEE 754 в режиме округления до ближайшего, что также является обычной системой для двойных Си.
import java.math.BigDecimal;
public class Test {
public static void main(String[] args) {
double a = 0.2;
double b = 100.0;
double c = a * b;
display(a);
display(b);
display(c);
BigDecimal exactProduct = new BigDecimal(a).multiply(new BigDecimal(b));
System.out.println(exactProduct);
BigDecimal down = new BigDecimal(20.0);
System.out.println(down);
BigDecimal up = new BigDecimal(Math.nextUp(20.0));
System.out.println(up);
System.out.println("Round down error "+exactProduct.subtract(down));
System.out.println("Round up error "+up.subtract(exactProduct));
}
private static void display(double in){
System.out.println(new BigDecimal(in));
}
}
Выход:
0.200000000000000011102230246251565404236316680908203125
100
20
20.000000000000001110223024625156540423631668090820312500
20
20.000000000000003552713678800500929355621337890625
Round down error 1.110223024625156540423631668090820312500E-15
Round up error 2.442490654175344388931989669799804687500E-15