Avoid rounding errors and other floating point nonsense
The Problem
The standard that JavaScript employs to represent floating point numbers is limited in its precission. This means that some decimal numbers cannot be represented in the binary format that is outlined in the standard. When performing simple calculations such as addition or multiplication, it's quite common for the results to be innacurate when the variables are floating point numbers. What follows is the most referenced example of this.
0.1 + 0.2 = 0.30000000000000004
I'm not all that great at math, so I don't fully grasp why this happens, but that's not what we're here to discuss. What does matter is that this inaccuracy can lead to problems with value comparison and rounding, especially when dealing with chained equations. These small errors can compound with each computation. This is something you absolutely want to avoid when dealing with money — because cash is king.
Solution 1: Cents
One of the most straightforward solutions is to use integers instead of floating point numbers in your calculations. JavaScript can handle integers up to +/- 9,007,199,254,740,991. By storing currency values in cents (smallest real currency unit) and performing all calculations with whole numbers, we can then scale to dollars (cents / 100) to display the value. (JavaScript's built-in Intl.NumberFormat object returns a properly formatted string representation of the number value.)
Cents Limitations
This solution generally assumes that the smallest currency unit is 1/100 of a dollar (1 cent). In order to scale between cents and dollars, we use a fixed ratio of 100:1. While most transactions are facilitated in dollars and cents, it is occasionally necessary to track fractions of pennies; think about large volumes of cheap parts. While the end goal is to produce a value in dollars and cents, the intermediate calculations will need to take place in the smallest unit provided (i.e. $0.00001).
Because we're now operating at a ratio of 100000:1, we effectively reduce the dollar amount we're able to store. We'll also need to keep track of our ratio, which might depend on what the user types (i.e. $100.25 versus $100.2591).
Solution 2: Strings
In order to get around the number-type limitations of JavaScript and perform arbitrary-precision arithmetic (equations with big big numbers), we can instead use strings. Libraries such as big.js and decimal.js store numbers internally as strings of decimal digits. Because the integer and fractional pieces of the number are represented absolutely, the pitfalls of binary representation are avoided. In both of these libraries, once the string version of a number is instantiated as a Big or Decimal object, calculations and rounding can be performed with accuracy at the desired precision.
JavaScript's Intl.NumberFormat also accepts strings, so there's no extra complication in displaying the value.
decimal.js Example
import Decimal from 'decimal.js'
const x = Decimal(0.1)
const y = Decimal(0.2)
const z = x.add(y)
console.log(z.toString()) // 0.3
Downsides to String Libraries
The biggest downside of using a library is the additional weight it adds to your application. Luckily, the trimmed-down version of decimal.js, decimal.js-light is only 12KB, while big.js — despite its name — is 6KB.
As seen in the example above, there's a considerable amount of overhead that comes with managing and computing Decimal values. It's not exactly a plug-and-play solution; you will need to remember that x + y will no longer work, and that you'll need to use .add() instead. This adds a degree of technical debt to your application that must be weighed.
All of that said, if you need accuracy in decimal values, this is the widely accepted solution.
← Back to blog