DEV Community

ZovoCalc
ZovoCalc

Posted on

Why 0.1 + 0.2 Breaks Your JavaScript Calculator (and 4 Ways to Fix It)

If you've ever built a calculator in JavaScript, you've probably typed 0.1 + 0.2 and watched it print:

0.1 + 0.2
// 0.30000000000000004
Enter fullscreen mode Exit fullscreen mode

Your users will notice, and they'll assume your calculator is broken. Here's why it happens and four practical ways to handle it.

Why it happens

JavaScript numbers are IEEE 754 double-precision floats. They store values in binary, and many "simple" decimals, like 0.1, have no exact binary representation. It's the same reason 1/3 can't be written exactly in decimal: 0.3333… goes on forever.

So 0.1 is really stored as something like 0.1000000000000000055511151231257827, and the tiny errors add up when you do arithmetic.

A few more examples that surprise people:

0.1 * 3          // 0.30000000000000004
1.1 * 1.1        // 1.2100000000000002
0.3 - 0.1        // 0.19999999999999998
9007199254740993 // 9007199254740992 (past Number.MAX_SAFE_INTEGER)
Enter fullscreen mode Exit fullscreen mode

Fix 1: Round for display, not for calculation

The simplest fix for most calculators: keep full precision internally, and round only when you show the result.

function formatResult(x, sig = 12) {
  if (!Number.isFinite(x)) return String(x);
  // toPrecision trims floating-point noise; Number() drops trailing zeros
  return String(Number(x.toPrecision(sig)));
}

formatResult(0.1 + 0.2); // "0.3"
formatResult(1.1 * 1.1); // "1.21"
Enter fullscreen mode Exit fullscreen mode

Twelve significant digits hides the noise while keeping more precision than most handheld calculators display.

Fix 2: Compare with a tolerance

Never compare floats with ===. Use a small tolerance instead:

const nearlyEqual = (a, b, eps = 1e-12) =>
  Math.abs(a - b) <= eps * Math.max(1, Math.abs(a), Math.abs(b));

nearlyEqual(0.1 + 0.2, 0.3); // true
Enter fullscreen mode Exit fullscreen mode

This matters for things like checking whether a result is an integer before showing it without decimals.

Fix 3: Work in integers for money

For currency, convert to the smallest unit (cents or paise) and do integer math:

const toCents = (x) => Math.round(x * 100);
const total = toCents(19.99) + toCents(5.01); // 2500
(total / 100).toFixed(2);                     // "25.00"
Enter fullscreen mode Exit fullscreen mode

For anything serious, like tax or interest over many periods, use a decimal library.

Fix 4: Use a decimal or BigInt library when exactness matters

If users need exact decimal arithmetic or very large integers:

  • BigInt (built in) handles arbitrarily large integers: 2n ** 100n
  • decimal.js or big.js handle arbitrary-precision decimals
  • fraction.js keeps exact fractions like 1/3 without converting to decimals

These are slower than plain numbers, so use them only where exactness matters.

Bonus: trig functions and "almost zero"

Math.sin(Math.PI) returns 1.2246467991473532e-16, not 0, because π itself can't be stored exactly. Significant-digit rounding alone won't fix this one, because 1.22e-16 is a perfectly valid 12-digit number. Snap tiny results to zero before formatting: if (Math.abs(x) < 1e-12) x = 0; It's also why a degree-mode calculator should convert degrees to radians with deg * Math.PI / 180 and then clean up the result for display.

Wrapping up

  • Floating-point noise is expected. It isn't a bug in your code.
  • Round for display, compare with a tolerance, and use integers or decimal libraries where exactness matters.

These are the kinds of details we deal with building ZovoCalc, a free browser-based scientific calculator. If you're building your own calculator or math tool, I hope this saves you a debugging session.

Top comments (0)