How to handle money in javascript

Magnus Tovslid
4 min readApr 22, 2018

--

In this article we’ll look at all the pitfalls and solutions to handling money in javascript.

Money as a data type

First of all, we need to acknowledge that money is not just a plain old number. It’s better to think of money as a separate data type. This data type has a number with a specific number of decimals, and a specific currency. Any operation on money should take this into account. Some examples:

  • It is not correct to add together money of two different currencies.
  • It is not correct to multiply two money objects (it is maybe obvious to you now, but if we think about money as just a number, it is very easy to make this mistake). Money can only be multiplied with a unit-less factor.
  • The precision of money should be explicit. The number of decimals in money is a standardized thing, and varies between different currencies. Each step of a calculation needs to handle this in a thoughtful way. More on this later.
  • How money is rounded after operations such as multiplication is not always up to you! There may be different regulations in different domains.

Let’s define our money datatype like this:

number
number of decimals
currency

And some operations we can perform on money:

Money.add(Money)
Money.subtract(Money)
Money.multiply(Factor)
Money.divide(Divisor)
Money.toCurrency(Currency, Factor, Unit)
Money.abs()
Money.equals(Money)
Money.compare(Money) // compare can be lte, lt, gte, gt, eq, etc.
Money.compare(0) // 0 is a special case
...and more

Money is not Price

As described already, money has a fixed number of decimals. There is no such thing as half a cent in the money data type. A price, however, may be given with a higher precision. Think of the price of gasoline for example. It is given with a higher precision than a cent, even though less than a cent is not a valid amount of money in and of itself. It is only when we sum all the partial amounts and round the result that we arrive at the money data type. It’s useful then, to handle price and money as two separate things, or at least be explicit about when an amount is money and when it is a price.

Pitfalls of floating point numbers in javascript

If you didn’t already know, floating point numbers are not to be trusted with money. There’s an inherrent lack of precision in this number type that just isn’t meant for precision calculations.

Here’s an easy-to-remember example of why floats are bad at handling money:

0.1 + 0.2 = 0.30000000000000004

But there are more insidious examples…

2090.5 * 8.61 => 17999.20499999999... // Should be 17999.205
(2090.5 * 8.61).toFixed(2) => 17999.20 // Should be 17999.21

Rounding this number gives the wrong result!

Why integers are not good enough

It is often said that money should be handled by using integer amounts of cents. This sounds pretty reasonable, but comes with some major pitfalls.

The first issue is that multiplication is still a problem even with integers. Take the example we saw earlier, but replace the money amount with an integer number of cents:

209050 * 8.61 => 1799920.499999999...

Damnit. Still the same problems with precision. The reason is that our multiplier is still a float. We can somewhat get around this, but we soon get into the next issue…

The second issue is that integers in javascript are not infinitely big. In fact, the number type has 53 bits set aside for the integer part of the number, which is roughly 10¹⁵. This should be more than enough for most money amounts, but when we start using cents we immediately need to remove 2 orders of magnitude of precision. If also multiply our multiplication factors, we need to remove even more precision. And by the way, a bunch of currencies have their unit specified in 1/1000, not 1/100, so it’s even worse. And of course we have the various cryptocurrencies with 6 or more decimals of required precision. In other words, the javascript integer is not nearly big enough!

Pitfalls with rounding

Quickly, what’s the result of the calculation below?

Math.round(-1.5)

You may be surprised to learn that the answer is -1, not -2. This is because Math.round rounds numbers in the positive direction, not away from zero. You can of course easily sovle this, but then you might look a bit further into rounding and discover that there is another type of rounding called bankers rounding…

Bankers rounding or normal rounding?

There are many types of rounding, but there are two types we’re interested in. There is the “normal” rounding where the halfway point between two numbers is rounded up, and then there is bankers rounding where the halfway point is rounded down if the number is even, and up if the number is odd. The reason for having bankers rounding is that it is numerically stable, i.e. it will not keep accumulating errors in one direction if rounding many numbers. It is useful when rounding the intermediate results of a calculation, and you want the result to be as close to the actual result as possible.

So what rounding should you use? The answer is that it depends. Sometimes the rounding type is specified by regulation. In any case, you need to be able to do both types.

How to handle money in javascript

So, finally, let’s answer the question in the title. I would like to tell you to get some package of off npm, but I’ve looked into at least 4 such packages, and they are all riddled with issues. So for now, I recommend using big.js, a small library for handling arbitrary decimal precision arithmetic. It supports all rounding types as well. I will probably try to open source my own money package pretty soon as well, if you can wait that long.

Edit: I finally got around to open sourcing a money library that solves the problems mentioned in this article (and a few more, like distributions). Hope it helps someone :)

--

--