Skip to content
JavaScript js numbers 4 min read

Floating-Point Precision

JavaScript has a single numeric type for non-integers: the IEEE 754 double-precision binary float. That format cannot represent most decimal fractions exactly, which is why 0.1 + 0.2 famously does not equal 0.3. Understanding why this happens — and the standard patterns for working around it — is essential for anything involving money, measurements, or precise rounding.

Why 0.1 + 0.2 !== 0.3

Numbers are stored in base-2. Just as 1/3 has no finite decimal representation (0.3333…), values like 0.1 and 0.2 have no finite binary representation. They get rounded to the nearest representable 64-bit value, and those tiny rounding errors accumulate when you add them.

console.log(0.1 + 0.2);            // not 0.3
console.log(0.1 + 0.2 === 0.3);    // false
console.log((0.1 + 0.2).toFixed(20));

Output:

0.30000000000000004
false
0.30000000000000004441

The result is off by about 4.4e-17. This is not a bug in JavaScript — every language using IEEE 754 doubles (Java’s double, C’s double, Python’s float) behaves the same way.

Integers are safe up to Number.MAX_SAFE_INTEGER (2^53 − 1). The precision problem is specifically about fractional decimal values, not whole numbers within that range.

Comparing floats with Number.EPSILON

Because exact === comparison is unreliable for computed floats, compare with a small tolerance. Number.EPSILON is the smallest difference between 1 and the next representable number (~2.22e-16) and makes a sensible baseline tolerance.

const nearlyEqual = (a, b, epsilon = Number.EPSILON) =>
  Math.abs(a - b) < epsilon;

console.log(nearlyEqual(0.1 + 0.2, 0.3)); // true

For larger magnitudes the absolute error grows, so scale the tolerance relative to the values being compared:

const closeEnough = (a, b) =>
  Math.abs(a - b) <= Number.EPSILON * Math.max(Math.abs(a), Math.abs(b), 1);

console.log(closeEnough(1000000.1 + 0.2, 1000000.3)); // true

Rounding strategies

Rounding is the most common fix when you only need to display a value at a fixed precision.

ApproachReturnsBest for
(n).toFixed(d)stringDisplay with exactly d decimals
Math.round(n * 10**d) / 10**dnumberRounding for further math
Intl.NumberFormatstringLocale-aware display, currency
(n).toPrecision(p)stringSignificant figures
const round = (n, d = 2) => Math.round(n * 10 ** d) / 10 ** d;

console.log(round(0.1 + 0.2));     // 0.3
console.log((2.5).toFixed(0));     // "3"
console.log((1.005).toFixed(2));   // "1.00"  ← surprise!

Output:

0.3
3
1.00

The last line is a classic gotcha: 1.005 is actually stored as 1.00499999…, so it rounds down. There is no clever rounding trick that fully escapes this — the value was already imprecise before you rounded it.

Never store currency as a float and round only at the end. The errors are already baked in. Use integers or a decimal library from the start.

Integer cents for money

The simplest robust pattern for money is to work entirely in the smallest unit (cents, pennies, satoshis) as integers, and only convert to a display string at the boundary.

// Store and compute in integer cents — never floats.
const addCents = (...amounts) => amounts.reduce((sum, c) => sum + c, 0);

const subtotal = addCents(1099, 250, 4999); // $10.99 + $2.50 + $49.99
console.log(subtotal);                        // 8348 cents

const format = (cents, locale = "en-US", currency = "USD") =>
  new Intl.NumberFormat(locale, { style: "currency", currency })
    .format(cents / 100);

console.log(format(subtotal)); // "$83.48"

Output:

8348
$83.48

Addition, subtraction, and multiplication-by-integer are all exact in this scheme. The only place you touch a float is the final cents / 100 divide, immediately consumed by Intl.NumberFormat for display.

Decimal libraries

When you need division, tax rates, interest, or arbitrary precision, reach for a dedicated decimal library. These represent numbers in base-10 internally, so decimal fractions are exact.

import Decimal from "decimal.js";

const total = new Decimal("0.1").plus("0.2");
console.log(total.equals("0.3")); // true
console.log(total.toString());    // "0.3"

const price = new Decimal("19.99");
const tax = price.times("0.0825");
console.log(tax.toDecimalPlaces(2).toString()); // "1.65"
LibraryNotes
decimal.jsArbitrary precision, configurable rounding modes
big.jsTiny, minimal API for basic arithmetic
bignumber.jsLike decimal.js, widely used in finance/crypto

For whole-number values larger than Number.MAX_SAFE_INTEGER, the built-in BigInt type is the right tool — but note it holds integers only, not decimals.

Best Practices

  • Never compare computed floats with ===; use a tolerance based on Number.EPSILON.
  • Store money as integer cents and convert to a display string only at the edge.
  • Use Intl.NumberFormat for currency and locale-aware formatting instead of manual string building.
  • Reach for a decimal library (decimal.js, big.js) when you need exact division or percentages.
  • Remember toFixed returns a string and can round counterintuitively ((1.005).toFixed(2)).
  • Round at the display layer, not during intermediate calculations, to avoid compounding error.
  • Use BigInt for large integers, but never expect it to handle fractional values.
Last updated June 1, 2026
Was this helpful?