Math·Numbers

Why 0.1 + 0.2 is not 0.3

Every language gets this answer, and it is not a bug. Binary floating point cannot hold 0.1, so the number you typed was never the number that got stored. Here is what is actually in there, and what to do about money.

Level
Beginner
Read
11 min
Updated
2026-09-08

Before you start

  • A REPL in any language, to check the numbers here yourself

Type 0.1 + 0.2 into any language that uses IEEE 754 doubles, which is nearly all of them, and you get 0.30000000000000004. This surprises people once and then annoys them for years.

It is not a rounding bug in the addition. The addition was exact. The problem happened earlier, when 0.1 was stored.

The numbers you typed are not the numbers you have #

A double holds a number as a sign, a 52-bit fraction and an exponent, all in base two. That means it can only represent numbers that are some whole number divided by a power of two. One tenth is not one of those, in the same way one third has no exact decimal expansion.

So 0.1 gets stored as the closest double there is, and the closest double is not one tenth:

you typed   0.1
you stored  0.1000000000000000055511151231257827021181583404541015625
you typed   0.2
you stored  0.2000000000000000111022302462515654042363166809082031250

Add those two stored values and you get their exact sum, which lands nearer to a different double than the one closest to 0.3:

0.1 + 0.2   0.3000000000000000444089209850062616169452667236328125
0.3         0.2999999999999999888977697537484345957636833190917968750

Two different doubles, so === is false. The gap between them:

The comparison that fails

5.55e-17

0.1 + 0.2 − 0.3

Not zero, so === is false

15–17

Exact digits in a double

Decimal digits, not binary

9,007,199,254,740,991

Largest exact integer

2⁵³ − 1

Computed with IEEE 754 binary64, the double type in JavaScript, Python floats, Java double and C double.

Note

Nothing here is specific to JavaScript. Python prints 0.30000000000000004 too, and so do Java, C, Go, Ruby, Swift and PHP. Languages that appear to get it right, like some calculator apps, are rounding the output for display.

Why the error moves around #

The error is not a constant you can subtract. Each operation rounds to the nearest representable double, so errors accumulate in whichever direction the rounding happened to go.

Adding 0.1 to a running total, over and over, drifts in both directions before settling into a consistent bias:

Error from adding 0.1 repeatedly Difference between the running sum and the exact value, in units of 10⁻¹⁵
-20 -15 -10 -5 0 5 10 20 30 40 50 60 70 80 90 100 Error

Measured, not modelled: each point is the observed value of a loop adding 0.1 that many times, minus the exact decimal answer.

View data as a table
Error from adding 0.1 repeatedly
AdditionsError
10-0.111
200.444
301.33
401.78
50-1.78
60-5.33
70-8.88
80-12.43
90-15.99
100-19.54

Ten additions of 0.1 do not give you 1. They give you 0.9999999999999999. Which is why this test never passes:

loop.js
let total = 0;
for (let i = 0; i < 10; i++) total += 0.1;

total === 1;          // false
total;                // 0.9999999999999999

The fix is not a better loop. It is to stop comparing floats for equality.

Precision runs out as numbers get bigger #

A double has a fixed number of significant digits, not a fixed number of decimal places. Spend them on the integer part and there are fewer left for the fraction.

Decimal places you have left, by magnitude How finely a double can still divide numbers of this size
0 5 10 15 20 1 15.65 1 thousand 12.94 1 million 9.93 1 billion 6.92 1 trillion 3.91 1 quadrillion 0.9

Derived from the actual gap between consecutive doubles at each magnitude. Past a quadrillion, doubles cannot represent every whole number, let alone a fraction of one.

View data as a table
Decimal places you have left, by magnitude
MagnitudeDecimal places
115.65
1 thousand12.94
1 million9.93
1 billion6.92
1 trillion3.91
1 quadrillion0.9

At one quadrillion the gap between neighbouring doubles is larger than 1, so consecutive integers collide:

collide.js
9007199254740992 === 9007199254740993; // true. Both are 2⁵³.

That is the real reason a 64-bit database id should not travel through JSON as a number. It arrives as a double, and ids past 2⁵³ quietly change value. Send them as strings.

What to do instead #

Three situations, three different answers. Picking by situation matters, because the standard advice for one of them is actively wrong for another.

Comparing two computed floats #

Compare with a tolerance, not with ===. And make the tolerance relative to the size of the numbers, because as the section above shows, an absolute epsilon that is right at 1 is far too small at a million.

close-enough.js
const closeEnough = (a, b, tolerance = 1e-9) =>
  Math.abs(a - b) <= tolerance * Math.max(1, Math.abs(a), Math.abs(b));

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

Money #

Do not use floats at all. Not with a tolerance, not with rounding at the end, not with a careful order of operations.

Store money as an integer number of the smallest unit, cents or minor units, and divide only when you display it. An invoice total that is out by 0.00000000000001 is not nearly right, it is a number that fails reconciliation and cannot be explained to an accountant.

money.js
// Cents, as integers. Exact, and it sorts and sums correctly.
const items = [1299, 499, 2350];
const subtotal = items.reduce((a, b) => a + b, 0); // 4148
const withVat = Math.round(subtotal * 1.2);        // 4978

const display = (cents) => `€${(cents / 100).toFixed(2)}`;
display(withVat); // "€49.78"

Watch out

Rounding at the end does not save you, because the value you are rounding is already wrong in a direction you did not choose. Math.round(1.005 * 100) / 100 gives 1, not 1.01, because 1.005 is stored as 1.00499999999999989... and it genuinely is below the midpoint. Every "round half up" helper written on top of floats has this bug.

Most languages ship an exact decimal type for exactly this: BigDecimal in Java, decimal.Decimal in Python, NUMERIC in Postgres. Use them, or use integers.

Displaying a result #

Round for output, and only for output. Keep full precision in the value and decide the number of digits at the edge, where you know what the number means.

display.js
const value = 0.1 + 0.2;
value.toFixed(2);        // "0.30"
value.toPrecision(3);    // "0.300"

The short version #

  • 0.1 is not representable in binary, so the value stored was never one tenth.
  • The addition is exact; the inputs were already approximate.
  • Error accumulates in both directions, so it cannot be corrected by subtracting a constant.
  • Precision is a budget of significant digits, and large integers spend all of it.
  • Never floats for money. Integer minor units, or a decimal type.

When a JSON payload comes back with a number that looks slightly wrong, or an id that changed in transit, the JSON formatter here will show you the value exactly as it parsed, which is usually enough to tell a display problem from a precision one. It runs in your browser, so nothing is uploaded.

Guides land here first.

New guides and tools get posted as they go up. One email when the Mac app ships, nothing else.