Quick Answer

Computers store most non-integer numbers in binary floating point (IEEE 754), and fractions like 0.1 have no exact binary form, the same way one third has no exact decimal form. Each literal is rounded to the nearest representable value, and those tiny errors add up. So 0.1 + 0.2 evaluates to 0.30000000000000004, and you should never compare floating-point results with a plain equality check.

The demo, in two languages

Node:

> 0.1 + 0.2
0.30000000000000004
> 0.1 + 0.2 === 0.3
false
> 0.3 - 0.1
0.19999999999999998
> (0.1 + 0.2).toFixed(2)
'0.30'

Python:

>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
>>> from decimal import Decimal
>>> Decimal('0.1') + Decimal('0.2')
Decimal('0.3')

Same answer. The same holds in Java, C, C#, Go, Ruby, and Swift, because they all use IEEE 754 double-precision for this. It is not a quirk of one runtime; it is the arithmetic the hardware does.

The result 0.30000000000000004 is the closest double to the true sum of the two stored values. A calculator or spreadsheet seems to get 0.3 only because it rounds for display before you see it; the bits underneath are still off. That rounding hides the problem in output but not in logic: an if (total === 0.3) check fails, a loop that adds 0.1 until it reaches 1.0 overshoots, and a running total of many values drifts further with every addition. The rest of this post is about avoiding those three traps.

Why binary cannot hold 0.1

In base 10, one third is 0.3333..., a repeating decimal you have to cut off somewhere. In base 2, 0.1 is the same story: 0.0001100110011... repeating forever. A double-precision float has only 52 bits for the fraction, so the runtime rounds that infinite pattern to the nearest 52-bit value the moment it reads the literal 0.1 in your source.

That stored value is not 0.1. It is very slightly more:

> (0.1).toPrecision(20)
'0.10000000000000000555'

Python agrees: Decimal(0.1) prints 0.1000000000000000055511151231257827021181583404541015625, the exact value the bits represent. So before you have added anything, 0.1 and 0.2 are each a hair off. Add two slightly-wrong numbers and the error surfaces in the 17th digit as 0.30000000000000004. Nothing rounded incorrectly; the inputs were never the values you typed.

Never compare floats with ==

Because results carry rounding noise, exact equality is unreliable. Compare within a tolerance instead:

const equalish = (a, b) =>
  Math.abs(a - b) <= Number.EPSILON * Math.max(1, Math.abs(a), Math.abs(b));

equalish(0.1 + 0.2, 0.3);   // true

Number.EPSILON is about 2.22e-16, the gap between 1 and the next representable double. A bare Math.abs(a - b) < Number.EPSILON check only works for values near 1; for larger magnitudes you must scale the tolerance by the operands, as above, or pick an absolute tolerance that suits your domain.

Python ships this as math.isclose(0.1 + 0.2, 0.3), which returns True using a relative tolerance by default. Prefer it over hand-rolling the comparison.

The same rule kills loop counters. for (let x = 0; x !== 1; x += 0.1) never stops: after ten additions x is 0.9999999999999999, then 1.0999..., and it skips 1 entirely. Count with an integer and divide: for (let i = 0; i <= 10; i++) { const x = i / 10; }.

Rounding, and why money is special

toFixed and round operate on the already-inexact binary value, so they surprise you:

> (1.005).toFixed(2)
'1.00'   // not '1.01'

1.005 is stored as 1.00499999999999989..., which is below the halfway point, so it rounds down. There is no way to fix this at the rounding step; the information was lost when the literal was parsed.

Accumulation compounds it. Adding 0.1 a thousand times gives 99.9999999999986, not 100, and 0.07 * 100 is 7.000000000000001. In a billing system, those sub-cent errors turn into reconciliation mismatches at month end.

For currency, do not use floating point at all. Store amounts as integers in the smallest unit: 1999 paise, not 19.99 rupees. Add, subtract, and apply tax on the integer, and divide by 100 only to display. Integers up to 2^53 are exact, so this arithmetic never drifts. Database columns for money should be DECIMAL or NUMERIC, never FLOAT.

Large integers break too

JavaScript numbers are doubles, so whole numbers above 2^53 lose precision:

> Number.MAX_SAFE_INTEGER
9007199254740991
> 9007199254740993 === 9007199254740992
true

Two different integers compare as equal because neither can be represented and both round to the same value. This bites when a 64-bit database ID or a nanosecond timestamp arrives as JSON: JSON.parse silently mangles it. Keep such values as strings, or use BigInt (9007199254740993n), which is exact but does not mix with regular numbers in arithmetic.

Python integers are arbitrary precision, so 9007199254740993 == 9007199254740992 is False there. But Python floats are still IEEE 754 doubles: 9007199254740993.0 == 9007199254740992.0 is True, and int(9007199254740993.0) gives back 9007199254740992. The moment a value passes through a float, the same limit applies in every language, so an integer division, an average, or a JSON round-trip through a JavaScript service can all quietly corrupt a large ID.

When you need exact math

Reach for an exact representation whenever a human will check the number against an expectation:

  • Money and tax: integer minor units, or a decimal type (decimal.Decimal in Python, BigDecimal in Java, DECIMAL in SQL).
  • IDs and counters that can exceed 2^53: BigInt or strings.
  • Repeated accumulation: summing thousands of floats compounds the error; sum scaled integers, or use a compensated-summation routine.

Floating point is the right default for measurements, physics, graphics, and statistics, where inputs are approximate anyway and a relative error near 1e-16 is irrelevant. The mistake is using it for exact quantities that a person will verify.

One more habit: never let a float be a map key, a cache key, or part of an equality-based deduplication step, because two values that should match may not. Decide which kind of number you have before you pick the type, round only at the boundary where a human reads the value, and when in doubt, keep money in paise.

Frequently Asked Questions

Is 0.1 + 0.2 not equalling 0.3 a JavaScript bug? No. It is IEEE 754 double-precision arithmetic and produces the same result in Python, Java, C, Go, Ruby, and most other languages.
How should I compare two floating-point numbers? Check that the absolute difference is within a small tolerance scaled to the operands' magnitude. In Python use math.isclose; in JavaScript build a check around Number.EPSILON.
Why does toFixed sometimes round the wrong way? The input is already an inexact binary value. 1.005 is stored slightly below 1.005, so it rounds down to 1.00. Round from a scaled integer instead.
How do I store currency safely? As an integer count of the smallest unit, such as paise or cents, dividing only when displaying. In databases use a DECIMAL column, never FLOAT.
When should I use BigInt? For integers that can exceed Number.MAX_SAFE_INTEGER (2^53 minus 1), such as 64-bit database IDs, large counters, or high-resolution timestamps.