Floating Point Precision Errors: Why 0.1 + 0.2 ≠ 0.3

9 min2026年5月22日

The Floating Point Precision Error Everyone Hits

Open any programming language and type 0.1 + 0.2. You'll get 0.30000000000000004. Not 0.3. This isn't a bug in JavaScript, Python, Java, C++, or Rust — it's a fundamental consequence of how computers represent decimal fractions in binary. The floating point precision errors you see are not mistakes; they're the mathematically correct result of binary arithmetic on approximated values.

The reason: 0.1 in decimal is a repeating fraction in binary (0.0001100110011...), just like 1/3 is repeating in decimal (0.333...). A 64-bit float can only store 53 significant bits, so 0.1 gets rounded to the nearest representable value: 0.1000000000000000055511151231257827021181583404541015625. When you add two of these approximations together, the rounding errors don't cancel — they accumulate.

This affects every language that uses IEEE 754 floating point (which is all of them for hardware-accelerated math). Python, JavaScript, C, Java, Go, Rust — all produce the same result because they all use the same 64-bit double-precision format. The only languages that avoid this by default are those using arbitrary-precision arithmetic (like Haskell's Rational type or Python's Decimal module), which trade speed for exactness.

How IEEE 754 Represents Numbers

IEEE 754 double-precision (64-bit) floats store numbers as: 1 sign bit, 11 exponent bits, and 52 mantissa bits (plus 1 implicit leading bit = 53 bits of precision). The value is: (-1)^sign × 2^(exponent-1023) × 1.mantissa. This gives you about 15-17 significant decimal digits of precision and a range from ±5×10^-324 to ±1.8×10^308.

The 53 bits of mantissa mean you can represent integers exactly up to 2^53 (9,007,199,254,740,992). Beyond that, consecutive representable numbers are more than 1 apart. In JavaScript: 9007199254740992 + 1 === 9007199254740992 evaluates to true. The number literally can't be represented. This is why JavaScript has BigInt and why database IDs above 2^53 must be transmitted as strings in JSON.

Between any two representable floats, there are infinitely many real numbers that can't be represented. The gap between representable numbers varies — near zero, consecutive floats are about 5×10^-324 apart. Near 1.0, they're about 1.1×10^-16 apart. Near 1,000,000, they're about 1.2×10^-10 apart. This means relative precision is roughly constant (~15 digits), but absolute precision decreases as numbers get larger.

Special values: +0 and -0 (yes, negative zero exists and -0 === +0 in JavaScript), Infinity and -Infinity (result of overflow or division by zero), and NaN (Not a Number — result of 0/0, sqrt(-1), or invalid operations). NaN has the bizarre property that NaN !== NaN — it's the only value in IEEE 754 that's not equal to itself. Use Number.isNaN() or isNaN() to check for it.

// The classic floating point precision errors
0.1 + 0.2                    // 0.30000000000000004
0.1 + 0.2 === 0.3            // false
1.0 - 0.9                    // 0.09999999999999998
0.1 * 0.2                    // 0.020000000000000004

// Large integer precision loss
9007199254740992 + 1         // 9007199254740992 (unchanged!)
9007199254740993 === 9007199254740992  // true (!)

// Special values
1 / 0                        // Infinity
-1 / 0                       // -Infinity
0 / 0                        // NaN
NaN === NaN                  // false (!)
Number.isNaN(NaN)            // true

// The epsilon approach for comparison
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON  // true
// But EPSILON is too small for accumulated errors:
Math.abs(0.1 + 0.2 + 0.3 - 0.6) < Number.EPSILON  // false!

Floating Point and Money (Don't Use Floats)

Never use floating point for financial calculations. $0.10 + $0.20 should equal $0.30, not $0.30000000000000004. Over thousands of transactions, these tiny errors accumulate into real discrepancies. A bank processing 10 million transactions per day with rounding errors of ±0.000000000000001 per transaction could drift by $0.01 per day — which triggers audit failures and regulatory issues.

The standard solution: store money as integers in the smallest unit (cents, pence, yen). $19.99 becomes 1999 cents. All arithmetic is exact integer math. Only convert to decimal for display. This is how Stripe, PayPal, and every serious payment system works. In JavaScript: const total = priceInCents * quantity; const display = (total / 100).toFixed(2).

For currencies with sub-cent precision (cryptocurrency, forex), use a fixed-point library. JavaScript has no built-in decimal type, but libraries like decimal.js, big.js, or dinero.js handle arbitrary-precision decimal arithmetic. Python has the decimal module (from decimal import Decimal; Decimal("0.1") + Decimal("0.2") == Decimal("0.3") is True). Java has BigDecimal. Use these for any calculation where exact decimal results matter.

A real-world example: in 2012, Knight Capital lost $440 million in 45 minutes due to a software bug. While not strictly a floating-point error, it illustrates how small computational errors in financial systems cascade catastrophically. The lesson: financial code needs exact arithmetic, extensive testing, and fail-safes. Our percentage-calculator uses integer math internally to avoid these issues when computing tax rates and discounts.

Comparing Floating Point Numbers

Never use === (or ==) to compare floating point results. Instead, check if the difference is smaller than a tolerance: Math.abs(a - b) < epsilon. But what should epsilon be? Number.EPSILON (2.2×10^-16) is the smallest difference between 1.0 and the next representable float — it's appropriate for comparing numbers near 1.0 but too small for larger numbers and too large for numbers near zero.

A better approach: relative epsilon. Compare the difference to the magnitude of the numbers: Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b)). This scales the tolerance with the size of the values. For most applications, a relative epsilon of 1e-9 to 1e-12 works well. For accumulated calculations (summing thousands of values), you might need 1e-6.

The ULP (Unit in the Last Place) approach: two floats are "equal" if they differ by at most N ULPs. One ULP is the distance between a float and its nearest neighbor. This is the most mathematically rigorous comparison but harder to implement. In practice, relative epsilon comparison covers 99% of use cases.

For sorting and deduplication, floating point comparison is especially tricky. If you're removing "duplicate" values from a list of measurements, you need a consistent equivalence relation (if a≈b and b≈c, then a≈c). Simple epsilon comparison doesn't guarantee this transitivity. Consider rounding to a fixed number of significant digits before comparison, or using integer representations.

Scientific Computing: When Precision Matters Most

Catastrophic cancellation: subtracting two nearly-equal numbers loses most of the significant digits. If a = 1.0000001 and b = 1.0000000 (both stored with 15 digits of precision), a - b = 0.0000001 has only 1 digit of precision — the other 14 digits are noise. This happens in quadratic formula calculations, numerical derivatives, and any algorithm that computes small differences of large numbers.

The fix for the quadratic formula: instead of x = (-b ± sqrt(b²-4ac)) / 2a, use the numerically stable form. Compute one root with the standard formula (choosing the sign that avoids cancellation), then use x1 * x2 = c/a to get the other root. This avoids the catastrophic cancellation that occurs when b² >> 4ac. Our scientific-calculator uses this stabilized form internally.

Summation order matters. Adding 1e-16 to 1.0 gives 1.0 (the small value is below the precision threshold). But adding 1e-16 a trillion times should give 0.0001. Naive summation loses these small contributions entirely. Kahan summation (also called compensated summation) tracks the rounding error and corrects for it, giving results accurate to the full precision of the float. Use it whenever you're summing many values of varying magnitude.

Interval arithmetic: instead of computing a single float, track the range [lower, upper] that contains the true result. After each operation, the interval widens to account for rounding. If your final interval is [2.99999, 3.00001], you know the true answer is 3.0 ± 0.00001. This gives you a rigorous error bound, unlike single-float computation where you're guessing how much error accumulated. Libraries like MPFI (C) and pyinterval (Python) implement this.

Language-Specific Gotchas

JavaScript: All numbers are 64-bit doubles (no integer type until BigInt). This means integer arithmetic is exact only up to 2^53. JSON.parse() of large integers silently loses precision: JSON.parse("9007199254740993") returns 9007199254740992. APIs returning large IDs (Twitter, Discord) must send them as strings. Use BigInt for exact integer arithmetic beyond 2^53.

Python: float is 64-bit double. But Python also has Decimal (arbitrary precision decimal) and Fraction (exact rational arithmetic). For financial code: from decimal import Decimal, ROUND_HALF_UP. For scientific code: numpy uses 64-bit doubles by default but supports float128 on some platforms. Python's // operator does floor division, which interacts with negative numbers differently than truncation.

C/C++: float is 32-bit (7 digits precision), double is 64-bit (15 digits), long double is 80-bit or 128-bit depending on platform. The x87 FPU uses 80-bit extended precision internally, which means the same code can give different results depending on whether intermediate values stay in registers (80-bit) or get stored to memory (64-bit). Use -ffp-contract=off and -fno-fast-math for reproducible results.

SQL: FLOAT and DOUBLE are IEEE 754 types with the same precision issues. DECIMAL(p,s) and NUMERIC(p,s) are exact fixed-point types — use these for money. PostgreSQL's NUMERIC can store up to 131,072 digits before the decimal point and 16,383 after. MySQL's DECIMAL supports up to 65 digits total. Always use DECIMAL for financial columns, never FLOAT or DOUBLE.

Practical Rules for Avoiding Floating Point Bugs

Rule 1: Use integers for money. Store cents, not dollars. Store satoshis, not bitcoin. Convert to decimal only for display. This eliminates 90% of floating-point bugs in business applications.

Rule 2: Never compare floats with ===. Use a tolerance-based comparison appropriate for your domain. For UI coordinates, 0.01 pixel tolerance is fine. For scientific calculations, use relative epsilon. For financial calculations, you shouldn't be using floats at all (see Rule 1).

Rule 3: Be suspicious of subtraction. If you're subtracting two numbers that might be close in value, you're at risk of catastrophic cancellation. Reformulate the algorithm to avoid the subtraction, or use higher precision for that specific calculation. The quadratic formula, numerical derivatives, and variance calculations are classic examples.

Rule 4: Test with adversarial inputs. Values near zero, very large values, values that are exact powers of 2 (representable exactly) vs values that aren't (0.1, 0.3, 0.7). Sums of many small values. Differences of nearly-equal values. If your code handles money, test with $0.01, $0.10, $99.99, and $999,999,999.99. Our basic-calculator tool is designed to handle these edge cases correctly by using appropriate internal representations.