Diego Betto
Photo by Aaron Lefter on Unsplash

Diego Betto · September 19, 2026 · 4 min di lettura

Why 0.1 + 0.2 !== 0.3 in JavaScript (and in almost every language)

How IEEE 754 floating-point numbers actually work, why this isn't a JavaScript-specific bug, and the safe ways to compare and store decimal numbers.

Condividi:XLinkedInFacebookWhatsApp

Open any JavaScript console and type this:

0.1 + 0.2 === 0.3; // false
0.1 + 0.2; // 0.30000000000000004

It looks like a bug. It isn’t — and it’s not even specific to JavaScript. Python, Java, C, Go, Rust: every language using standard IEEE 754 double-precision floating-point numbers produces the exact same result. Understanding why turns this from a weird gotcha into a predictable, manageable fact about how computers represent numbers.

How IEEE 754 doubles actually work

JavaScript’s Number type stores every number — integer or decimal — as a 64-bit IEEE 754 double-precision float. That format represents numbers in binary, using a fixed number of bits for the fractional part. This works perfectly for numbers with a clean binary representation (0.5, 0.25, 2), but many decimal fractions that look “clean” in base 10 have no exact binary equivalent — the same way 1/3 has no exact finite representation in base 10 (0.3333...).

0.1 in binary is an infinitely repeating fraction, just like 1/3 is in decimal. Since the format only has 64 bits, it gets rounded to the closest representable value — which is extremely close to 0.1, but not exactly 0.1. Add two of these tiny rounding errors together (0.1’s and 0.2’s), and the result no longer rounds back to exactly 0.3.

(0.1).toFixed(20); // "0.10000000000000000555"
(0.2).toFixed(20); // "0.20000000000000001110"

Neither value is exactly what it looks like — they’re the closest doubles available, and the tiny errors compound.

Why this isn’t a bug worth “fixing”

This isn’t sloppy engineering — it’s an inherent trade-off of representing infinite real numbers in a fixed number of bits, present in essentially every mainstream programming language’s default numeric type. The alternative (arbitrary-precision decimal arithmetic by default) would be dramatically slower for the vast majority of numeric code that doesn’t need exact decimal precision.

Comparing floating-point numbers safely

Never compare floats for exact equality when they’ve been through arithmetic. Instead, check whether the difference is smaller than an acceptably tiny threshold:

function nearlyEqual(a, b, epsilon = Number.EPSILON) {
  return Math.abs(a - b) < epsilon;
}

nearlyEqual(0.1 + 0.2, 0.3); // true

Number.EPSILON is the smallest difference JavaScript can represent between 1 and the next representable number above it — a reasonable default tolerance for comparisons near that magnitude, though for numbers far from 1 you may need a larger, scaled tolerance.

Money: the case where this actually matters

The floating-point rounding error above is usually invisible in everyday code, but it becomes a real problem the moment you’re summing prices, calculating totals, or doing anything involving currency, where users will notice a cent missing.

// Fragile — accumulated rounding error across many additions
let total = 0;
[19.99, 5.0, 3.5].forEach((price) => (total += price));
console.log(total); // 28.49 today, but this class of bug compounds badly at scale

The standard fix is to work in the smallest currency unit (cents, not dollars) using integers, which don’t have this rounding problem at all:

// Robust — integers have no floating-point rounding issue
const cents = [1999, 500, 350];
const totalCents = cents.reduce((sum, c) => sum + c, 0);
console.log(totalCents / 100); // 28.49, computed exactly

For anything beyond simple sums — multi-currency conversion, tax calculations with many decimal places — reach for a dedicated decimal arithmetic library (like decimal.js or big.js) instead of hand-rolling cent-based math everywhere.

💡 Consiglio

toFixed(2) alone does not fix this — it only rounds the displayed string, the underlying number still carries the accumulated error, which can resurface the next time you do arithmetic on it. Fix the representation (integers, or a decimal library), not just the formatting.

FAQ

❓ Does this affect integers too?

Not for the range where it matters practically — integers up to 2^53 (Number.MAX_SAFE_INTEGER) are represented exactly in a double. The rounding error above is specific to fractional decimal values that don’t have an exact binary representation.

❓ Can BigInt fix this?

BigInt solves a related but different problem — arbitrary-precision integers, not decimals. It doesn’t have a fractional part at all, so it’s not a drop-in fix for money math, though representing currency as integer cents (as shown above) pairs naturally with it for very large sums.

❓ Is this fixed by Temporal or other newer JS features?

No — Temporal solves date/time correctness, a separate problem from decimal arithmetic. There’s a separate TC39 proposal for a native Decimal type, but as of now it hasn’t shipped, so integer-based math or a library remains the practical fix.

Conclusion

0.1 + 0.2 !== 0.3 isn’t a JavaScript quirk — it’s a direct, predictable consequence of representing decimal fractions in binary floating point, shared by nearly every mainstream language. Never compare floats for exact equality after arithmetic; use an epsilon-based comparison instead, and for money specifically, work in integer cents or reach for a decimal library rather than trusting toFixed() to paper over the underlying representation.

References

Condividi:XLinkedInFacebookWhatsApp
Diego Betto

Written by

Diego Betto

Co-Founder & CTO at PAPION. Senior full-stack engineer specializing in React, TypeScript, Node.js, and application security.