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
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)
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"
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
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"
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)