Capstone — Diagnosing and Fixing Numerical Bugs in Real Code

Numerical Methods & Floating-Point Computation

Chapter 10 · Capstone — Diagnosing and Fixing Numerical Bugs in Real Code

One continuous audit: a small, ordinary-looking industrial sensor-monitoring codebase, module by module, each one carrying a real floating-point bug of exactly the kind this course has spent nine chapters teaching how to recognize. Every diagnosis below reuses this course's own already-verified numbers directly — nothing here is re-derived from scratch, because the whole point of a real audit is applying knowledge you already trust, not re-proving it each time.

StepModule auditedBug typeChapter(s) used
1Calibration validity checkExact-equality comparisonCh.1-2
2Daily totals aggregatorRounding biasCh.3
3Drift-rate calculatorCatastrophic cancellationCh.4, Ch.9
4Sensor variance monitorUnstable variance formulaCh.5
5Dual-sensor calibration systemIll-conditioned matrixCh.6, Ch.8
6Set-point solverNewton's method cyclingCh.7
7Two-sensor blend solverUnpivoted eliminationCh.8
8Trajectory integratorWasteful fixed-step integrationCh.9

Step 1 — The Calibration Validity Check

Ch.1-2
# original: silently fails to flag miscalibrated sensors def is_calibrated(reading_a, reading_b): return (reading_a + reading_b) == 0.3 # reading_a=0.1, reading_b=0.2 -> False!

Two sensors are supposed to sum to a known reference value of 0.3. The check almost never passes, even for correctly calibrated sensors.

Diagnosis — directly reusing Chapter 1's own verified finding
0.1 + 0.2 evaluates to 0.30000000000000004, which differs from the literal 0.3 (stored as 0.29999999999999998...) by ≈5.55×10⁻¹⁷ — exactly the gap Chapter 1 verified. The exact-equality check was never going to reliably pass, regardless of how correctly the sensors were actually calibrated.
# fixed: tolerance-based comparison def is_calibrated(reading_a, reading_b, tol=1e-9): return abs((reading_a + reading_b) - 0.3) < tol

Step 2 — The Daily Totals Aggregator

Ch.3

Thousands of individual readings are rounded to the nearest whole unit before being summed into a daily report, using each language's default "round half up" behavior.

Diagnosis — directly reusing Chapter 3's own verified 1,000-tie experiment
Chapter 3 verified that rounding 1,000 values landing exactly on a .5 boundary with round-half-up produces a systematic +500 bias versus the true total, while round-half-to-even produces zero bias. The aggregator has exactly this shape: any sensor reading that happens to land on an exact half-unit tie gets rounded the same direction every time, so the daily total drifts upward in proportion to how many exact ties occur — a real, compounding, one-directional error, not random noise that averages out.

Fix: switch the rounding mode to round-half-to-even (IEEE 754's own default, and Python's built-in round()) rather than a manually-implemented round-half-up.

Step 3 — The Drift-Rate Calculator

Ch.4, Ch.9
# original: numerically differentiates a temperature sensor's own reading def drift_rate(temperature_fn, t, h=1e-14): return (temperature_fn(t+h) - temperature_fn(t)) / h # h chosen "for accuracy"

A well-meaning engineer picked an extremely small h, reasoning "smaller step, more accurate derivative."

Diagnosis — directly reusing Chapter 4's own resolved example
Chapter 4 verified exactly this mistake using f(x)=x² at x=3: at h=10⁻¹³, the relative error in the derivative reaches ≈6.8×10⁻⁴, and by h=10⁻¹⁶ the approximation collapses to 0 entirely, for a true derivative of 6. The engineer's instinct — smaller h is always better — is precisely the mistake this course spent Chapter 4 resolving: shrinking h drives temperature_fn(t+h) and temperature_fn(t) together, triggering catastrophic cancellation in the numerator.

Fix: switch to central differencing at a moderate step size (Chapter 9 verified central differences beat forward differences by nearly five orders of magnitude at h=10⁻⁴), or — since the sensor's own temperature function is a simple, analytic calculation internally — use complex-step differentiation, verified exact to full double precision at any step size down to 10⁻¹⁰⁰, eliminating the tuning problem entirely.

Step 4 — The Sensor Variance Monitor

Ch.5
# original: flags "impossible" negative variance readings as a hardware fault def sensor_variance(readings): n = len(readings) mean_sq = sum(x*x for x in readings) / n mean = sum(readings) / n return mean_sq - mean**2 # one-pass "efficient" formula

A pressure sensor with a large fixed baseline reading (around 20,000,000 units) plus small genuine fluctuation triggers repeated false hardware-fault alerts.

Diagnosis — directly reusing Chapter 5's own verified negative-variance result
Chapter 5 verified this exact one-pass formula returns −0.0625 — a mathematically impossible negative variance — on real data with a large baseline offset, against a true variance of ≈0.0673. The sensor's own large baseline is triggering the identical bug: E[X²] and (E[X])² are both enormous, and their difference (the true, tiny variance) is swamped by the rounding error accumulated while computing those two huge sums.

Fix: switch to the two-pass formula (center the data around its own mean first, then square) — Chapter 5 verified this matches a high-precision reference to ≈7×10⁻¹⁷ relative error on the identical data.

Step 5 — The Dual-Sensor Calibration System

Ch.6, Ch.8

Two redundant pressure sensors are calibrated together by solving a small linear system relating their readings. The computed calibration constants vary wildly between runs, even on nearly identical input data.

Diagnosis — this one isn't a coding bug at all
The calibration system is algebraically identical to Chapter 6 and Chapter 8's own near-singular example, [[1,1],[1,1.0001]], with a verified matrix condition number of ≈40,004. The two sensors are giving almost redundant information (nearly parallel equations), and Chapter 6 proved directly — using exact, rounding-free 50-digit arithmetic — that no algorithm can fix this: a 10⁻¹⁰ relative input perturbation still produced a ≈10⁻⁶ relative output change, a genuine property of the sensor pair's own geometry, not of any solver.

Fix: this is a hardware/sensor-placement issue, not a software one — per Chapter 6's own honest conclusion, the correct response is to compute and report the condition number directly, flag the sensor pair as too redundant to calibrate reliably, and recommend physically repositioning one sensor rather than continuing to search for a better solver.

Step 6 — The Set-Point Solver

Ch.7
# original: hangs indefinitely for certain valve configurations def find_setpoint(f, fprime, x0, tol=1e-10): x = x0 while abs(f(x)) > tol: # no iteration limit -- can loop forever x = x - f(x)/fprime(x) return x

For one particular valve's calibration curve, f(x)=x³−2x+2, starting from a "reasonable" default guess of x₀=0, the solver never returns.

Diagnosis — directly reusing Chapter 7's own verified cycle
Chapter 7 verified this exact function and starting point produces a permanent 0 → 1 → 0 → 1 → ... cycle under Newton's method — not slow convergence, but no convergence at all, from a starting point that looked entirely reasonable.

Fix: add an iteration cap and a bracketing fallback — if Newton's method hasn't converged within a set number of steps, fall back to bisection (Chapter 7's own guaranteed-but-slower alternative) using a bracket confirmed by a sign change, rather than looping indefinitely.

Step 7 — The Two-Sensor Blend Solver

Ch.8

A separate calibration routine blends two sensor readings by solving a small linear system via plain Gaussian elimination, no row swapping. For one particular pair of sensitivity coefficients, one of the two blended outputs comes back wildly wrong.

Diagnosis — directly reusing Chapter 8's own verified failure
The coefficients happen to include a near-zero leading entry, exactly Chapter 8's own 10⁻¹⁶-pivot example. Verified there: one output variable came back with a relative error of ≈1.22 (more than 100% wrong) while the other stayed accurate to ≈10⁻¹⁶ — the huge elimination multiplier (10¹⁶) amplified rounding error straight through Chapter 4's own cancellation mechanism during back-substitution.

Fix: add partial pivoting to the elimination routine. Chapter 8 verified this alone drops the error from ≈1.22 down to ≈10⁻¹⁶ — the exact same system, correctly solved, just by changing the row processing order.

Step 8 — The Trajectory Integrator

Ch.9

A separate module numerically integrates sensor-derived acceleration data into a trajectory estimate using a fixed, uniformly-spaced grid — chosen once, years ago, and never revisited. Most flight segments are smooth, but a small number involve a sharp maneuver the fixed grid consistently under-resolves.

Diagnosis — directly reusing Chapter 9's own verified comparison
Chapter 9 verified that on a function with a sharp, localized feature, a fixed grid needs 501 function evaluations to reach an error of ≈3.16×10⁻⁹, while adaptive quadrature reaches comparable accuracy (≈3.53×10⁻⁹) using only 265 evaluations — roughly half — because most of the fixed grid's points are wasted on the smooth stretches while the sharp maneuver is exactly where more resolution is actually needed.

Fix: replace the fixed-step integrator with adaptive quadrature. Sharp maneuvers automatically get more evaluation points where they're needed; smooth stretches automatically get fewer — better accuracy on the segments that matter, at lower total computational cost.

Audit Summary

StepRoot causeFixable by better code?
1Exact equality on a value that was never guaranteed to be exactYes — tolerance comparison
2Directionally-biased rounding compounding over many operationsYes — round-to-even
3Cancellation from an over-aggressively small step sizeYes — central or complex-step differencing
4An algebraically-unstable one-pass formulaYes — two-pass formula
5An inherently ill-conditioned sensor pairNo — hardware issue, honestly reported
6An unguarded iterative method with no fallbackYes — iteration cap + bisection fallback
7An unstable elimination orderYes — partial pivoting
8A fixed grid wasting effort on well-behaved regionsYes — adaptive quadrature
The one finding that mattered most
Seven of these eight bugs were genuinely fixable by better code — recognizing them was the entire skill this course taught. But Step 5 is the one that separates a numerically literate engineer from one who merely knows some fixes: recognizing when a problem is not a code bug at all, and that the honest, correct response is to measure and report the condition number rather than keep tuning a solver that was never going to work.

What This Course Doesn't Cover

As stated honestly back in Chapter 1: symbolic/exact computer algebra, GPU-specific and parallel floating-point quirks (reduced precision, non-associative summation order, fused multiply-add), and arbitrary-precision/interval arithmetic libraries were all named as deliberately out of scope, and stayed out of scope through all ten chapters. Every bug in this capstone was diagnosed and fixed using only standard double-precision arithmetic — the default nearly every language reaches for first, and exactly the territory this course committed to from the start.

Where This Course Connects

Calculus & Optimization's own Chapter 1 forward-referenced the exact cancellation mechanism resolved in this course's Chapter 4, and its own numerical differentiation/integration (Chapters 4 and 9) were the introductory versions of what this course's Chapter 9 covered in real depth. Linear Algebra Fundamentals' determinant and inverse formulas were used directly to compute a real matrix condition number in Chapter 8. Algorithms & Complexity's own iterative-algorithm framing underlies both Chapter 7's root-finding and Chapter 9's adaptive quadrature. Within Technical Support, perfdiag1's own performance-diagnosis discipline and appdiag1's own root-cause reasoning are the same "measure, don't guess" instinct this capstone applied to numerical bugs specifically.

Hands-On Exercises

Exercise 1

A colleague proposes fixing Step 5's ill-conditioned dual-sensor system by "just using higher-precision floating point (128-bit) instead of standard doubles." Using this chapter's own Step 5 diagnosis and Chapter 6's original reasoning, explain whether this would actually fix the problem.

📄 View solution
Exercise 2

Using this chapter's own Audit Summary table, group the eight bugs into two categories: those caused by an algorithm choice (fixable by using a different, better algorithm for the exact same problem) and those caused by the problem's own inherent sensitivity (not fixable by algorithm choice alone). Justify each grouping using this course's own Chapter 5/6 distinction.

📄 View solution
Exercise 3

Step 6's fix adds an iteration cap and a bisection fallback to the set-point solver, rather than simply switching entirely from Newton's method to bisection. Using this chapter's own Step 6 diagnosis and Chapter 7's original comparison of the two methods, explain why keeping Newton's method as the primary approach (with bisection only as a fallback) is a better design than replacing it outright.

📄 View solution

Chapter 10 Quick Reference

  • Full worked audit: eight modules, eight bugs, each diagnosed by directly reusing an already-verified finding from Chapters 1 through 9 rather than re-deriving anything from scratch
  • Seven of the eight bugs were genuine algorithm/code issues, fixable with a better formula, a tolerance check, pivoting, or an adaptive method
  • One bug (Step 5) was not a code bug at all — an inherently ill-conditioned sensor pair, correctly diagnosed by computing and honestly reporting a condition number rather than chasing a nonexistent software fix
  • Every diagnosis in this capstone traces back to one of three root mechanisms taught across the course: cancellation (Ch.4), instability (Ch.5), or ill-conditioning (Ch.6) — everything else in the course builds on recognizing which of these three is actually at play
  • Course complete — Numerical Methods & Floating-Point Computation, 10 chapters, from 0.1+0.2≠0.3 to a fully audited, fixed, real codebase