Double Trouble (Don’t use doubles for currency or accurate decimals)
This is an article that I wrote a few years ago that still applies today. It sounds very specific to Java, but it’s something you need to watch out for in any language.
Working with decimal numbers can be more complicated than it might seem. Many a Java programmer has been bitten by using doubles when calculating currency or another decimal value that relies on precision.
The problem
Let’s say that we need to calculate a customer’s bill. They used 80000.01 kWHs (big bill!) and we charge a dollar per kWH for this customer. To calculate that in cents, we just do:
double amount = 80000.01 * 100.0;
The answer we expect from this is 8000001. What we actually get, however, is 8000000.999999999! Why is this? Because doubles are inaccurate with large numbers. They just can’t handle the calculation. They are actually made to be fast, not accurate.
You see, the decimal number must first be converted into a binary number. Due to the way that works, it can be impossible to store that decimal number into a binary representation, Just like you can’t make ⅓ into a decimal number.
⅓ = 0.33333333333333333333333333333333...
The threes just never end! If you want to store that number somewhere, you’ll have to eventually cut it off somewhere, and that means you’ve lost your precision. It’ll be almost correct, but not quite.
If you’re not careful with this, you can start to drop cents and, with enough accounts being calculated this way, dropped cents will soon mean a dropped job. When doing large financial, with-real-money calculations, almost correct is not correct.
So what should we do?
The Java language has anticipated this and created an object that can handle decimal numbers accurately. That class is BigDecimal
. Using BigDecimals, our previous example would look like this:
BigDecimal kWH = new BigDecimal("80000.01");
BigDecimal price = new BigDecimal("100.0");
BigDecimal amount = kWH.multiply(price);
More complicated but we get the right answer of 8000001.00. It’s important to create the BigDecimal with Strings so that we keep the numbers safe from being accidentally converted into doubles at any time. If we called this as new BigDecimal(80000.01);
, the number would be translated as a double first and then a BigDecimal, and we’ll have the same problem we had before. We should avoid using double
s at all when dealing with important decimal numbers.
Remember: BigDecimal, not double or Double!