Floating Point Rounding Errors
In Chapter 3 of my book, I mentioned offhand that sometimes you expect the number 5, but you get 4.99999999998 instead. I sort of punted on the explanation because it seemed to be a touch of a distraction. Naturally, chromatic called me on that and suggested I explain a bit more. As part of my explanation, I wrote a sample program that would print out the fractions used to build the mantissa of a number. For example, .75 is 1/2 + 1/4.
Here's the program:
use strict;
use warnings;
my $num = .75;
my $bits = 32;
my $accumulator = 0;
my $bitstring = '';
my @fractions;
for ( 1 .. $bits ) {
my $denominator = 2**$_;
my $fraction = 1 / $denominator;
if ( $accumulator + $fraction <= $num ) {
push @fractions, "1/$denominator";
$bitstring .= "1";
$accumulator += $fraction;
}
else {
$bitstring .= "0";
}
}
my $fractions = join " + ", @fractions;
print <<"END";
Fractions: $fractions
Bits: $bitstring
Result: $accumulator
END
So running that will print:
Fractions: 1/2 + 1/4
Bits: 11000000000000000000000000000000
Result: 0.75
But using .3 for the number will print (formatted to fit the blog):
Fractions: 1/4 + 1/32 + 1/64 + 1/512 + 1/1024 + 1/8192
+ 1/16384 + 1/131072 + 1/262144 + 1/2097152
+ 1/4194304 + 1/33554432 + 1/67108864
+ 1/536870912 + 1/1073741824
Bits: 01001100110011001100110011001100
Result: 0.299999999813735
I think this plus my accompanying text (not reproduced here) does a reasonable job of showing the rounding errors. Can I do better?
I think this is a great way to visualize how a limited number of bits are mangled into representing something that can't be accurately enumerated that way.
That's a great idea and a great program to do it.
The only suggestion I have is that you accept the number from the command-line: to make it easier for people to run it with different values and see how the results change.