Exercises — Floating point gotchas — catastrophic cancellation, associativity failure
Level 1 — Recognition
Goal: spot where precision dies without computing anything hard.
Exercise 1.1 — Name the villain
For each expression, is the danger (A) catastrophic cancellation, (B) absorption / associativity failure, or (C) neither?
- for
- starting the running sum at
- for
Recall Solution 1.1
1 → (A). Both square roots are and nearly equal; subtracting near-twins reveals hidden relative error. Amplifier is huge.
2 → (B) absorption. Each term is astronomically smaller than the running sum . The gap between representable doubles near is about , and is far below that gap → each add contributes nothing. See Catastrophic cancellation's cousin, absorption.
3 → (C). Multiplication does not amplify relative error; the product is stored with relative error . Safe.
4 → (A). ; storing it rounds away almost all of the interesting part, then subtracting leaves a mangled result. Fix: use math.expm1.
Exercise 1.2 — True or false
- Float addition is commutative: always.
- Float addition is associative: always.
- Subtracting two nearly-equal floats is itself an inaccurate operation.
Recall Solution 1.2
1 → True. and round the same real number, so they agree exactly.
2 → False. Each + rounds at a place set by the partial sum's magnitude; regrouping rounds differently.
3 → False. By the Sterbenz lemma (defined at the top of this page), when are within a factor of the subtraction is exact. It merely exposes error already baked into the stored operands.
Level 2 — Application
Goal: run the machine model on real numbers.
Exercise 2.1 — The amplifier factor
Let and (treat as exact reals). Using the bound compute the amplification factor and the worst-case relative error of .
Recall Solution 2.1
WHAT: plug numbers into the derived amplifier. , and . Worst-case relative error . WHY it matters: we started with relative error and blew it up to — we lost about 7 decimal digits, exactly of the amplifier.
Exercise 2.2 — Predict the absorption
Compute by hand what Python prints for (1.0 + 1e16) - 1e16 and for 1.0 + (1e16 - 1e16). The gap between representable doubles near is exactly .
Recall Solution 2.2
First expression: . The true sum is . The two candidate representable results are (below) and (above, one full gap up). The true value sits at , which is exactly half a gap between them. IEEE-754 uses round-to-nearest, ties-to-even: it picks the neighbour whose last mantissa bit is (even). Here has last bit while has last bit... also representable, but is the even-mantissa neighbour, so the tie rounds down to . Then . Prints 0.0.
Second expression: exactly, then . Prints 1.0.
Same three numbers, brackets only — answers differ by 100%. This is associativity failure via absorption. (The tie-to-even detail is why the vanishes rather than nudging up; even off the exact tie, any addend below would round down regardless.)
Exercise 2.3 — Stable quadratic root
Solve so , , . Compute both roots stably. Report to full precision using the identity .
Recall Solution 2.3
WHAT/WHY: , so ; the + branch adds same-sign numbers → safe root first.
, i.e. just under .
Now the dangerous root without subtraction:
Check: ✓ and ✓.
The naive subtracts two numbers → catastrophic. See Quadratic formula numerical issues.
Level 3 — Analysis
Goal: reformulate to kill the cancellation, and quantify the win.
Exercise 3.1 — Rationalise the square-root difference
Evaluate at . Give the naive value's problem, the stable rewrite, and the stable numeric answer.

Recall Solution 3.1
Naive: and . Their difference is , but each root has relative error absolute error in each; subtracting near-twins with amplifier wrecks it. Rewrite (WHY): multiply by the conjugate — this replaces subtraction of near-equals with an addition in the denominator: Stable value: . Look at the figure: the red "near-twin" bars subtract to a sliver; the blue rewrite computes that sliver directly with no cancellation.
Exercise 3.2 — The limit
At the naive returns (true limit ). Show why it returns using the gap argument, then compute the stable value from .
Recall Solution 3.2
Why : . But near the smallest step is , and is below half a step rounds to exactly . Then , and . Total loss. Stable: with , (computed accurately for tiny arguments), so Relative error — perfect.
Exercise 3.3 — Count the digits lost
You subtract with amplifier . Roughly how many significant decimal digits survive in the result, starting from a double's ?
Recall Solution 3.3
Rule: digits lost . digits lost. Survivors significant decimal digits. This is the practical meaning of the amplifier: its base-10 log is the digit death toll. See Round-off error propagation.
Level 4 — Synthesis
Goal: assemble a full stable algorithm.
Exercise 4.1 — Hand-trace Kahan summation
Run Kahan compensated summation on the data (added in this order). The true sum is . Show s and c after each step, and compare to the naive left-to-right sum.
Recall Solution 4.1
Recall the loop: y = x - c; t = s + y; c = (t - s) - y; s = t. Facts we use: the gap near is , so integers there are only representable if even; and are representable, is not.
Naive left-to-right: . (the absorbed), (absorbed again), then . Naive result — wrong by .
Kahan trace (start ). Each line: WHAT the operation is, then the rounded value:
- :
y = 1 - 0 = 1(exact).t = fl(0 + 1) = 1(exact).c = fl((1 - 0) - 1) = 0.s = 1. → . - :
y = fl(1e16 - 0) = 1e16(exact).t = fl(1 + 1e16) = 1e16— the is below half the gap, absorbed. Now recover:t - s = fl(1e16 - 1) = 1e16(again absorbed, exact difference rounds up to ), thenc = fl((t - s) - y) = fl(1e16 - 1e16) = 0. Hmm — so on this simple integer data Kahan storesc = 0. → . (WHY the lost is not yet captured: botht-sand the recovery landed on exact grid points, so no residual surfaced this step — the loss is carried implicitly in the fact thatsis short.) - :
y = fl(1 - 0) = 1(exact).t = fl(1e16 + 1) = 1e16— absorbed again.t - s = fl(1e16 - 1e16) = 0, soc = fl(0 - 1) = -1. Now the dropped is captured: .s = 1e16. → . - :
y = fl(-1e16 - (-1)) = fl(-1e16 + 1) = -1e16(the absorbed, but it is held iny's intent).t = fl(1e16 + (-1e16)) = 0(exact cancellation).t - s = fl(0 - 1e16) = -1e16,c = fl((-1e16) - (-1e16)) = 0.s = 0. → .
Final s = 0.0 with this exact IEEE trace. The single interleaving of around lone 's defeats even Kahan here, because the compensation is itself absorbed when re-added to . Lesson: Kahan is not magic against pure absorption of a subnormal-relative addend into a giant that is then cancelled — the cure is to reorder (cancel the giants first). This is exactly Exercise 5.3.
Contrast — an order Kahan does save. On , naive gives , but summing left-to-right gives ; since is representable, this is . Exact. The moral holds: order beats absorption; Kahan helps when losses are residuals, not total absorptions of terms that later cancel.
Exercise 4.2 — Design a robust float comparison
Write the predicate for "are and close?" that Python's math.isclose uses, and explain each term. Then decide: is 0.1 + 0.2 close to 0.3 under defaults rtol=1e-9, atol=0.0?
Recall Solution 4.2
Predicate:
rtol(relative tolerance) scales with the operands' magnitude — needed because float error is relative.atol(absolute tolerance) rescues the case where both are near (relative test degenerates there). The case: the computed sum is , differing from by . Threshold . Since , they ARE close → predicate returnsTrue, even though==returnsFalse.
Level 5 — Mastery
Goal: prove and generalise.
Exercise 5.1 — Prove the amplifier bound
Starting from stored operands , with , and assuming the subtraction is exact (Sterbenz, since — see the definition at the top of this page), prove
Recall Solution 5.1
Step 1 (WHAT): expand the computed result. Step 2: subtract the true : Step 3 (bound the numerator): by the triangle inequality and , Step 4 (divide by ): WHY the "exact subtraction" assumption is fair: the Sterbenz lemma guarantees no new rounding when ; all the damage lives in , which is precisely what the proof isolates.
Exercise 5.2 — Absorption threshold, exactly
Adding a small to a large : for what values of does (total absorption)? Express the threshold using the gap , then apply it to .
Recall Solution 5.2
Step 1 (WHAT the gap is): near the spacing between representable doubles is This is the weight of the least-significant mantissa bit at 's exponent. Step 2 (round-to-nearest rule): the true value lies between the two neighbours and . Round-to-nearest snaps it back to whenever it is at most half a gap above : At exactly half a gap, ties-to-even decides: if 's last mantissa bit is (even) the tie also rounds to , so the strict absorption region is for even- (and otherwise). Step 3 (apply ): , so Therefore any is fully absorbed: . Indeed is the first positive integer a double cannot represent — a famous landmark: doubles represent every integer up to , then start skipping.
Exercise 5.3 — Order a sum to minimise error
You must sum with plain left-to-right addition (no Kahan). True sum . Find an ordering that gives the exact answer, and explain the principle.
Recall Solution 5.3
Bad order : absorb, absorb, then cancel → . Good order : cancel the giants first ( exactly), then . Exact. Principle: kill large opposite-sign terms against each other before they can swallow the small survivors. When you can't reorder (streaming data), use Kahan for residual losses — but as Exercise 4.1 showed, even Kahan cannot rescue a total absorption that is later cancelled, so reordering is the primary weapon. This is the associativity-failure lesson turned into a strategy.
Recall
Recall Rapid self-test — cover the answers
- Digits lost from an amplifier of ? ::: about (i.e. of it), leaving .
- Stable rewrite of ? ::: .
- First integer a double cannot represent? ::: .
math.isclosepredicate? ::: .- Exact sum of left-to-right? ::: (cancel giants first).
- Which quadratic root do you compute directly, sign-wise? ::: the one whose matches the sign of (same-sign addition).
Parent: Floating point gotchas · See also IEEE-754 floating point representation, Catastrophic cancellation, Kahan compensated summation.