Lesson 49 of 80
0.1 + 0.2 !== 0.3 is the famous symptom. Number.EPSILON, and knowing when to reach for it, is the actual fix for comparing floats safely.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Every JS Number is a 64-bit IEEE-754 double: a sign bit, an 11-bit exponent, and a 52-bit fraction (mantissa). That fraction can only represent sums of powers of two exactly — so 0.5, 0.25, 0.125 are exact, but 0.1 isn't, for the same reason 1/3 has no exact finite decimal: the value just doesn't land on a representable point in that base. The engine stores the closest double it can, and every arithmetic operation on an already-approximate value can compound that tiny error.
The practical consequence isn't that math is "broken" — it's that exact equality (===) on the result of floating-point arithmetic is almost never the check you actually want. Number.EPSILON exists to give you a principled tolerance to compare within, instead of picking an arbitrary threshold.
0.1 + 0.2 === 0.3;
// false
0.1 + 0.2;
// 0.30000000000000004
Number.EPSILON;
// 2.220446049250313e-16 — the smallest gap representable between 1 and the next double
Number.EPSILON isn't "the amount of error to expect" — it's a unit: the smallest possible difference between two doubles near 1. A tolerance built from it is the standard pattern for "close enough":
function nearlyEqual(a, b, epsilon = Number.EPSILON) {
return Math.abs(a - b) < epsilon * Math.max(Math.abs(a), Math.abs(b), 1);
}
nearlyEqual(0.1 + 0.2, 0.3); // true
nearlyEqual(0.1 + 0.2, 0.3, 0); // false — zero tolerance is just === again
Notice the Math.max(...) scaling: floating-point error grows with the magnitude of the numbers involved, not just with how many operations you did. nearlyEqual(100000.1 + 0.2, 100000.3) needs a bigger absolute tolerance than the 0.1 + 0.2 case, because doubles have fewer bits of fraction left over to represent decimals once the integer part is large — scaling the epsilon by the operands' magnitude is what keeps the check meaningful at any scale, rather than working only near 1.
A related, more common trap — rounding for display, not comparison:
(1.005).toFixed(2);
// "1.00", not "1.01" — 1.005 isn't exactly representable either; the closest
// double is actually a hair under 1.005, so rounding-to-2-places rounds down
Number.EPSILON alone, unscaled, is not a universal tolerance. Using Math.abs(a - b) < Number.EPSILON works only for values near 1; for large numbers the actual rounding error is far bigger than EPSILON itself, and the check will wrongly report "not equal" for values that are as close as floating point can represent. Scale it, as in nearlyEqual above.Number.MAX_SAFE_INTEGER — this isn't an integer problem. 2 + 2 === 4 is always true. The imprecision specifically comes from fractional (non-power-of-two) values, or from integers past 2^53 - 1, which is the separate concern the BigInt-and-precision lesson covers.0.1 + 0.2 result. The fix is always a modeling choice: tolerate it with an epsilon, avoid it with integers/decimals, or round it away only at the display boundary with toFixed/Intl.NumberFormat, never mid-calculation.The BigInt lesson covers this same IEEE-754 double from the integer side — what happens once a whole number outgrows the 53 bits of exact integer range, and BigInt as the fix. This lesson covers the fractional side of the identical format — values that were never exactly representable in binary to begin with, no matter how small — where the fix isn't a new type but a deliberate comparison tolerance or a switch to integer-based units. Same root cause, two different symptoms, two different fixes.