DEV Community

EvvyTools
EvvyTools

Posted on

Why 0.1 + 0.2 Doesn't Equal 0.3 (And Why That's Not a Bug)

Open a console in almost any mainstream programming language, type 0.1 + 0.2, and hit enter. Instead of 0.3, you'll get something like 0.30000000000000004. New programmers usually assume they found a bug. They didn't. They found one of the most well-documented properties of how computers represent decimal numbers, and it's worth actually understanding rather than just memorizing "floats are weird."

The Short Version

Computers store numbers in binary, base 2, not base 10. Some fractions that are simple to write in decimal, like 0.1 or 0.2, can't be represented exactly in binary any more than 1/3 can be written exactly as a finite decimal. The computer has to round to the nearest value it can actually store, and when you add two of those rounded values together, the tiny rounding errors don't always cancel out cleanly.

This isn't a flaw in any particular language. Python, JavaScript, Java, C, and nearly every mainstream language exhibit the exact same behavior, because nearly all of them use the same underlying standard for representing floating-point numbers.

The Standard Behind It

That standard is IEEE 754, which defines how floating-point numbers are stored and manipulated in binary. It's been the near-universal standard since the late 1980s, which is why the same 0.1 + 0.2 quirk shows up identically across languages that otherwise have almost nothing in common. If you want the deeper mechanics, Wikipedia's page on floating-point arithmetic covers exactly how the rounding works and why certain values round cleanly while others don't.

The intuition that helps most: think about how 1/3 can't be written exactly in decimal, only approximated as 0.333... with more or fewer digits. The same thing happens to 0.1 in binary, just with a different set of fractions that round cleanly versus don't.

Where It Actually Bites You

For most day-to-day arithmetic, this rounding error is far too small to matter, we're talking about differences in the 17th decimal place. It becomes a real problem in two specific situations. First, financial calculations, where summing many small floating-point values can accumulate visible rounding drift, which is why serious financial software typically uses fixed-point or decimal-specific types instead of raw floats. Second, equality comparisons, where code that checks if (a + b == 0.3) will silently fail even when the math is conceptually correct, because the stored value isn't exactly 0.3.

The standard fix for comparisons is checking whether two floats are within a small tolerance of each other, rather than checking for exact equality. For financial or precision-sensitive work, using a dedicated decimal type instead of a plain float avoids the problem at the source.

Checking Your Intuition Against a Calculator

If you want to see the effect without opening a code editor, most scientific calculators that show full precision will reveal similar rounding behavior, though the exact digits shown depend on how many decimal places the calculator displays. The free Scientific Calculator on EvvyTools is handy for spot-checking arithmetic like this alongside code, since it shows step-by-step evaluation rather than just a final rounded number, which makes it easier to see where a discrepancy actually originates.

This kind of quiet, well-documented weirdness in how tools represent numbers isn't limited to floating point. There's a broader piece on why two calculators can give different answers to the same expression that covers order-of-operations ambiguity and angle-mode errors, which come from a related family of "the notation looked unambiguous but wasn't" problems.

The Takeaway

0.1 + 0.2 not equaling 0.3 isn't a language quirk to work around case by case, it's a fundamental property of binary floating-point representation that every language built on IEEE 754 shares. Once you know it's coming, it stops being a mystery and becomes something you design around: tolerance-based comparisons for general math, decimal types for money, and a healthy skepticism toward exact equality checks on any value that started life as a float.

Which Numbers Actually Round Cleanly

It's not that every decimal fraction has this problem, some binary fractions line up perfectly. 0.5, 0.25, and 0.125 all convert to binary exactly, because they're each a sum of clean negative powers of two (1/2, 1/4, 1/8). The numbers that cause trouble are the ones that, like 1/3 in decimal, require an infinitely repeating binary fraction to represent exactly. 0.1 happens to be one of those, along with 0.2, 0.3, and most decimal fractions you'd type casually.

This is why some floating-point arithmetic looks perfectly clean and other arithmetic immediately shows rounding artifacts: it depends entirely on whether the specific values involved happen to be expressible exactly in binary, not on how "simple" the number looks when written in decimal.

Practical Patterns for Working Around It

For general-purpose comparisons, the standard pattern is checking whether the absolute difference between two floats is smaller than some small tolerance value, often called epsilon, rather than checking for exact equality. Something like abs(a - b) < 0.0001 instead of a == b. The right tolerance depends on the scale of the numbers involved and how much precision your use case actually needs.

For money specifically, the more robust fix is avoiding floating point entirely. Store amounts as integer cents instead of dollar floats, or use a language's dedicated decimal type, which represents numbers in base 10 internally instead of base 2, avoiding the rounding problem at the source rather than working around it after the fact. Python's decimal module, Java's BigDecimal, and similar libraries in other languages exist specifically for this.

A Quick Way to See It Yourself

If you want to see the underlying representation rather than just the rounded display, most languages let you print a float with extra decimal places to reveal the stored value rather than the friendly rounded one you normally see. Doing this once or twice is a good way to build lasting intuition for when floating point is safe to trust at face value and when it needs a tolerance check instead. It's a five-minute exercise that saves a lot of confused debugging later, especially the first time a unit test fails on an equality check that "should" have passed.

The Underlying Lesson

This isn't really a story about a bug, it's a story about a tradeoff. IEEE 754 floating point trades exact decimal representation for speed and a wide dynamic range, and that tradeoff is the right one for the overwhelming majority of computing tasks, graphics, physics simulations, scientific computing, machine learning. The cases where it causes visible problems, financial math and naive equality checks, are narrow and well understood, which is exactly why dedicated decimal types exist as the standard fix rather than everyone reinventing tolerance-based comparisons from scratch every time.

A Common Follow-Up Question: Why Not Just Fix the Standard?

People new to this often ask why IEEE 754 doesn't just get updated to avoid the problem entirely. The honest answer is that there isn't a fix that doesn't trade away something else. Base-10 floating point formats do exist, and IEEE 754 actually includes decimal floating-point variants as part of the 2008 revision, but they're slower and use more memory than binary floating point for equivalent precision, which is why binary remains the default for general-purpose computing, graphics, and anything performance-sensitive. Base-2 arithmetic is what computer hardware does natively and efficiently, base-10 arithmetic in binary hardware requires extra conversion work every single operation.

So the "fix" already exists, in the form of decimal types, it's just opt-in rather than the default, because the default needs to prioritize speed and memory efficiency for the vast majority of use cases where a few billionths of rounding error genuinely doesn't matter.

Testing Yourself: A Short Exercise

If you want to build real intuition for this rather than just remembering the fact that 0.1 + 0.2 doesn't equal 0.3, try predicting the result of a few similar expressions before running them: 0.1 + 0.1, 0.2 + 0.2, 1.1 - 1.0. Some of these round cleanly, some don't, and there's no shortcut to knowing which without either doing the binary conversion yourself or just running the code and observing. After a few rounds of this, you'll start developing a rough sense for which decimal values are "safe" and which aren't, which is a genuinely useful skill for catching floating-point bugs before they cause a confusing test failure down the line.

Where This Knowledge Actually Pays Off

The single most common place this bites working developers isn't exotic scientific computing, it's writing a unit test that asserts an exact floating-point value and watching it fail intermittently or on a different machine architecture, for reasons that have nothing to do with the logic being tested. Recognizing that pattern immediately, instead of spending twenty minutes debugging application logic that was never actually broken, is the practical payoff of understanding what's covered here. It's a small piece of knowledge, but it's one of those things that separates a five-minute fix from a genuinely frustrating debugging session.

If you want a deeper reference on the standard itself, the Wikipedia entry on IEEE 754 links out to the formal specification and covers the different precision levels (single, double, and beyond) that most languages expose as float and double types, which is useful background if you ever need to reason about precision limits rather than just the existence of rounding error.

Top comments (0)