Fixing Floating Point Math in JavaScript
Cover photo by Aaron Lefler on Unsplash
const sum = 0.1 + 0.2;console.log(sum === 0.3); // falseWhy does that output false? If you rely on JavaScript for any kind of financial calculation, you’re going to have a bad time if you don’t understand IEEE 754 floating point math. My stance is simple: never trust base-10 decimals in your code, and always handle them as integers whenever you can.
Why does this happen?
JavaScript uses 64-bit floats for all numbers. It stores them in binary. Just like some fractions don’t translate perfectly into finite decimals (think 1/3), many decimal numbers don’t translate perfectly into finite binary (like 0.1). When the CPU performs the math, it gets a tiny bit of rounding error that manifests as that annoying trailing four at the end of your total.
Use integers for money
I’ve seen developers try to round their way out of this with toFixed(2). Don’t do that. It turns your numbers into strings and is a total headache to manage later (especially if you need to do further math). The most robust fix is to convert your values to the smallest currency unit, like cents, before you do any math.
const price = 10.99;const cents = Math.round(price * 100); // 1099
// Now perform calculationsconst total = (cents + 50) / 100; // 11.49This keeps your numbers as integers, which computers are exceptionally good at handling without losing precision. It’s boring, but it works.
What about decimals that aren’t money?
If you’re dealing with scientific data or complex engineering calculations where cents don’t apply, you have a few options. I personally reach for a library like Big.js or Decimal.js when I need arbitrary precision. Sure, you’re adding a dependency to your package.json, but you get guaranteed accuracy.
“But why can’t I just use Number.EPSILON to compare them?”
You definitely can, and it’s a valid pattern for equality checks. If you need to check if two values are close enough to be considered equal, that’s the standard tool.
function areClose(a, b) { return Math.abs(a - b) < Number.EPSILON;}This is fine for basic logic checks, but it doesn’t solve the underlying data storage issue. If you use this approach to calculate prices for an invoice, you’re just hiding the error until it shows up in a report somewhere. (And nobody likes explaining math errors to a client at three in the morning.)
Is there a limit to this advice?
I admit this isn’t a silver bullet. If you’re building a game engine where performance is everything and you need to perform millions of calculations per second, Big.js might be too slow for your needs. In those cases, you often accept a level of drift because the visual representation won’t notice it anyway. You have to know your requirements.
Ultimately, just treat floating point precision as a potential bug source, not a quirk to ignore. Are you currently using a specific library for your math, or are you just rolling the dice with standard operators?