Craig, my co-worker and founder of Jobvent, was working on a .net application that had a simple function. We had a floating point number that was adding up prices. We had about 200 items to add up and each item cost exactly 0.15 cents. We expected a total of exactly 30 bucks but I noticed the price didn't add up to 30. Somehow we where off by a penny.
This is where things got really strange. We wrote a simple program that just performed this exact calculation and ran the program and it produced the same mistake. One lost penny. In fact we noticed that the calculation was off way before we hit 200.
We took the same exact code, converted it to C++ and ran it again. The penny was back. Could it be that C# had an error in it's calculation engine? Well after a while scratching our heads and searching Google we eventually found an explanation. As it turns out when you tell C# that you want a floating point number with a value of 0.15 it doesn't store 0.15 it stores the closest floating point number using 32 bits. These bits break down to a sign bit (positive or negative) an eight bit exponent and a 23 bit fraction.
If you do a simple calculation in decimal such as dividing 1 by 3 you get a repeating fraction. 0.33333 repeating. Well the same problem exists in binary. If you try to convert .15 decimal into binary you get: .00100110011001100110011001100110 repeating. Now since this fraction has to be stored in 23 bits you can't properly store the value without rounding. So a portion of those 15 cents is lost due to rounding error.
The moral of the story?
- If you're dealing with money use a decimal format instead of floating points.
- If your bank is off by a penny make sure you understand why.
- Small errors do add up to big bucks.
- Never assume the computer is doing what you think it should be doing.