Floats and Doubles can lose you money…use Decimals or a custom Money class instead
Ariane 5 Rocket Launcher Crash
In 1996, the European Space Agency lost a valuable shuttle just 37 seconds into the launch sequence. After a deep dive into the root cause analysis, here is a direct excerpt from their report:
The internal SRI* software exception was caused during execution of a data conversion from 64-bit floating point to 16-bit signed integer value. The floating point number which was converted had a value greater than what could be represented by a 16-bit signed integer.
Basically, the navigation software of the rocket malfunctioned due to a simple conversion error – a concept that is taught in undergrad programming classes.
While not of the same caliber, currency conversion within your software can cause the same TYPE of problems – related in effect, to the ‘type’ of the storage variable used. We all know that trying to store Decimals as integers is a bad idea – for e.g. storing $10.3 as $10 – would lose 30 cents on every single transaction. In the same manner, storing any currency in the FLOAT type (which is what ALL programmers tend to start with), is a mistake. The DECIMAL class was invented for just this purpose. As this article will demonstrate, even that though, is not always sufficient.
As a first step, when dealing with Currencies, use the Decimal type
Floats are imprecise. Doubles are more precise than floats – but still not completely precise. Which means that if you start using floats (or doubles) to handle currency types, chances are you are losing precision – and therefore losing money. Here is a simple example:
Say you have a $100 which you have to divide 3 ways. You end up with $33.33 a piece. If you multiply this again by 3 – you do not end up with your $100. You just lost 0.01 cents because of your conversion. You could argue that – Let us keep more decimal places. So, instead of 33.33, we have 33.33333333.
There are two problems here:
- The number of decimal places you specify cannot be unlimited.
- Even if you specify a LOT of decimal places, you will still lose money when dealing with larger starting amounts (instead of $100, if we started with $100,000 for example).
The problem above is exactly what computers face when trying to store any number. A float is only an approximate representation of a number. A double, though better than a float, is still approximate. To get around this problem, C# (.NET) defines a Decimal type. The
Decimal d = 100.00M; // The M is what tells us that this is a ‘literal’ type – which is different from double, float
// divide by 3
Decimal div = d / 3;
// multiply by 3 (should get back 100.00 or as close as possible)
Decimal product = div * d;
As a second step, use a special Money type (Full Source Code available at end of article)
The Decimal solution is better, but not perfect. There is still some loss of precision. Ideally, one should implement the Money pattern (as described in this book). Just such an implementation can be found here. Using this Money class, all a client needs to do is:
Money usDollars = new Money(100m, “en-us”);
Money convertedCanadianDollars = new Money(usDollars.Amount * 1.12565m, “en-CA”);
Summary
We should end up with a number as close to our starting number as possible with the use Decimal. However, even a Decimal isn’t perfect. The best solution is to use the Money class ( a special class designed to handle currency representation and conversion). The Money class will ensure as close to 100% precision as possible when dealing with currency applications.
If only the Ariane 5 could have it’s navigation module code rewritten to include a ‘Latitude’ and a ‘Longitude’ class….
Leave a Reply