Worked examples — Floating point gotchas — catastrophic cancellation, associativity failure
Before anything: three words we will reuse.
The scenario matrix
Every trouble on this topic falls into one of these cells. Each row is one worked example below.
| # | Case class | Trigger | What breaks | Fix family |
|---|---|---|---|---|
| A | Subtract near-equal positives | , both | catastrophic cancellation | algebraic reformulation |
| B | Quadratic, same-sign root | , take safe sign | fine (no fix needed) | — |
| C | Quadratic, opposite-sign root | the other root | cancellation in | |
| D | Degenerate: term rounds to zero | , | total loss (answer = 0) | trig identity |
| E | Absorption — small + huge | the small one vanishes | reorder / Kahan | |
| F | Associativity flip | grouping changes answer | cancel large terms first | |
| G | Summation order over a list | many tiny + one huge | error grows with | sort ascending / Kahan |
| H | Equality comparison | 0.1+0.2 == 0.3 |
False unexpectedly |
tolerance compare |
| I | Real-world word problem | GPS / distance difference | cancellation hides in disguise | reformulate the physics |
| J | Exam twist | "higher precision fixes it?" | conditioning ≠ precision | reformulate, not upgrade |
Example A — subtract near-equal positives
Steps.
-
True value. . Why this step? We need the honest target so we can measure error against it. The approximation comes from .
-
Naive compute. In doubles, exactly, and — but rounds to (the gap near is , so adding falls off the edge, cell E in disguise). So both square roots equal , and the subtraction gives . Why this step? Shows the double failure: absorption inside, then subtracting two identical numbers gives exactly .
-
Measure the damage. Relative error — 100% wrong. Why this step? Confirms "catastrophic": the answer is entirely lost.
-
Reformulate. Multiply top and bottom by the conjugate: Now compute . No subtraction of near-equals — we only add same-sign roots and divide. Why this step? Division and same-sign addition never amplify relative error (parent note, §2), so we route around the villain.
Verify: the reformulated value matches the true to full precision. Sanity: for large the gap between consecutive roots should shrink like — it does. ✓
Examples B & C — the two quadratic roots
We take one quadratic and extract both roots, because the two roots live in different matrix cells: one is safe (B), one cancels (C).
Steps.
-
Discriminant. , so . Why this step? Both roots share this square root; compute once.
-
Same-sign root (Cell B — safe). Since , the term is positive; the safe root adds two positives: Why this step? Adding two same-sign near-equal numbers does not cancel — no amplification. This root is trustworthy.
-
Opposite-sign root, naive (Cell C — catastrophic). Here the cancellation happens between two operands and . The parent note's amplifier, written for a generic subtraction , is (the letters in §2 of the parent are just placeholder operands — not the quadratic coefficients here). Plugging in: — precision annihilated. Why this step? Exposes the cancellation and its size, being careful not to confuse the operands with the coefficients .
-
Stable second root. Use (product of roots), so Why this step? We already have the safe root ; the product identity gives using only division — no subtraction of near-equals. See Quadratic formula numerical issues.
Verify: plug back: . Product check: . Sum check: . ✓
Example D — a term rounds to zero (degenerate)
Steps.
-
True value. Taylor: , so and . Why this step? Establishes the target .
-
What the machine stores. . But , so rounds to exactly . Why this step? The information that made nonzero lives below machine epsilon — it is gone before subtraction.
-
Naive result. , so . Relative error (total loss). Why this step? Confirms degeneracy: the answer collapses to zero.
-
Reformulate with a trig identity. , hence of a small argument is computed accurately (it's the argument, no cancellation), and , so . Why this step? We replaced "subtract near-equals" by " of a small number", which the library evaluates faithfully.
Verify: at , , so . Matches the limit. ✓
The figure below plots both the naive and reformulated versions across many . Read it like this: the dashed gray line is the true limit ; the green curve (reformulated) hugs it all the way down; the red curve (naive) rides along until , then falls off a cliff to the moment rounds to . That cliff is the catastrophic cancellation — the picture shows exactly where in the naive formula dies.

Example E — absorption (small + huge)
Steps.
-
Gap at . Near a value of magnitude , consecutive doubles differ by (the mantissa's last bit now weighs , not ). So has neighbours apart. Why this step? We must know the resolution at that magnitude — precision is relative, so big numbers have coarse spacing (see IEEE-754 floating point representation).
-
Add the 1. (odd) — but that's not representable; the nearest doubles are and . Rounding-to-nearest keeps . Why this step? The is smaller than half the gap, so it is absorbed — vanishes entirely.
-
Consequence. . The 1 left no trace. Why this step? This is the mechanism behind the associativity break in Example F.
Verify: in Python,
1e16 + 1 == 1e16isTrue;1e16 + 2 == 1e16isFalse. The gap is exactly . ✓
Example F — associativity flip
Before the example, one small lemma we will lean on.
Steps.
-
Left grouping . (absorption, Example E). Then . Why this step? Shows the 1 dies in the first add, so the result is .
-
Right grouping . exactly — this is the Sterbenz lemma above: and are equal in magnitude, so the subtraction introduces no rounding. Then . Why this step? Here the huge numbers cancel first (exactly), protecting the tiny .
-
Compare. Left , right . Differ by 100%. Why this step? Demonstrates floating addition is not associative — grouping decides which bits survive.
-
Rule of thumb. Cancel large opposite-sign terms before adding small ones. Why this step? Prevents the small term being absorbed.
Verify:
(1.0 + 1e16) + (-1e16) == 0.0and1.0 + (1e16 + (-1e16)) == 1.0. ✓
The bar chart below makes the flip vivid. Read it like this: the left red bar is the grouping — it collapses to because the was absorbed by before could cancel it; the right green bar is — it reaches because the two huge terms annihilate first (Sterbenz-exact) and the survives. Two identical sets of numbers, two different heights: that height gap is associativity failure.

Example G — summation order over a list
Steps.
-
Naive left-to-right. Start . Add the first : (absorbed). Every subsequent is likewise absorbed. Final — all ones lost. Why this step? The running partial sum is huge from step one, so each tiny addend falls below its gap.
-
Ascending order. Sort so the huge number is last: sum the million ones first, getting (each add is between comparable numbers — safe), then add . Now : the gap at is , and , so it survives: . Why this step? Adding smallest first lets them accumulate into a number big enough not to be absorbed. See Round-off error propagation.
-
Kahan option. Kahan compensated summation carries the lost low bits in a compensator
c, giving error independent of order and of . Why this step? When you can't sort (streaming data), Kahan recovers accuracy anyway.
Verify: naive gives ; ascending gives ; both compared to the true . ✓
Example H — the equality trap
Steps.
-
Why they aren't exact. are not finite binary fractions (like in decimal). Each is stored as the nearest double, already off by . Why this step? The premise "equal in math ⇒ equal in floats" is false because the inputs themselves are rounded.
-
The sum. , while . They differ by . Why this step? Shows the two sides land on different doubles, so
==returnsFalse. -
Fix: tolerance compare. Use
math.isclose(x, y), i.e. with defaultrtol=1e-9. Why this step? We ask "are they equal within round-off?" instead of "bit-identical?".
Verify:
0.1 + 0.2 == 0.3isFalse; the difference is about ;abs((0.1+0.2)-0.3) < 1e-9isTrue. ✓
Example I — real-world word problem
Steps.
-
True difference. . Why this step? Sets the target: a 25 cm difference.
-
Digit accounting. Each altitude is good to significant digits (double). Absolute error of each is about . Subtraction is exact (Sterbenz — the two values are within a factor of 2), so those two absolute errors carry straight into : worst case . Relative error of is then . Why this step? The relative-error amplifier tells us we lose about digits. Starting from good digits, roughly remain — so is still fine here. But if the two altitudes were 25 nanometres apart, the amplifier would be and nothing would survive. Why this matters: the danger scales with how close the two big numbers are.
-
Engineering fix. Store the difference from a common reference: subtract a base altitude (say ) from each reading before rounding, or work in a local coordinate frame where the small quantity is represented directly rather than as a difference of two huge ones. Then you keep full precision on the 25 cm quantity, because it is never born from a big-minus-big. Why this step? Never let the interesting small number arise as a difference of two nearly-equal large numbers — represent it directly. See Numerical stability and conditioning.
Verify: ; amplifier ; digits lost . ✓
Example J — the exam twist
Steps.
-
What float128 buys. `np.float128` (x86 80-bit extended) has – significant digits, . Why this step? Know the new resolution before claiming it helps.
-
Does the cancellation vanish? ; now , so the term survives at this particular , and you get . It looks fixed. Why this step? Shows higher precision only postpones the cliff.
-
Push smaller. At , , so
float128also rounds to . The catastrophe returns — you only moved the threshold. Why this step? Proves the problem is the formula's conditioning, not the machine's precision. -
Correct fix. Reformulate as (Example D). This works in plain
float64for all small . Why this step? Fixing the expression beats upgrading the hardware — the mistake "more precision always fixes accuracy" from the parent note, cell J.
Verify: at ,
float64naive but reformulated ; the reformulation needs no extra precision. ✓
Recall
Recall Cover the answers — which fix for which cell?
Which cell is "subtract two big same-sign numbers"? ::: A / C — catastrophic cancellation, fix by reformulation. The safe quadratic root uses which sign of ? ::: The sign that makes add (same sign as ), so no cancellation. How do you get the dangerous quadratic root safely? ::: from the product of roots. Why does give exactly 0 in float64? ::: The correction is below , so rounds to . Best summation order for one-huge-many-tiny? ::: Ascending (smallest first) — or Kahan. Does float128 fix a cancelling formula? ::: No, it only moves the threshold; reformulate instead. What does the Sterbenz lemma guarantee? ::: If two same-sign floats are within a factor of 2, their subtraction is computed exactly (no rounding).