The most famous bug that is not a bug
Adding one tenth to two tenths in any mainstream language does not produce three tenths. It produces 0.30000000000000004, and the comparison against 0.3 is false.
This surprises almost everyone once, and then it never stops being relevant, because it is the reason financial code is written with integers and the reason float comparisons in tests are written with a tolerance.
The code below shows the result, the failed comparison, and what is really stored behind the innocent literal 0.1.
Looking at what is really stored
The sum, the comparison, and 0.1 printed to twenty decimal places.
print(0.1 + 0.2) print(0.1 + 0.2 == 0.3) print(f"{0.1:.20f}")
Output
0.30000000000000004 False 0.10000000000000000555
The third line is the explanation for the first two. The literal 0.1 is not stored as one tenth, it is stored as a value slightly above it, and normal printing hides that by showing only as many digits as round-trip correctly.
The comparison is false by a margin of about 5.5 × 10⁻¹⁷. That is far too small to matter in a measurement and exactly large enough to break an equality test, which is the worst combination.
Why this happens
From lesson 2-1, everything is bits, so decimals must be stored in binary too.
In decimal, 1/3 has no exact form and runs 0.3333... forever. In binary, 1/10 has the same problem. There is no finite pattern of bits equal to exactly 0.1, so the computer stores the closest value it can, which is 0.100000000000000005551 and onward.
The standard for this is floating point, the float type, using 64 bits per number. It gives about 15 to 16 reliable decimal digits, and every calculation can pick up a microscopic rounding error.
So 0.1 + 0.2 adds two already-slightly-wrong numbers and lands on 0.30000000000000004. Nothing is broken.
| Fraction | Exact in decimal | Exact in binary |
|---|---|---|
| 1/2 | yes, 0.5 | yes |
| 1/4 | yes, 0.25 | yes |
| 1/3 | no | no |
| 1/10 | yes, 0.1 | no |
Only fractions whose denominator is a power of 2 come out exact in binary, which is why halves and quarters behave perfectly and tenths do not.
What to do about it
Three standard tools cover essentially every case, ordered from most common to most exact:
- Never compare floats with
==. Compare rounded values, or usemath.isclose, which accepts a relative tolerance. - For money, use integers. Count cents rather than dollars, so 19.99 dollars is the integer 1999, and integers in binary are exact.
- For decimal-exact math such as finance and billing, use the
decimalmodule, which stores digits the way humans write them at the cost of speed.
| Need | Tool |
|---|---|
| is this measurement close enough | math.isclose |
| exact currency arithmetic | integer cents |
| exact decimal arithmetic with fractions | decimal.Decimal |
The integer-cents approach is the one most production systems use, because it is exact, fast, and needs no library. The decimal module earns its cost when the arithmetic itself involves decimal fractions, such as a tax rate applied to a price.
The three fixes
Rounding before comparing, math.isclose, and Decimal.
import math print(round(0.1 + 0.2, 9) == round(0.3, 9)) print(math.isclose(0.1 + 0.2, 0.3)) from decimal import Decimal print(Decimal("0.1") + Decimal("0.2"))
Output
True
True
0.3Rounding to nine decimal places throws away an error that lives around the seventeenth, which is why the comparison then succeeds. math.isclose does the same thing more honestly, by asking whether the difference is small relative to the values.
Decimal takes a string such as "0.1" on purpose. Writing Decimal(0.1) would first build a float and inherit its error, so the exactness would be lost before Decimal ever saw the value.
Fixing a money bug with integer cents
Three items at ten cents each, first in floats and then in cents.
total_float = 0.10 + 0.10 + 0.10 print(total_float) total_cents = 10 + 10 + 10 dollars = total_cents // 100 cents = total_cents % 100 print(f"${dollars}.{cents:02d}")
Output
0.30000000000000004 $0.30
total_cents is 30, an exact integer that no amount of further addition can drift. Integer division by 100 gives the whole dollars and % 100 gives the leftover cents.
The :02d format pads to two digits, so five cents prints as 05 rather than 5. Without it, a total of 305 cents would print as $3.5, which is a formatting bug sitting on top of otherwise correct arithmetic.
Switching languages does not help
A teammate proposing to move to JavaScript because 0.1 + 0.2 != 0.3 will find JavaScript printing the identical result.
Every mainstream language uses the same 64-bit binary floating point standard, called IEEE 754, and it is implemented in hardware. The CPU itself does float math this way, so Python, JavaScript, Java, C++, and the rest all print 0.30000000000000004.
| Language | 0.1 + 0.2 |
|---|---|
| Python | 0.30000000000000004 |
| JavaScript | 0.30000000000000004 |
| Java | 0.30000000000000004 |
The fix is technique rather than a language switch: integer cents, isclose, or a decimal type. Any language that appeared to avoid the problem would be hiding it in its printing, not in its arithmetic.