JavaScript and money
Accuracy problem of floating point numbers
Floating point numbers can’t represent all decimal fractions precisely. The accuracy problem stems from the fact that underneath the value is stored in a binary which doesn’t match perfectly the decimal.
To show the problem consider the following:
0.1 + 0.2 === 0.3 // => false
This looks strange. It seems so obvious to us, but we are used to calculating things in decimal. With floating point numbers, the number we see is an only approximation. With that knowledge we can recognize why the previous test was failing:
0.1 + 0.2 // => 0.30000000000000004
It’s one of those things which one needs to be aware of when programming, especially in JavaScript. It’s because JavaScript only has floating point numbers. It simplifies a lot of things, but doing precise math is not one of them.
The accuracy problem becomes especially pronounced when money is involved. Fortunately, the solution is quite simple. Scale all the values to whole cents (euro cents, centavos, kuruş, etc). Then do all the calculations on scaled values. Once calculations are done and ready to be displayed (or to be stored) convert it back to a fractional representation.
What it means is that the floating point number behaves like a regular integer and there’s no accuracy problem in doing calculations on those.
To help myself when working on those calculations I like to create two simple functions:
const cents = n => n * 100;
const dollars = n => n / 100;
With those functions, the problematic case from the beginning is fixed:
cents(0.1) + cents(0.2) === cents(0.3) // => true
Using descriptive names makes it easy to see when things are being converted and in which direction.
Another helpful function is display
which converts the number directly to a string:
const display = n => (n / 100).toFixed(2);
That way monetary value can be stored in cents directly.