I'm studying IEE754 and I'm kinda baffled by these code lines
double a = 0.2;
double b = 100.0;
double c = a * b;
I know that 0.2 cannot be perfectly represented via powers of 2 while 100 can, but I'm getting a perfect result of 20 as result in c.
Visualizing the powers of 2 that make up these values (I'm using a simple js visualizer: http://bartaz.github.io/ieee754-visualization/) I see that 0.2 starts with
2^-3 + 2^-4 + 2^-7...
and 100 with
2^6 + 2^5 + 2^2
and now to my question: here's what 20, aka c
looks like
2^4 + 2^2
^^^
what? Where did 2^4 come from? If I were to mathematically multiply all terms of 0.2 by all terms of 100 I'd get 2^3 as greatest power.
So, assuming the visualizer is correct:
c
the exact result?There is nothing in the IEEE arithmetic rules that prevents round-to-nearest from hitting what would be the exact result of the decimal calculation you wanted to do.
The exact value of the double literal 100.0 is, of course, 100.
The exact value of the double literal 0.2 is 0.200000000000000011102230246251565404236316680908203125
Their product is 20.000000000000001110223024625156540423631668090820312500
The rounding error on rounding this down to 20 is 1.110223024625156540423631668090820312500E-15
The rounding error on rounding it up to 20.000000000000003552713678800500929355621337890625, the smallest double greater than 20, would be 2.442490654175344388931989669799804687500E-15
Since the error on rounding up is greater than the error on rounding down to 20, the correct round-to-nearest result of the double multiplication is 20.0. The rounding error is 2^-50 + 2^-52
, your "lost" powers of 2.
I used a Java program to do the calculations, because of the convenient BigDecimal class which can exactly represent all finite double numbers, and the results of some arithmetic on them, including multiplication. Java double arithmetic follows IEEE 754 64-bit binary floating point in round-to-nearest mode, which is also the usual system for C doubles.
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));
}
}
Output:
0.200000000000000011102230246251565404236316680908203125
100
20
20.000000000000001110223024625156540423631668090820312500
20
20.000000000000003552713678800500929355621337890625
Round down error 1.110223024625156540423631668090820312500E-15
Round up error 2.442490654175344388931989669799804687500E-15