Course outline · 0% complete

0/32 lessons0%

Course overview →

Floats Are Approximate

lesson 3-3 · ~9 min · 9/32

The most famous surprise in programming

Ask Python for 0.1 + 0.2 and it answers 0.30000000000000004. This is not a bug. Computers store floats in binary (base 2), and 0.1 has no exact binary representation, the same way 1/3 has no exact decimal form (0.3333... forever). Python stores the closest binary value it can, and the leftover error occasionally becomes visible.

This matters the first time money or measurements enter your code: a price total that prints with 17 decimal places, or an == comparison that fails for no visible reason. Two working rules:

  1. Never compare floats with ==. Round both sides first, or check that the difference is tiny.
  2. Format for display. An f-string with :.2f shows the number the way a human expects, without changing the stored value.

Seeing the error, then working around it

The first two lines expose the representation error, and the last two show round restoring the comparison to something usable.

print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(round(0.1 + 0.2, 2))
print(round(0.1 + 0.2, 2) == 0.3)

Output

0.30000000000000004
False
0.3
True

The sum lands a hair above 0.3, and == reports False because it compares the stored values exactly, not the values as a human would read them. Rounding to two decimals collapses that tiny excess, so the third line prints 0.3 and the fourth comparison finally succeeds. The lesson is not that == is broken but that it is exact, and exactness is the wrong tool for numbers held approximately.

0.1 + 0.2 == 0.3 evaluates to False because floats are stored in binary, and neither 0.1 nor 0.2 has an exact binary form.

Python keeps the nearest representable value for each, and those two small inaccuracies combine so that the sum sits a fraction above 0.3. The comparison then correctly reports that two different stored numbers are not equal.

Nothing about this is specific to Python. Every mainstream language using standard floating point behaves identically, since the cause lies in the number format rather than the language. The practical responses are to round both sides before comparing, to test whether the difference is tiny, or to work in whole units such as cents where the values are exact integers.

Working with money anyway

Serious systems dodge the problem by doing money math in whole cents, because ints are exact, and only converting to dollars for display. Two more number tools worth knowing now:

  • abs(x) gives the distance from zero: abs(-7) is 7. It powers the standard "how far apart are these numbers" check, abs(a - b).
  • round(x, 2) rounds to 2 decimal places, but it rounds the stored binary value, so round(2.675, 2) gives 2.67, not the 2.68 you might expect. One more reason whole cents is the professional habit.

The cents technique keeps the arithmetic in exact integers and converts back to a decimal only at the moment of display.

a = 0.1
b = 0.2
cents = round((a + b) * 100)
print(cents)
print(f"total: {cents / 100:.2f}")

Output

30
total: 0.30

Multiplying by 100 moves the value into cents, and round snaps the slightly-off result onto the exact integer 30. From that point the number is an int, so any further adding or comparing is exact and none of the earlier surprise can return. The division by 100 happens inside the f-string, purely for presentation, and :.2f supplies the trailing zero that round alone would not produce.