So here I am implementing happily along in Gnucash for Android. For the most part, everything works out fine and all the automated tests implemented so far pass on the target devices. Great! But sometimes, and only sometimes, I notice that when I open a transaction to edit, it is not exactly the amount which I put in there which is displayed. Sometimes, the last digit has been rounded off, or dropped completely. This is strange, and annoying because it is not reliably reproducible. So I keep trying to reproduce it and finally notice that it mostly occurs especially when I enter big amount, like with 7 digits and up. This is good, because now I can somehow reproduce it. The question is, what is happening?
Now, before I continue, I must say that after the alpha release, similar reports of crashes came in all related to recording transactions. Not all of these were due to what I am about to describe. Mostly, the crashes were due to bugs with the parsing of the money and currency.
First of all, it is money we are dealing with. You do not want to mess with your users’ accounting figures and of course people will run from any software which rounds their figures. So I set about investigating what was happening and how I could remedy it. When I enabled the debugger, I immediately noticed that the internal representation of the figures was not always straight forward. Once the number becomes big enough, it is stored (or at least shown in the debugger) in the scientific format or rounded off in some way which loses precision. So if someone entered a figure as 18975699, it was held in the double as1.8976×107 . Who wants to see their money in that format? Not me!
The problem is when coding money, you always have the internal representation of the value, and that which the user sees. So you have to convert your amount into a string to display it, while saving it in a way which keeps its precision. So I started searching about how to best represent money in Java. It turns out that floats or doubles are a big no-no for money amounts. They cannot accurately represent base 10 numbers. I thought I was fine since I was using doubles instead of floats. But they are not any safer. BigDecimals were mostly recommended for anything which needs laser precision (like money).
Most of the users of the alpha version of GnucashMobile for Android have not noticed this particular bug yet because in a mobile expense tracker application, mostly smaller amounts are entered and doubles are usually large enough to hold the values and display them. But you can never predict what you users will input and it is better to anticipate anything.
So after refactoring the code to use BigDecimal, I reran my automated tests without updating them and hoped they would fail (sweet irony). Sure enough, they did. When validating the amount fields, I got the log message “AssertionFailedError: expected <9.9900000000000000213162820…..> but was: <9.99>.” $9.99 was the actual value entered but the tests still run with double values, which tags along some extra digits for the ride. I will be updating the tests shortly.
You might argue that they are in effect the same after rounding, but when it comes to money, I like the thought that when my users enter $9.99, it is actually $9.99 that gets saved and nothing else. And I think my users would like that too. It’s money we’re talking about after all .