All posts

#software bugs#floating point#javascript

Why Can't Computers Add 0.1 + 0.2? The Hidden World of Floating Point

Type 0.1 + 0.2 into your browser console: the result is 0.30000000000000004. It's not a bug; it's how computers store numbers. Why it happens, how it cost lives in a missile defense system, and how to protect your money.

Why Can't Computers Add 0.1 + 0.2? The Hidden World of Floating Point
Contents 9

Press F12 in your browser, open the console and type:

0.1 + 0.2
// 0.30000000000000004

A computer doing billions of operations a second gets a primary-school sum wrong. And it's not just JavaScript: Python, Java, C#, C++ and Excel under the hood produce the same result. It isn't a bug; it's an unavoidable consequence of how computers store numbers. And this "tiny" difference has cost lives.

In short:

  • Computers store numbers in binary (base 2). In binary, 0.1 repeats forever, just like 1/3 does in decimal.
  • Infinite digits don't fit in a finite box, so the number is rounded; 0.1 is actually stored as 0.1000000000000000055...
  • In 1991, a similar rounding error in the Patriot missile defense system led to a failed interception that killed 28 soldiers.
  • Never use floating-point numbers (float/double) for money; use integers in the smallest unit or a decimal type.

Try writing 1/3 as a decimal

The easiest way to understand the problem is a familiar example. Write 1/3 as a decimal: 0.3333333... However many digits you write, it never ends. You have to stop somewhere and round. Add three 0.3333s and you get 0.9999, not 1.

In base 10, a fraction has a finite decimal form only if its denominator is made of the factors 2 and 5 (10 = 2 × 5). 1/4 = 0.25 is finite; 1/3 is infinite.

Computers work in binary, whose only base factor is 2. There, a fraction is finite only if its denominator is a power of 2. 0.5 (1/2), 0.25 (1/4) and 0.125 (1/8) are fine. But 0.1 = 1/10, and 10 contains a 5. So:

0.1 (decimal) = 0.0001100110011001100110011... (binary)

The "0011" pattern repeats forever. To a computer, 0.1 is what 1/3 is to us.

What is floating point?

Computers usually store decimal numbers according to a standard called IEEE 754. The most common format, the "double" (double precision), uses 64 bits:

Part Bits Role
Sign 1 Positive or negative
Exponent 11 Where the point sits
Fraction (mantissa) 52 Significant digits

That's where the name comes from: the point isn't fixed, it "floats" with the size of the number. The same 64 bits can express both the size of atoms and distances between galaxies. The trade-off is that every number is kept to about 15-17 significant digits.

The 52-bit fraction cuts off 0.1's infinite binary expansion and rounds it. The "0.1" in a computer's memory is exactly:

0.1000000000000000055511151231257827021181583404541015625

You can see it yourself in Python:

from decimal import Decimal
print(Decimal(0.1))
# 0.1000000000000000055511151231257827021181583404541015625

0.2 is also stored slightly too large. When you add them, the small errors combine and the result lands one step past the closest storable version of 0.3: 0.30000000000000004.

Then why do we see 0.1 on screen?

If 0.1 is really 0.1000000000000000055..., why does console.log(0.1) show a clean "0.1"? Because when printing, languages pick the shortest decimal representation that converts back to the same stored value. The shortest number that maps to 0.1's stored value is "0.1", so that's what we see. The sum lands on a value different from 0.3's stored value, so the language can't print it as 0.3 and shows all the digits.

This tiny error cost lives

Floating-point errors aren't always harmless. In 1991, during the Gulf War, a Patriot missile defense battery couldn't represent a tenth of a second (1/10) exactly in binary, so every time measurement carried a tiny error. After about 100 hours of continuous operation the error reached 0.34 seconds, and an incoming Scud missile wasn't intercepted; 28 soldiers were killed. We told the full story in the most expensive software bugs in history. What matters here is that the root cause was exactly what this post is about: the infinite binary expansion of 0.1.

How a stock index lost half its value

A less tragic but very instructive example: in 1982 the Vancouver Stock Exchange launched a new index at 1,000. It was recalculated after every trade, and the result was truncated instead of rounded to three decimal places. Those tiny cuts, repeated thousands of times a day, always pushed the same way: down. About two years later the index stood around 520. When the calculation was fixed, the true value turned out to be above 1,000.

The lesson is clear: a rounding error that seems harmless on its own can change the result itself when repeated millions of times.

What should you do with money?

If you build e-commerce, accounting, banking or billing software, the rule is simple: never store money as a float or double. There are three safe approaches:

1. Use integers in the smallest unit. Store $12.50 as 1250 cents. Addition and subtraction with integers are exact. Divide by 100 only when displaying.

2. Use a decimal type. Most languages offer a type that calculates in base 10:

// Java: always build BigDecimal from a string
new BigDecimal("0.1").add(new BigDecimal("0.2")); // 0.3

// Wrong: building from a double carries the error in
new BigDecimal(0.1); // 0.1000000000000000055511151231257827...
from decimal import Decimal
Decimal("0.1") + Decimal("0.2")  # Decimal('0.3')

In C#, the decimal type does the same job.

3. Pick the right database column type. Use NUMERIC(12,2) / DECIMAL for amounts, not FLOAT or DOUBLE.

Also watch the rounding rule: in calculations like sales tax, whether you round per line or on the total, and which rounding method you use, is a business rule. Don't leave it to whatever the code happens to do.

How should you compare decimals?

For scientific or graphics work, floating point is the right choice; it's unbeatable in speed and range. But never compare two floating-point numbers with ==. Check whether the difference is within a small tolerance instead:

Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true

Number.EPSILON is the difference between 1 and the smallest double greater than 1. With large numbers, scale the tolerance to their magnitude.

Frequently asked questions

Is this a JavaScript bug?

No. Every language using IEEE 754 gives the same result: Python, Java, C#, C, Go, Ruby and more. It's just more visible in JavaScript because all numbers are doubles by default.

Does Excel have this problem too?

Yes. Excel also stores numbers as IEEE 754 doubles and shows 15 significant digits. You may see unexpected tiny differences in some formulas; for precise work, use the ROUND function deliberately.

Why don't computers just use base 10?

They can, and decimal types do exactly that. But binary is much faster and more efficient in hardware. In scientific computing, graphics and AI, that speed is worth far more than tiny rounding differences.

Do games and AI use floating point too?

Yes. AI models often use even lower-precision 16- or 8-bit floating-point formats to save speed and memory. In those fields small errors don't change the outcome; unlike with money.

0.1 + 0.2 is one of the best examples of the gap between "looks right" and "is right" in software. If you're building systems that handle sensitive calculations like finance, e-commerce or billing, reach us through our enterprise software development page.

Sources

ShareLinkedInXWhatsApp
Need help with this?

If you would like to apply what this post covers to your own project, let’s look at it together.

Write to us
YE

Founder of EngerekTech. Builds web, mobile and enterprise software for businesses with Angular, Spring Boot and Flutter, and made the KPSS Düello and Kelime Kavanozu apps. On the blog he covers AI tools and software development as he uses them in his own projects.