An Hour of Code… A Lifelong Lesson in Floating-Point
Copyright © 2008-2016 Exploring Binary
The 2015 edition of Hour of Code includes a new blocks-based, Star Wars themed coding lesson. In one of the exercises — a simple sprite-based game — you are asked to code a loop that adds 100 to your score every time R2-D2 encounters a Rebel Pilot. But instead of 100, I plugged in a floating-point number; I got the expected “unexpected” results.
Here is a screenshot of puzzle 9, where I’ve coded the value 0.1 for the score increment:
Here specifically is the code block:
Here’s the score after the first encounter (looks OK):
Here’s the score after the second encounter (looks OK):
Here’s the score after the third encounter (what on earth?):
What’s Going On?
The root of the problem is that 0.1 is not 0.1 in binary floating-point. Specifically, in double-precision binary floating-point, it is
Decimal numbers such as 0.1 do not have exact binary equivalents.
Furthermore, every time you add this to the score, rounding may occur (double-precision floating-point has a fixed precision of 53 bits). The internal double-precision score after each encounter is as follows (you can do the binary additions and rounding yourself, using my decimal/binary converter and binary calculator):
One strategy for printing double-precision floating-point numbers is to round them to 17 significant digits; that way, they are guaranteed to round-trip convert back to the same double-precision number. But that is not what is done here; that would make the scores 0.10000000000000001, 0.20000000000000001, and 0.30000000000000004, respectively.
Another strategy for printing floating-point numbers is to round them to the shortest number that will round-trip. In this case, that would be 0.1, 0.2, and 0.30000000000000004. That’s what’s happening here.
You can show this by using my decimal to floating-point converter. 0.10000000000000001 and 0.1 both convert to the original floating point number, as do 0.2 and 0.20000000000000001. However, 0.30000000000000004 converts to the original floating point number, but 0.3 does not; it converts to 0.299999999999999988897769753748434595763683319091796875.
Other “Anomalies” You Can Force
I entered a value of 1e308 as the score increment and the game displayed 1e+308 after the first encounter, as expected. After the second and third encounters, it displayed Infinity — also as expected. (The maximum value of a double-precision number is approximately 1.8e308.) This is not so much an anomaly as a demonstration of the limits and properties of floating-point.
In other exercises, where you can run up a higher score, you can see similar printing anomalies using the 0.1 score increment. The next one for which this happens is after the eighth score; it prints the 16-digit value 0.7999999999999999, the shortest number that round-trips.
How Can You Fix This?