The first time 0.30000000000000004 showed up on my screen I did what everyone does. I blamed Python. Then I blamed my keyboard. Then Python again, louder.
It's not Python. JavaScript prints the same number, and so does nearly every language on your machine, because nearly every language stores decimals the same way: as IEEE 754 doubles, a standard from 1985 that is older than most of the people complaining about it.
Where does the extra 4 come from?
Write one third as a decimal. You can't. You get 0.333 and then the 3s keep going until you run out of patience or paper, and whatever you eventually write down is close to a third but is not a third.
Binary has the same problem with different numbers. 0.1 in binary is 0.0001100110011..., the "0011" repeating forever. A double gives you 53 bits of precision to store that with, so the machine stores the closest value it can fit and moves on.
You can see the stand-in Python actually uses:
from decimal import Decimal
print(Decimal(0.1))
Output:
0.1000000000000000055511151231257827021181583404541015625
That's the 0.1 your computer owns. 0.2 is a different approximation with a different tail. Add the two stand-ins and the errors come along for the ride:
print(0.1 + 0.2)
0.30000000000000004
Nobody made an error in the addition. The addition is exact. It's exact arithmetic performed on inexact numbers, which is a different and much more annoying sentence.
Does it get worse if you keep adding?
Yes, politely.
total = 0.0
for x in [0.1] * 10:
total += x
print(total)
0.9999999999999999
Ten copies of almost-0.1, added one at a time, each rounding nudging the pile. Amusing footnote: Python 3.12's built-in sum() returns exactly 1.0 for this, because it now uses compensated summation that tracks the lost crumbs. The plain loop doesn't. When a language starts quietly patching around a problem, that's your sign the problem is everywhere.
When should you actually care?
Two situations. The first is equality. 0.1 + 0.2 == 0.3 is False, and any code that compares floats with == is a landmine with a countdown you can't read. Compare with a tolerance instead:
import math
print(math.isclose(0.1 + 0.2, 0.3))
True
The second is money, and this one I take personally. I sell a small digital product priced at $7.99 and work out revenue per visitor in a spreadsheet. The spreadsheet runs on the same doubles. At my scale, a floating point error is a ghost worth a ten-thousandth of a cent and I lose no sleep over it. But the fix costs nothing, so the rule in my notebook is absolute: money is counted in integers of the smallest unit, 799 cents, or in Decimal. Never in floats.
Decimal does base-10 arithmetic, the kind money is actually denominated in:
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2'))
Decimal('0.3')
Note the quotes. Decimal(0.1), no quotes, builds the decimal out of the binary float, and you already saw what that contains. Feeding Decimal a float is mopping the floor with the muddy water. Strings in, exact decimals out.
JavaScript gets no separate treatment here, because there's nothing to treat. Its numbers are the same doubles:
console.log(0.1 + 0.2 === 0.3);
false
Same standard, same costume, same party.
The part nobody puts on the poster
Floats are not a scam. For graphics, physics, machine learning, anything where the input was an approximation to begin with, a double is fast, hardware-supported, and accurate far beyond what the measurement deserved. The honest catch is that Decimal is slower and grumpier to write, and reaching for it everywhere is its own kind of cargo cult. The sin was never using floats. The sin is using them in the two places they're guaranteed to embarrass you: equality checks and cash.
Every senior dev I've watched hit this has the same story shape. The demo worked. The tests worked. Then the system ran a few million operations, the errors held hands, and a total came out one cent or one vote or one millisecond wrong, and somebody got to spend a Friday finding out why.
Where did floating point last bite you? I want the specific version, the one with the Friday in it.
P.S. Everything I write stays free — that part doesn't change. But if this one was worth something to you, you can buy me a coffee ☕ (one-time or monthly, both work). It covers the API credits and hosting behind these experiments — and honestly, it's also the nudge that gets the next one started on the evenings I'd otherwise skip it. — Salam

Top comments (0)