ESC

Type to search the knowledge base.

Number Precision and IEEE 754

Why 0.1 + 0.2 !== 0.3, safe integers, rounding strategies, and when to use integers, BigInt, or decimal libraries.

intermediate3 min read
  • javascript
  • number-precision

JavaScript’s Number is IEEE 754 double-precision binary floating point (64-bit). That gives a huge range and fractional values — and classic surprises like money math that doesn’t add up.

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

Not a bug in V8. Binary fractions cannot represent 0.1 exactly, same as 1/3 in decimal.

Mental model

  • 1 sign bit, 11 exponent bits, 52 mantissa bits (+ implicit leading 1)
  • Integers are exact only in a range: Number.MIN_SAFE_INTEGER … Number.MAX_SAFE_INTEGER (±(2⁵³−1))
  • Outside that range, integers start skipping (you can’t represent every integer)
Number.MAX_SAFE_INTEGER;      // 9007199254740991
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; // true — both round to same
Number.isSafeInteger(9007199254740993); // false

Comparisons that work

function nearlyEqual(a, b, eps = Number.EPSILON) {
  return Math.abs(a - b) < eps;
}
// For larger magnitudes, use relative epsilon:
function nearlyEqualRel(a, b, rel = 1e-9) {
  return Math.abs(a - b) <= rel * Math.max(1, Math.abs(a), Math.abs(b));
}

Number.EPSILON is the difference between 1 and the next representable number — fine near 1, wrong as a universal money tolerance.

Money: don’t use raw floats

// Prefer integer minor units
const priceCents = 1999; // $19.99
const taxCents = Math.round(priceCents * 0.08);
const totalCents = priceCents + taxCents;

function formatUSD(cents) {
  return (cents / 100).toLocaleString('en-US', {
    style: 'currency',
    currency: 'USD',
  });
}

Or a decimal library / BigInt for fixed-scale arithmetic. Never accumulate floats for ledgers.

Rounding APIs

Math.round(1.5);  // 2 — half away from -∞ for positives… know banker's rounding is NOT default
Math.floor(-1.2); // -2
Math.trunc(-1.2); // -1
(1.005).toFixed(2); // "1.00" in many engines — string, and still FP quirks

For UI display, format once at the edge with Intl.NumberFormat. For correctness, round at defined business steps in integer space.

Parsing and precision

Number('0.1');
parseFloat('0.1px'); // 0.1 — stops at non-num
parseInt('08', 10);  // always pass radix

0.1 + 0.2 + 0.3; // left-assoc FP path ≠ 0.6 exactly

BigInt when integers exceed safe range

const id = 9007199254740993n;
id + 1n; // exact
// Cannot mix Number and BigInt with +

IDs from backends as large integers should arrive as strings or BigInt, not Number.

Interview answer (out loud)

“JS numbers are IEEE 754 doubles. Many decimals aren’t exact in binary, so 0.1+0.2 isn’t 0.3. Integers are safe only up to 2^53−1. For money I use integer cents or a decimal type; for comparisons I use an epsilon or relative tolerance; for huge ints I use BigInt or strings.”

Multiplication order

0.1 * 3; // 0.30000000000000004
(0.1 * 10 * 3) / 10; // sometimes used as a trick — still not a money system

Integer scaling remains the robust approach.

isFinite vs Number.isFinite

isFinite('10'); // true — coerces
Number.isFinite('10'); // false
Number.isFinite(10); // true

Prefer Number.isFinite / Number.isNaN for type-safe checks.

Further reading

Related guides