IEEE 754 and the Languages That Escape It
Why 0.1 + 0.2 has been quietly breaking demos, tests, and financial reports for forty years, no matter which language you happen to be holding.
A look at the standard almost every language inherited, and the few that opted out
Somewhere on your team right now, there is probably a comment in the codebase that says something like // rounding, don't ask. I have written that comment myself, more than once, before I actually understood what was happening underneath it. The short version is that it was never a bug in your code. It is a decision made in 1985, baked into almost every processor built since, and it shows up the moment you ask a computer to add two decimal fractions that look perfectly ordinary to a human.
The bug that is not really a bug
Open an interpreter in nearly any mainstream language and type 0.1 + 0.2. You will not get 0.3. You will get something like 0.30000000000000004, and the exact tail of extra digits does not depend on which language you picked, because they are almost all doing the same thing underneath: representing numbers using the IEEE 754 double precision floating point standard.
| Language | Expression | Printed result |
|---|---|---|
| Python 3 | 0.1 + 0.2 | 0.30000000000000004 |
| JavaScript (Node) | 0.1 + 0.2 | 0.30000000000000004 |
Java double | 0.1 + 0.2 | 0.30000000000000004 |
Every one of those results was checked directly for this article, not copied from someone else’s blog post, and they line up exactly because Python, JavaScript, and Java all lean on the same 64-bit IEEE 754 format for their default number type. C, C++, C#, Go, Rust, Swift, and Kotlin all do the same thing by default. This is not a language design flaw so much as a shared inheritance.
Why binary cannot hold one tenth
Here is the part that actually explains it, and it is simpler than it sounds. A finite binary fraction can only exactly represent numbers built from sums of powers of two: one half, one quarter, one eighth, and so on. Decimal fractions do not map cleanly onto that system unless the denominator itself is a power of two. One tenth is not, so 0.1 in binary is a repeating fraction that never terminates, the same way one third never terminates in decimal. The computer has to cut it off somewhere, and that cutoff is where the tiny error creeps in.
How far off the stored value actually is

On its own, an error of five billionths of a billionth would never matter to anyone. The trouble starts when you add numbers like this together, over and over, inside a loop.
The error does not stay small
I ran a simple test: add 0.1 to a running total, one thousand times, using ordinary double precision arithmetic, and compare the result against the exact answer of 100. The gap does not stay flat. It grows by roughly two orders of magnitude every time the number of additions grows by ten times.
Accumulated error after repeated addition of 0.1

This is exactly the shape of bug that survives code review, passes every unit test written with round numbers, and then shows up months later as a one cent discrepancy in a financial reconciliation report that nobody can explain without knowing this history.
The escape hatches languages actually offer
The good news is that this problem was solved decades ago, just not by fixing binary floating point itself. Instead, several languages ship an entirely separate number type that stores decimal digits directly instead of approximating them in binary.
Java’s answer is java.math.BigDecimal, part of the standard library since Java 1.1. It stores an arbitrary precision integer alongside a scale, so it can represent 0.1 exactly rather than approximately. There is a well known trap here worth calling out directly, because I have seen it catch experienced developers: constructing a BigDecimal from a double inherits that double’s existing error, while constructing it from a String avoids the problem entirely.
import java.math.BigDecimal;
BigDecimal correct = new BigDecimal("0.1").add(new BigDecimal("0.2"));
System.out.println(correct); // 0.3
BigDecimal trap = new BigDecimal(0.1);
System.out.println(trap); // 0.1000000000000000055511151231257827021181583404541015625
Python takes the same underlying idea and puts it in the standard library’s decimal module. Like BigDecimal, it wants strings rather than floats as input, for exactly the same reason.
from decimal import Decimal
result = Decimal("0.1") + Decimal("0.2")
print(result) # 0.3
C# goes a step further and builds a 128-bit decimal type directly into the language itself, spelled decimal, no import required, with roughly 28 to 29 significant digits of precision. It is the closest thing to a first-class citizen that this problem gets in any mainstream language.
Who has a built-in fix, and who does not
| Language | Exact decimal option | Where it lives |
|---|---|---|
| Java | Yes | java.math.BigDecimal, standard library |
| Python | Yes | decimal.Decimal, standard library |
| C# | Yes | native decimal keyword, built into the language itself |
| JavaScript | No | third-party libraries, a decimal proposal is still in draft |
| C and C++ | No | external libraries such as GMP or mpdecimal |
| Go | No | math/big for arbitrary precision, or a third-party module for decimal specifically |
| Rust | No | community crates such as rust_decimal, nothing in the standard library |
Having a built-in escape hatch does not mean every project needs it. BigDecimal and Decimal are slower than native floating point and more verbose to work with. The right rule of thumb is to reach for them specifically when you are handling money, or anything else where an exact decimal value has legal or contractual meaning, and to stick with ordinary floats everywhere else.
What to actually do about it
None of this means floating point is broken or that you need to rewrite anything that currently works. It means knowing where the sharp edge is. Never compare floating point values with a plain equality check, since 0.1 plus 0.2 will simply never equal 0.3 bit for bit. For scientific or graphics code, compare against a small tolerance instead. For money, invoicing, or anything a regulator might ask you to reconcile to the cent, reach for your language’s decimal type from the very first line of code, not as a fix applied after something goes wrong in production.
What we have learned
0.1 plus 0.2 does not equal 0.3 in almost any mainstream language, and that is not a flaw in any one of them, it is a shared consequence of the IEEE 754 binary floating point standard that most languages adopted for good reasons. Binary fractions simply cannot represent most decimal fractions exactly, the resulting error is tiny on its own but grows the more arithmetic you stack on top of it, and a handful of languages solved this decades ago with dedicated decimal types such as Java’s BigDecimal, Python’s Decimal, and C#’s native decimal keyword. Languages without a built-in answer, including JavaScript, C, C++, Go, and Rust, lean on external libraries instead. The fix is not avoiding floating point altogether, it is knowing exactly when to reach for its exact-decimal alternative.
Notes on this article
- All numeric outputs shown (Python, Node.js, and Java results, representation error values, and the accumulated error series) were computed and verified directly while writing this piece, not sourced secondhand.
- Wikipedia, IEEE 754
- Oracle Java documentation, BigDecimal
- Python documentation, decimal module
- Microsoft Learn, C# floating-point and decimal numeric types


