When 0.1 + 0.2 Doesn't Equal 0.3: The Floating-Point Bug That's Already in Your Code
Photo: Adam majewski, CC BY-SA 4.0, via Wikimedia Commons
Open up a JavaScript console — or a Python shell, or a Ruby REPL, honestly it doesn't matter much — and type 0.1 + 0.2. Go ahead, I'll wait.
You get 0.30000000000000004. Not 0.3. Not even close enough to round off without thinking about it. Just a quietly wrong answer sitting there, looking completely normal, ready to propagate through your entire application before anyone notices.
This isn't a language bug. It's not a runtime quirk. It's the IEEE 754 floating-point standard doing exactly what it was designed to do — and that's kind of the terrifying part.
Why Your Computer Literally Cannot Write Down 0.1
Here's the thing most CS curricula gloss over: computers store numbers in binary. And just like how one-third can't be written as a finite decimal (0.3333... forever), a lot of perfectly normal decimal fractions can't be represented as finite binary fractions either.
0.1 in binary is 0.0001100110011001100... repeating infinitely. So your CPU stores the closest approximation it can fit in 64 bits, which is almost 0.1 but not exactly. Same goes for 0.2. When you add two approximations together, you get a third approximation — and the rounding errors compound.
This is a primary error in the most literal sense: a mistake baked in at the representation layer, long before your application logic ever runs.
The IEEE 754 standard, which governs how virtually every modern processor handles floating-point math, was actually a massive improvement over the chaos that existed before it. Before standardization in 1985, the same calculation could produce different results on different hardware. IEEE 754 fixed that. Now you get the same wrong answer everywhere. Progress.
The Disasters That Made History
You might be thinking: okay, a rounding error at the 16th decimal place, who cares? Your users aren't going to notice.
Tell that to the US Army.
In 1991, during the Gulf War, a Patriot missile defense battery in Dhahran, Saudi Arabia failed to intercept an incoming Scud missile. The system's internal clock tracked time as an integer count of tenths of a second, then converted it to a floating-point number for targeting calculations. After the battery had been running continuously for about 100 hours, the accumulated floating-point error in the time calculation had drifted by about 0.34 seconds. That's enough to throw off the targeting range by more than half a kilometer. The Scud hit a barracks. Twenty-eight soldiers were killed.
A rounding error. In a time conversion. Running long enough to matter.
On the less catastrophic but still deeply embarrassing end of the spectrum, there's the Vancouver Stock Exchange. In 1982, the exchange launched a new index starting at 1000.000. By November 1983, the index stood at 524.811 — which seemed like a brutal bear market, except the actual market was doing fine. The problem was that the index was being recalculated thousands of times per day, and each calculation truncated the result to three decimal places rather than rounding it. Those tiny truncation errors accumulated. The index was off by nearly 50%. When they fixed the calculation method and restarted from the correct value, the index jumped to 1098.892 overnight.
It Gets Worse When Money Is Involved
Floating-point and financial calculations are a combination that should make any senior engineer nervous. The problem isn't just precision — it's that money is fundamentally a decimal system, and floating-point is fundamentally binary.
Consider a system processing a million transactions per day, each involving a calculation like splitting a dollar amount across multiple accounts. Each split might be off by a fraction of a cent. Across a million transactions, those fractions add up to real dollars. In regulated industries, those discrepancies have to be explained and reconciled. Sometimes they can't be. Sometimes they result in fines.
The standard solution in financial software is to avoid floating-point entirely for monetary values. Use integer arithmetic denominated in the smallest currency unit (cents, not dollars). Or use a Decimal type — Python's decimal module, Java's BigDecimal, .NET's decimal type — that performs arithmetic in base 10 rather than base 2. These approaches are slower and require more care, but they give you exact decimal representations of exact decimal values.
If you're seeing floats in a financial codebase and nobody has left a comment explaining why that's intentional and safe, treat it as a bug until proven otherwise.
Detecting the Damage Before It Ships
The nastiest thing about floating-point errors is how invisible they are. Your code runs, it returns a number, the number looks plausible, and the test passes — because your test probably asserts result == 0.3 and you haven't shipped yet so nobody's caught the fencepost.
A few practical habits that help:
Never compare floats for equality. Instead of if (a == b), use an epsilon comparison: if (Math.abs(a - b) < 1e-9). The tricky part is choosing the right epsilon for your domain — financial calculations need a different threshold than scientific simulations.
Log intermediate values during development. Floating-point errors compound, which means they're often traceable if you can see where the drift started. If your final result looks wrong, print the intermediate steps and find where it first went sideways.
Use property-based testing for numeric code. Tools like Hypothesis (Python) or fast-check (JavaScript) will throw randomized inputs at your functions and surface edge cases you'd never write by hand. Numeric edge cases — very large values, very small values, values near representational boundaries — are exactly where floating-point misbehaves.
Understand your language's built-in tools. Python's math.isclose() handles approximate equality comparisons correctly. Most languages have something similar. Use them.
The Part Nobody Likes to Hear
There's no clean fix for floating-point precision because the limitation is architectural, not incidental. You can work around it — use integer arithmetic, use decimal types, use arbitrary-precision libraries — but each workaround has costs in performance, complexity, or both.
What you can do is make the tradeoffs consciously. Know when floating-point is fine (physics simulations where approximate is acceptable, graphics rendering, machine learning where you're averaging across millions of samples anyway) and know when it isn't (anything involving money, anything that accumulates errors over time, anything where equality comparisons matter).
The Patriot missile battery had been running for 100 hours when the error became fatal. The Vancouver Stock Exchange index drifted for over a year before anyone figured out why it was wrong. These aren't exotic failure modes — they're what happens when floating-point errors run long enough in systems where they were never supposed to matter.
Your code has floats in it right now. Some of them are fine. Some of them are quietly accumulating toward a number that's going to be very hard to explain to someone who doesn't know what IEEE 754 is.
Might be worth finding out which is which before that conversation happens.