Why 0.1 + 0.2 is not 0.3, and what to do about it

Every mainstream language gives 0.30000000000000004 for 0.1 + 0.2. The reason is not a bug, it is base two, and it has consequences for anything that adds money in a loop. Here is the exact arithmetic, the four places it bites, and the rules that keep it harmless.

Open a browser console, a Python prompt, a Ruby prompt, a Java file or a C compiler and ask for 0.1 + 0.2. You will get 0.30000000000000004 in every one of them.

This is not a bug in any of those languages. They all implement the same standard — IEEE 754, published in 1985 and revised since without changing this behaviour — and the standard is doing exactly what it promises. It is worth understanding properly if you write anything that handles money, because the failure mode is not a crash. It is a total that is off by a cent, in a report that nobody checks by hand.

The short version

Computers store fractions in base two. In base two, a tenth is a repeating fraction, the same way a third is in base ten. You cannot write 1/3 exactly with a finite number of decimal digits, and a computer cannot write 1/10 exactly with a finite number of binary digits. It stores the closest value it can, and that value is not quite a tenth.

Why a tenth does not fit

A fraction terminates in base b if and only if every prime factor of its denominator also divides b. Base ten has factors 2 and 5, so tenths, quarters and eighths all terminate but thirds and sevenths do not. Base two has only the factor 2, so halves, quarters and eighths terminate — and anything with a 5 in the denominator does not.

You can watch it fail. To write a decimal fraction in binary, repeatedly double it and record the integer part:

0.1 × 2 = 0.2  → bit 0
0.2 × 2 = 0.4  → bit 0
0.4 × 2 = 0.8  → bit 0
0.8 × 2 = 1.6  → bit 1
0.6 × 2 = 1.2  → bit 1
0.2 × 2 = 0.4  → bit 0  ← loop

The carry has returned to a value already seen, so the sequence repeats forever.

The machine has to stop somewhere. A double-precision float gives 53 bits to the significant digits, so it keeps 53 of those bits and rounds the rest to the nearest representable value.

What is actually stored

A double is a sign, an 11-bit exponent and a 53-bit significand (52 bits stored, one implied), so every value it can hold is an integer of at most 53 bits times a power of two. The three numbers in this problem are:

You wrote It stored Which is exactly
0.1 3602879701896397 ÷ 2⁵⁵ 0.1000000000000000055511151231257827021181583404541015625
0.2 3602879701896397 ÷ 2⁵⁴ 0.200000000000000011102230246251565404236316680908203125
0.3 5404319552844595 ÷ 2⁵⁴ 0.299999999999999988897769753748434595763683319091796875

Every one of those is the closest double to the decimal you typed. Note that the errors do not run the same way: the stored 0.1 and 0.2 are slightly above their targets, and the stored 0.3 is slightly below its own.

The addition, step by step

Add the stored values exactly, as fractions.

That numerator needs 54 bits, and a double has 53. The sum sits between two neighbouring doubles and has to be rounded to one of them. Rewriting it over 2⁵⁴ shows where it lands:

10808639505689191/2⁵⁵
 = 5404319552844595.5/2⁵⁴

Exactly halfway. IEEE 754’s default rule for a tie is round half to even: of the two candidate significands, 5404319552844595 and 5404319552844596, take the even one. So the sum rounds up, away from the value that would have printed as 0.3:

result = 5404319552844596/2⁵⁴
      = 0.3000000000000000444…

and the nearest double to the literal 0.3 is one step lower, at 5404319552844595 / 2⁵⁴. The two are one unit in the last place apart:

(0.1 + 0.2) − 0.3
  = 2⁻⁵⁴
  = 5.551115123125783e-17

Which is why the printed result grows an extra digit. Every language prints the shortest decimal string that round-trips back to the same double, and for the sum that string is 0.30000000000000004 — the 0.3 you wanted belongs to the double next door.

Two things follow. The sum is not “wrong” by any amount the standard promised to avoid: the error is half an ulp, the best any correctly rounded addition can do. And the result of comparing it to 0.3 with === is not a matter of luck. It is deterministic, on every machine, forever.

The four places this actually bites

Comparing computed values for equality

0.1 + 0.2 === 0.3 is false and always will be. So is a loop that increments by 0.1 and tests total === 1. Compare with a tolerance instead, and scale the tolerance to the size of the numbers you are comparing — an absolute tolerance of 1e-9 is far too loose for values near zero and far too tight for values in the billions.

Accumulating in a loop

This is the one that costs money, because it hides. Add 0.1 to a running total ten times and you get 0.9999999999999999. Every addition rounds, and the rounding errors accumulate over the sequence rather than cancelling.

Any schedule built by repeated addition inherits this: an amortisation table that subtracts each month’s principal from a running balance, a contribution plan that adds each period’s deposit to a running total. Over 360 months the drift is still tiny, but the last row is where it shows up, and the last row is exactly the one a reader checks — a mortgage schedule whose final balance reads 0.0000000000004 instead of zero looks broken even though it is correct to within a ten-thousandth of a cent.

Two defences. Where a closed form exists, compute the value from the period index rather than from the previous row, so errors cannot compound. Where a running balance is genuinely required, make the last step land on zero by construction rather than by luck. An amortisation schedule that caps each payment at what is actually owed trims the final payment to the exact remaining balance, and closes on a true zero instead of on a residue you then have to decide whether to forgive.

Rounding at the wrong moment

0.145 × 100 is 14.499999999999998, not 14.5, because the stored 0.145 is a hair below the decimal you typed. Round that to the nearest integer and you get 14, when every human on earth expects 15.

The fix is to do the base-ten shift in base ten. Take the number’s decimal string form, move the exponent, and re-parse: '0.145e2' parses to the double nearest 14.5, which then rounds the way a reader expects. That is the trick behind the rounding helper the arithmetic on this site shares.

Integers larger than 2⁵³

A double holds 53 bits of significand, so 9007199254740992 + 1 evaluates to 9007199254740992. Above that threshold, consecutive integers are no longer all representable.

This is not a theoretical limit in crypto. One ether is 10¹⁸ wei, which is over a hundred times past the safe integer range, so any wei-scale value handled as a float is already approximate before you do anything with it. There is no rounding policy that rescues this; the type is simply wrong for the job. The answer is arbitrary-precision integers — BigInt in JavaScript. Convert through exact integer arithmetic, and where a conversion genuinely cannot be represented exactly in the target unit, report that rather than quietly rounding. Crypto amounts are integers works through why the protocols do it this way.

The three fixes, and when each is right

Round at the output boundary. Compute at full precision and round exactly once, on the way out. Right for estimates: projections, comparisons, anything where the result informs a decision rather than settles a debt. It is what the arithmetic here does, with money to two decimal places, percentages to four and quantities to eight.

Work in minor units as integers. Hold cents, not currency; hold satoshis, not coins. All the arithmetic is exact integer arithmetic, and there is nothing to round until you display it. Right for ledgers, payments and anything that has to reconcile to the last unit. The cost is that division and percentages need explicit rounding decisions at every step, which is a feature — those decisions were always there, and integers force you to make them on purpose.

Use a decimal library. Arbitrary-precision decimal arithmetic gets you exact tenths and configurable rounding modes. Right for accounting, tax and interest calculations that must match a published figure to the cent. The costs are real: every operation becomes a method call, and the library is a dependency that ships to every visitor if the code runs in a browser.

Choosing between them is a question about consequences, not about correctness. What breaks if this number is off by a hundredth of a unit? If the answer is “a report reads slightly differently”, round at the boundary. If the answer is “an account does not balance”, use integers or decimals.

Rules of thumb

  1. Never test computed floats for exact equality. Use a tolerance, scaled to the magnitude.
  2. Round once, at the edge, when you display or return. Never feed a rounded value back into the next calculation.
  3. Prefer a closed form over an accumulator when both are available.
  4. Anything above 2⁵³ that must be exact is not a float. Use integers.
  5. Do base-ten rounding through the decimal string, not by multiplying by a power of ten.
  6. State the precision of every output. A number quoted to eight decimal places is making a claim about its own accuracy, and it should be a true one.

This piece describes IEEE 754 double-precision arithmetic, which is what JavaScript, Python floats, Java doubles and C doubles all use. Languages with a native decimal type behave differently by design.