When building an application, some lines of code are more important than others. Screw up your mutex logic and you may run into a deadlock or a race condition. Screw up a database query and you can wipe out an entire table. Screw up how you handle money, and someone will get short-changed (or even worse: you get fined by the SEC).
But how can you screw up how you handle money, you ask?
Hint: you may have snoozed during that boring lecture where the professor was covering floating point arithmetic and scientific notation. Let’s have a quick refresher from Tom in the video below:
I couldn’t have said it better than Bill Karwin:
Bill Karwin@billkarwinIf I had a dime for every time I've seen someone use FLOAT to store currency, I'd have $999.997634. #ieee754jokes03:49 AM - 20 Jun 2013
But let’s try this out! I’ll be using Python in the examples in this post, but this is not a Python-specific issue. This has to do with how computers represent numbers internally.
The reason issues like the one in the code above occur is due to rounding errors when doing floating point arithmetic. Let’s repeat the example from the video above. Here we’re adding just two floating point numbers:
If in either of these two examples you pull up your favorite calculator, you will get the “correct” answer, i.e.
0.1 + 0.2 = 0.3 and
165 * 19.4 = 3201.
Hopefully, by now I should have convinced you that using float to store money is a bad idea. If you’re still not convinced, keep reading!
While float is the troubled child in the family, integer is the poster child that always behaves as it’s expected of her. In our case: no rounding errors.
I think the primary reason we automatically use floats to represent money is that we more commonly think in a currency’s main unit, e.g. 19.4 dollars.
But forget what Diddy and Biggie told you. It’s all about the cents.
To avoid rounding errors, you should store money in a currency’s smallest unit using an integer.
For example, we’d represent 19.4 dollars as 1940 cents. Just make sure you use long integers, so you don’t run into brand new problems.
Storing money in a currency’s smallest unit is a common software design pattern made popular by Martin Fowler back in 2002. It’s even adopted by Stripe in their APIs. That’s a pretty solid endorsement in my book.
Let’s retry the code for the example above using this pattern:
Note that now that every monetary value is in cents, we need to be aware of that and properly format stored values into what the user expects to see.
At minimum, we need to divide the stored value by 100:
>>> 320100 / 100 3201.0
This is what we would expect as a result if we’d done the math by hand.
As we saw above, it may be easy to forget that monetary values are in cents and run into new problems in the presentation layer/UI. The best way to deal with this is to use a dedicated library (or class) for everything money-related.
The most common one for Python is good, but it uses
Decimal to store values. While this is generally fine, we really wanted to stick with the Fowler pattern across all systems and languages and use integers to represent money everywhere.
That’s why I decided to write my own and make it available on GitHub. You can check it out here:
Python class for working with money, following Martin Fowler's money pattern
It’s still in beta, but has decent test coverage. PRs welcome!
Hopefully this was useful to you, or at least a good refresher how floating point arithmetic works. If you want to nerd out further, I’d recommend going deep in this classic article from ACM. Mind you, there are other issues one can run into when handling money, like allocation and calculating percentages. Both these will be added to
agentrisk-money library very soon.
This article was originally posted on the AgentRisk blog.