AI Skill Report Card

Debugging Float Precision Issues

A90·Sep 5, 2026·Source: Web
15 / 15

Bug report: "My test assert total == 1.0 fails but total prints as 1.0."

Python
import math total = sum([0.1] * 10) print(total) # 1.0 (displayed, but actually 0.9999999999999999) print(total == 1.0) # False — the bug # Fix: use fsum for accurate accumulation, isclose for comparison total = math.fsum([0.1] * 10) print(total == 1.0) # True
Recommendation
Add a brief note on floating-point representation (why 0.1+0.1+0.1 != 0.3) for completeness, even just one line, to ground the decision tree
14 / 15

Use this decision process when a numeric result is "wrong" but looks right:

Progress:

  • Step 1: Reproduce — print repr(x) or f"{x:.20f}", not str(x), to see hidden precision error
  • Step 2: Classify the bug (see decision tree below)
  • Step 3: Apply the targeted fix
  • Step 4: Re-verify with math.isclose() — never re-add a raw == check
  • Step 5: Add a regression test using the same tolerance

Decision Tree

Symptom: equality comparison fails unexpectedly → Never compare floats with ==. Use math.isclose(a, b, rel_tol=1e-9).

  • If comparing against zero, rel_tol is useless (relative to 0 is always 0) — pass abs_tol explicitly: math.isclose(x, 0, abs_tol=1e-9).

Symptom: summing many floats drifts from the mathematically exact answer → Built-in sum() accumulates rounding error one addition at a time. Replace with math.fsum(), which uses Shewchuk's algorithm for a correctly-rounded result.

Python
values = [1e16, 1.0, -1e16] sum(values) # 0.0 (wrong — 1.0 got lost) math.fsum(values) # 1.0 (correct)

Symptom: int(math.sqrt(n)) gives the wrong integer for large nmath.sqrt converts to float, which loses precision above ~2^53. Use math.isqrt(n) for exact integer results, no float roundtrip.

Python
n = 10**30 int(math.sqrt(n)) ** 2 == n # False — precision lost math.isqrt(n) ** 2 <= n # True — isqrt is exact

Symptom: computing exp(x) - 1 or log(1 + x) for small x gives noisy/zero results → Catastrophic cancellation: subtracting two nearly-equal large-magnitude floats destroys precision. Use math.expm1(x) and math.log1p(x), which compute these directly without the cancellation step.

Python
x = 1e-15 math.exp(x) - 1 # 1.1102230246251565e-15 (noisy, wrong-ish) math.expm1(x) # 1.0000000000000007e-15 (accurate)

Symptom: combinatorics or factorial results overflow or lose precision → Don't compute n! / (k! * (n-k)!) manually with floats. Use math.comb(n, k) / math.perm(n, k), which stay in exact integer arithmetic.

Symptom: distance/norm calculation is unstable for very large or small coordinates → Manual sqrt(x**2 + y**2) can overflow/underflow for extreme values. Use math.hypot(x, y, ...), which scales internally to avoid overflow.

Recommendation
Include an example showing a 'bad fix' (e.g., using round() incorrectly) with its failure mode, not just the pitfalls list
18 / 20

Example 1: Financial-style running total that never quite hits zero

Input:

Python
balance = 0.0 for amount in [10.10, -3.30, -6.80]: balance += amount print(balance == 0.0) # expected True, got False

Diagnosis: repeated += on floats accumulates rounding error. Output (fix):

Python
import math balance = math.fsum([10.10, -3.30, -6.80]) print(math.isclose(balance, 0.0, abs_tol=1e-9)) # True

Example 2: Integer square root for cryptography-scale numbers

Input:

Python
n = 123456789123456789123456789123456789 root = int(n ** 0.5) print(root * root <= n) # False — overflowed float precision

Output (fix):

Python
import math root = math.isqrt(n) print(root * root <= n) # True print((root + 1) ** 2 > n) # True — confirms floor sqrt is correct

Example 3: Probability computed as ratio of factorials blows up

Input:

Python
n, k = 1000, 500 p = math.factorial(n) // (math.factorial(k) * math.factorial(n - k)) # works but wastefully computes huge intermediate factorials

Output (fix):

Python
import math p = math.comb(n, k) # same result, computed more efficiently, no huge intermediates
Recommendation
Consider adding a section on Decimal/Fraction as alternatives when precision requirements exceed what math module functions can offer
  • Treat == on floats as a code smell during review — always ask "should this be isclose?"
  • When precision matters near zero, always set abs_tol explicitly in isclose; the default abs_tol=0.0 makes the check meaningless there.
  • Prefer math.fsum over sum in any loop that accumulates more than a handful of floats, especially in financial or scientific accumulation code.
  • Prefer math.isqrt, math.comb, math.perm over float-based equivalents whenever inputs are integers — they avoid the float precision ceiling (~2^53) entirely.
  • For values near a singularity (x near 0 in exp/log), reach for expm1/log1p before assuming a bug elsewhere.
  • Don't "fix" a float bug by rounding with round(x, 2) — this masks the symptom without addressing accumulated error, and can reintroduce bugs at different scales.
  • Don't use math.isclose() with defaults when comparing to zero — it will always return False for any nonzero a since relative tolerance scales with the compared values.
  • Don't convert large integers to float for sqrt/pow just for convenience — verify the magnitude first (n.bit_length() > 53 is a red flag) and switch to math.isqrt.
  • Don't assume math.fsum is needed everywhere — for small lists of similar-magnitude numbers, the overhead isn't worth it; reserve it for precision-sensitive accumulation.
0
Grade AAI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
15/15
Workflow
14/15
Examples
18/20
Completeness
18/20
Format
15/15
Conciseness
14/15