Type at least 2 letters. Enter opens the first result, Esc closes.

    Linear Algebra · Lesson 13 · Numerical linear algebra

    Floating point, cancellation, mixed precision, conditioning and backward error

    Intermediate 85 min 6 interactive figures 4 videos Colab notebook

    Before this lesson: lesson 08 (the SVD), lesson 11 (norms) and lesson 12 (QR and least squares). Time: about 85 minutes, plus the notebook.


    Why this matters #

    Every number in a training run is rounded, and bf16 keeps only 8 significant bits (2–3 decimal digits). Rounding explains failures that look like bugs: a loss that becomes NaN in fp16, a running sum that stops growing, a variance that comes out negative, regression coefficients that change completely when one data point is added. Two ideas organize all of it. Conditioning says how sensitive the problem is: you lose about log⁡10κ\log_{10}\kappa digits, whatever algorithm you use. Stability says whether the algorithm adds more error than that.

    1. Predict first #

    2. The picture #

    A 3-digit decimal computer. Suppose a calculator keeps 3 significant digits: 1.23×1051.23 \times 10^5, 4.56×10−24.56 \times 10^{-2}. The gap between neighbours grows with size (0.01 near 1, 1000 near 10510^5), but the relative gap stays about the same. Rounding to the nearest number has a relative error of at most u=12×10−2=0.005u = \tfrac12 \times 10^{-2} = 0.005.

    Binary formats work the same way, with powers of 2 instead of powers of 10. Between 2e2^e and 2e+12^{e+1} the numbers are evenly spaced; at each power of 2 the gap doubles.

    One fp16 number, bit by bit. fp16 uses 16 bits: 1 sign bit ss, 5 exponent bits EE and 10 fraction bits ff. A normal number is

    x=(−1)s×(1.f)2×2E−15.x = (-1)^s \times (1.f)_2 \times 2^{E - 15}.

    The 15 is the bias. It lets the stored EE be an unsigned integer from 0 to 31 while the true exponent e=E−15e = E - 15 can be negative. Decode the bits 1 10001 1010000000:

    1. Sign: s=1s = 1, so the number is negative.
    2. Exponent: E=100012=16+1=17E = 10001_2 = 16 + 1 = 17, so e=17−15=2e = 17 - 15 = 2.
    3. Fraction: f=0.10100000002=2−1+2−3=0.625f = 0.1010000000_2 = 2^{-1} + 2^{-3} = 0.625, so the significand is (1.f)2=1.625(1.f)_2 = 1.625. The leading 1 is not stored (the hidden bit), so fp16 has p=10+1=11p = 10 + 1 = 11 bits of precision.
    4. Value: x=−1.625×22=−6.5x = -1.625 \times 2^2 = -6.5.

    Two exponent values are special. E=0E = 0 gives zero and the subnormal numbers (no hidden bit). E=31E = 31 gives ±∞\pm\infty (if f=0f = 0) and NaN (if f≠0f \ne 0). So the largest fp16 number has E=30E = 30 and all fraction bits 1: (2−2−10)×215=65504(2 - 2^{-10}) \times 2^{15} = 65504.

    Interactive figure: loading…

    Click the bits or type a number. The sign, exponent and fraction bits are decoded step by step below them. Try largest (65504 in fp16), then ∞: all exponent bits are 1 and the fraction is 0; flip one fraction bit of ∞\infty and you get NaN. Type 0.1 and look at the stored value: 0.1 has no finite binary expansion. Switch to bf16: the same number gets 8 exponent bits and only 7 fraction bits.

    Interactive figure: loading…

    Move xx or pick a preset. The top chart is the gap between neighbouring floats near xx: a staircase that doubles at every power of 2, for bf16, fp16 and fp32. The bottom rows draw the representable numbers of each format in the same small window around xx; the dot is where xx is stored. Try 2049 (fp16 cannot store it), 1e−8 (fp16 underflows to 0) and 70000 (fp16 overflows).

    Quick check

    fp16 has p=11p = 11 bits of precision. What is the gap between neighbouring fp16 numbers near x=1000x = 1000?

    Cancellation on the 3-digit computer. The true values 1.23491.2349 and 1.21511.2151 are stored as 1.231.23 and 1.221.22, each with a relative error below 0.4%. The subtraction 1.23−1.22=0.011.23 - 1.22 = 0.01 is exact, but the true difference is 0.01980.0198: the result is wrong by 49%. The leading digits cancel, and the old rounding errors become the main part of what is left.

    A matrix as an amplifier. AA maps the unit circle to an ellipse with semi-axes σ1≥σ2\sigma_1 \ge \sigma_2. Solving Ax=bAx = b runs the map backwards. If bb lies along the long axis, xx is small (∥x∥=∥b∥/σ1\|x\| = \|b\|/\sigma_1). A change δb\delta b along the short axis gives a large change ∥δx∥=∥δb∥/σ2\|\delta x\| = \|\delta b\|/\sigma_2. So the relative error can grow by σ1/σ2=κ\sigma_1/\sigma_2 = \kappa. A flat ellipse (large κ\kappa) means a nearly singular matrix.

    Interactive figure: loading…

    Drag δb\delta b in the small disk. The two equations of Ax=bAx = b are the orange and blue lines, and xx is where they meet. Changing bb shifts both lines in parallel (dashed), and the intersection moves to x+δxx + \delta x. The violet ellipse holds every possible x+δxx + \delta x for ∥δb∥≤r\|\delta b\| \le r. Make δ\delta small: the lines become nearly parallel, the ellipse stretches like 1/σ21/\sigma_2, and the amplification approaches κ2(A)\kappa_2(A) when you press Worst δb.

    3. Definitions and main results #

    Floating-point numbers #

    A binary floating-point number is x=±(1.f1f2…fp−1)2×2ex = \pm(1.f_1f_2\dots f_{p-1})_2 \times 2^e: a significand with pp bits of precision (the leading 1 is not stored) and an exponent ee in a fixed range.

    format bits s/e/f ε=21−p\varepsilon = 2^{1-p}
    fp64 (double) 1/11/52 2−52≈2.2×10−162^{-52} \approx 2.2\times10^{-16}
    fp32 1/8/23 2−23≈1.2×10−72^{-23} \approx 1.2\times10^{-7}
    fp16 1/5/10 2−10≈9.8×10−42^{-10} \approx 9.8\times10^{-4}
    bf16 1/8/7 2−7≈7.8×10−32^{-7} \approx 7.8\times10^{-3}
    format largest smallest normal
    fp64 (double) 1.8×103081.8\times10^{308} 2.2×10−3082.2\times10^{-308}
    fp32 3.4×10383.4\times10^{38} 1.2×10−381.2\times10^{-38}
    fp16 6550465504 6.1×10−56.1\times10^{-5}
    bf16 3.4×10383.4\times10^{38} 1.2×10−381.2\times10^{-38}

    "s/e/f" counts the sign, exponent and fraction bits; the precision is pp = fraction bits + 1. Below the smallest normal number come subnormal numbers with fewer significant bits; the smallest positive fp16 number is 2−24≈6×10−82^{-24} \approx 6\times10^{-8}.

    • Machine epsilon ε=21−p\varepsilon = 2^{1-p} is the gap between 1 and the next larger float: 2−522^{-52} in double. It is not the smallest relative gap: the relative gap varies between ε/2\varepsilon/2 and ε\varepsilon.
    • Spacing. In [2e,2e+1)[2^e, 2^{e+1}) the floats are evenly spaced with gap ε 2e=2e−p+1\varepsilon\,2^e = 2^{e-p+1}. So the gap near xx lies between 12ε∣x∣\tfrac12\varepsilon|x| and ε∣x∣\varepsilon|x|: the absolute gap grows with ∣x∣|x|, the relative gap stays near ε\varepsilon. Example: the gap at 1024 in double is 2−42≈2.3×10−132^{-42} \approx 2.3\times10^{-13}.
    • Unit roundoff u=ε/2u = \varepsilon/2 (2−53≈1.1×10−162^{-53} \approx 1.1\times10^{-16} in double) bounds the relative error of rounding to nearest: fl⁡(x)=x(1+δ)\operatorname{fl}(x) = x(1 + \delta) with ∣δ∣≤u|\delta| \le u (no overflow or underflow).
    • Standard model. IEEE 754 rounds the exact result of +,−,×,/+, -, \times, / and  \sqrt{\ }: fl⁡(x∘y)=(x∘y)(1+δ)\operatorname{fl}(x \circ y) = (x \circ y)(1 + \delta), ∣δ∣≤u|\delta| \le u. One operation is always accurate; trouble comes from how many operations combine.

    Why 0.1+0.2≠0.30.1 + 0.2 \ne 0.3. 0.1=(0.00011‾)20.1 = (0.0\overline{0011})_2 has no finite binary expansion, so it is rounded when stored (0.1000000000000000055…0.1000000000000000055\ldots), and so are 0.20.2 and 0.30.3. The sum of the stored 0.10.1 and 0.20.2 rounds to 0.3000000000000000444…0.3000000000000000444\ldots, while the stored 0.30.3 is 0.2999999999999999888…0.2999999999999999888\ldots: two neighbouring doubles. The cause is that these decimals are not representable in binary, not the size of ε\varepsilon.

    Cancellation #

    Subtracting nearly equal floats is exact (Sterbenz: if y/2≤x≤2yy/2 \le x \le 2y, then x−yx - y is a float). The damage comes from the errors already in the inputs. If x^=x(1+δ1)\hat x = x(1 + \delta_1) and y^=y(1+δ2)\hat y = y(1 + \delta_2) with ∣δi∣≤u|\delta_i| \le u, then (x^−y^)−(x−y)=xδ1−yδ2(\hat x - \hat y) - (x - y) = x\delta_1 - y\delta_2, so

    ∣(x^−y^)−(x−y)∣∣x−y∣≤u ∣x∣+∣y∣∣x−y∣.\frac{|(\hat x - \hat y) - (x - y)|}{|x - y|} \le u\,\frac{|x| + |y|}{|x - y|}.

    The factor (∣x∣+∣y∣)/∣x−y∣(|x| + |y|)/|x - y| is the condition number of subtraction. When x≈yx \approx y it is huge: this is catastrophic cancellation.

    The fix: rewrite so that no nearly equal numbers are subtracted.

    • Algebra: x+1−x=1/(x+1+x)\sqrt{x + 1} - \sqrt{x} = 1/(\sqrt{x + 1} + \sqrt{x}); 1−cos⁡x=2sin⁡2(x/2)1 - \cos x = 2\sin^2(x/2).
    • Library functions for small xx: log1p(x) =log⁡(1+x)= \log(1 + x), expm1(x) =ex−1= e^x - 1.
    • Quadratic ax2+bx+cax^2 + bx + c: compute the root of larger magnitude with the sign that adds (−b-b and −sign⁡(b)b2−4ac-\operatorname{sign}(b)\sqrt{b^2 - 4ac} have the same sign), then the other root from Vieta's formula x1x2=c/ax_1x_2 = c/a.
    • Variance: 1N∑xi2−xˉ2\frac1N\sum x_i^2 - \bar x^2 subtracts two large, nearly equal numbers when the mean is large compared with the spread. Use two passes, 1N∑(xi−xˉ)2\frac1N\sum(x_i - \bar x)^2, or Welford's one-pass update of a running mean and a running sum of squared deviations.
    Interactive figure: loading…

    Pick a formula and move the probe. Each dot is the true relative error of one evaluation in double precision (the exact values come from 100-digit arithmetic). The naive formula follows the dashed bound u(∣a∣+∣b∣)/∣a−b∣u(|a| + |b|)/|a - b| up to error 1; the stable rewrite stays at the level of uu for every input.

    Conditioning against stability, in one example. The small root of x2−108x+1=0x^2 - 10^8x + 1 = 0 is about 10−810^{-8}. It is a well-conditioned function of the coefficients: a relative change of bb or cc changes the root by the same relative amount (condition number 1). Still, the textbook formula (108−1016−4)/2(10^8 - \sqrt{10^{16} - 4})/2 gives 7.45×10−97.45 \times 10^{-9} in double, 25% wrong. The formula is an unstable algorithm for a well-conditioned quantity: the formula itself creates the cancellation. The rewrite 2/(108+1016−4)2/(10^8 + \sqrt{10^{16} - 4}) changes the algorithm, not the problem.

    Quick check

    For small xx, which formula computes 1−cos⁡x1 - \cos x accurately in floating point?

    Mixed precision #

    Training in bf16 or fp16 halves memory traffic and runs on fast tensor cores, but it needs three safeguards. The first one comes from a simple fact: a 16-bit running sum stops growing.

    Why a 16-bit sum stops growing, step by step. fp16 has p=11p = 11.

    1. In [2e,2e+1)[2^e, 2^{e+1}) the fp16 numbers are 2e−p+1=2e−102^{e-p+1} = 2^{e-10} apart.
    2. Below 2048 (in [1024,2048)[1024, 2048)) the gap is 20=12^{0} = 1. From 2048=2112048 = 2^{11} on, the gap is 21=22^{1} = 2: the numbers are …,2046,2047,2048,2050,2052,…\dots, 2046, 2047, 2048, 2050, 2052, \dots
    3. Up to 2048, each s+1s + 1 is a float, so the sum is exact.
    4. 2048+1=20492048 + 1 = 2049 lies exactly halfway between 2048 and 2050. A tie goes to the neighbour whose significand ends in 0 (round to even): 2048=(1.0000000000)2×2112048 = (1.0000000000)_2 \times 2^{11} ends in 0, and 2050=(1.0000000001)2×2112050 = (1.0000000001)_2 \times 2^{11} ends in 1. So 2048+12048 + 1 rounds to 20482048.
    5. From now on, every addition gives 2048 again. The sum is stuck.

    bf16 has p=8p = 8. The gap at 256=28256 = 2^8 is 28−7=22^{8-7} = 2, so 256+1256 + 1 rounds back to 256: bf16 sums of ones stop at 256. In general, adding tt to ss is lost when ∣t∣|t| is less than half the gap at ss (this loss is called swamping). fp32 has p=24p = 24, so the same sum of ones only stops at 224≈1.7×1072^{24} \approx 1.7 \times 10^7.

    Interactive figure: loading…

    Change the term tt and the number of additions. Each line is a running sum rounded after every addition: bf16, fp16 and fp32. On these log–log axes the exact sum is the grey diagonal, and a sum that stops growing becomes a horizontal line: fp16 at 2048 and bf16 at 256 for t=1t = 1. The violet line keeps the inputs in bf16 but the running sum in fp32 (as tensor cores do); it stays on the diagonal.

    The three safeguards:

    1. fp32 accumulation. Matmuls take 16-bit inputs but accumulate in fp32. Large reductions (softmax sums, normalization statistics, the loss) also run in fp32. The error bound for a sum of nn terms is about (n−1)u∑∣xi∣(n-1)u\sum|x_i|; with bf16's u=2−8u = 2^{-8} this bound exceeds the sum itself once nn passes a few hundred.
    2. fp32 master weights. An update ηg\eta g vanishes when ∣ηg∣|\eta g| is below half the gap at ww. In bf16 the gap above w=1w = 1 is 2−7≈0.00782^{-7} \approx 0.0078, so 1+10−31 + 10^{-3} rounds back to 1. The optimizer keeps an fp32 copy of the weights, applies updates there, and casts down for the forward and backward pass.
    3. Loss scaling (fp16). fp16 cannot represent magnitudes below about 6×10−86\times10^{-8}, and it loses precision below 6.1×10−56.1\times10^{-5}. Small gradients underflow: 10−810^{-8} becomes 00. Multiply the loss by SS (a power of 2, so the scaling itself is exact). By the chain rule every gradient is multiplied by SS and moves into range (10−8⋅216≈6.6×10−410^{-8}\cdot 2^{16} \approx 6.6\times10^{-4}). Unscale in fp32 before the update. Dynamic loss scaling (PyTorch GradScaler starts at S=216S = 2^{16}) halves SS and skips the step when an inf or NaN appears (overflow above 65504), and doubles SS after 2000 steps without one.
    Quick check

    A team switches mixed-precision training from fp16 to bf16. Which safeguard can they usually drop?

    Conditioning #

    The relative condition number of a problem x↦f(x)x \mapsto f(x) is the worst amplification of a small relative input change:

    κf=lim⁡δ→0 sup⁡∥Δx∥≤δ∥f(x+Δx)−f(x)∥/∥f(x)∥∥Δx∥/∥x∥.\kappa_f = \lim_{\delta\to 0}\ \sup_{\|\Delta x\| \le \delta} \frac{\|f(x + \Delta x) - f(x)\|/\|f(x)\|}{\|\Delta x\|/\|x\|}.

    For a scalar differentiable ff: κf(x)=∣xf′(x)/f(x)∣\kappa_f(x) = |x f'(x)/f(x)|.

    For an invertible matrix AA, the condition number is κ(A)=∥A∥ ∥A−1∥\kappa(A) = \|A\|\,\|A^{-1}\| (an induced norm). In the 2-norm, ∥A∥2=σ1\|A\|_2 = \sigma_1 and ∥A−1∥2=1/σn\|A^{-1}\|_2 = 1/\sigma_n (because A−1=VΣ−1U⊤A^{-1} = V\Sigma^{-1}U^\top), so

    κ2(A)=σmax⁡σmin⁡≥1.\kappa_2(A) = \frac{\sigma_{\max}}{\sigma_{\min}} \ge 1.

    κ2(Q)=1\kappa_2(Q) = 1 for orthogonal QQ, κ(cA)=κ(A)\kappa(cA) = \kappa(A) for c≠0c \ne 0, and κ=∞\kappa = \infty for a singular AA. For a rectangular AA with full column rank, κ2(A)=σ1/σn\kappa_2(A) = \sigma_1/\sigma_n as well. The determinant does not measure conditioning: 0.1 I1000.1\,I_{100} has det⁡=10−100\det = 10^{-100} and κ=1\kappa = 1.

    Theorem (error bound for Ax=bAx = b) #

    Let AA be invertible, Ax=b≠0Ax = b \ne 0, and A(x+δx)=b+δbA(x + \delta x) = b + \delta b. Then

    ∥δx∥∥x∥≤κ(A) ∥δb∥∥b∥,\frac{\|\delta x\|}{\|x\|} \le \kappa(A)\,\frac{\|\delta b\|}{\|b\|},

    and in the 2-norm the bound is attained.

    Proof. Each line says which fact it uses.

    1. A δx=δbA\,\delta x = \delta b. Subtract Ax=bAx = b from A(x+δx)=b+δbA(x + \delta x) = b + \delta b.
    2. δx=A−1δb\delta x = A^{-1}\delta b. AA is invertible.
    3. ∥δx∥≤∥A−1∥ ∥δb∥\|\delta x\| \le \|A^{-1}\|\,\|\delta b\|. An induced norm satisfies ∥Mv∥≤∥M∥ ∥v∥\|Mv\| \le \|M\|\,\|v\| (lesson 11); use it with M=A−1M = A^{-1}.
    4. ∥b∥≤∥A∥ ∥x∥\|b\| \le \|A\|\,\|x\|. The same inequality with M=AM = A, since b=Axb = Ax.
    5. 1∥x∥≤∥A∥∥b∥\dfrac{1}{\|x\|} \le \dfrac{\|A\|}{\|b\|}. Divide line 4 by ∥x∥ ∥b∥\|x\|\,\|b\|; both are positive because b≠0b \ne 0.
    6. ∥δx∥∥x∥≤∥A∥ ∥A−1∥ ∥δb∥∥b∥=κ(A) ∥δb∥∥b∥\dfrac{\|\delta x\|}{\|x\|} \le \|A\|\,\|A^{-1}\|\,\dfrac{\|\delta b\|}{\|b\|} = \kappa(A)\,\dfrac{\|\delta b\|}{\|b\|}. Multiply lines 3 and 5 (both sides are nonnegative).
    7. Sharpness in the 2-norm. Lines 3 and 4 are equalities for the right vectors. Take b=u1b = u_1: then x=v1/σ1x = v_1/\sigma_1 and ∥b∥=∥A∥ ∥x∥\|b\| = \|A\|\,\|x\|. Take δb=η un\delta b = \eta\,u_n: then δx=η vn/σn\delta x = \eta\,v_n/\sigma_n and ∥δx∥=∥A−1∥ ∥δb∥\|\delta x\| = \|A^{-1}\|\,\|\delta b\|. The ratio is exactly σ1/σn\sigma_1/\sigma_n. ■\blacksquare

    A 2×22 \times 2 check. A=diag⁡(1,0.01)A = \operatorname{diag}(1, 0.01) has σ1=1\sigma_1 = 1, σ2=0.01\sigma_2 = 0.01, κ2=100\kappa_2 = 100.

    • b=(1,0)b = (1, 0), so x=(1,0)x = (1, 0). Take δb=(0,0.01)\delta b = (0, 0.01): then δx=(0,1)\delta x = (0, 1). The relative change of bb is 0.010.01; the relative change of xx is 1=100×0.011 = 100 \times 0.01. The bound is attained.
    • Same bb, but δb=(0.01,0)\delta b = (0.01, 0): then δx=(0.01,0)\delta x = (0.01, 0), a relative change of 0.010.01. The same size of δb\delta b has a 100 times smaller effect: the direction matters.
    • b=(0,1)b = (0, 1), so x=(0,100)x = (0, 100), and δb=(0,0.01)\delta b = (0, 0.01): δx=(0,1)\delta x = (0, 1), a relative change of 0.010.01 again. The worst case needs bb along u1u_1 (small xx) and δb\delta b along unu_n.
    Perturbing AA instead of bb

    If (A+δA)(x+δx)=b(A + \delta A)(x + \delta x) = b, then A δx+δA (x+δx)=0A\,\delta x + \delta A\,(x + \delta x) = 0, so δx=−A−1δA (x+δx)\delta x = -A^{-1}\delta A\,(x + \delta x) and

    ∥δx∥∥x+δx∥≤κ(A) ∥δA∥∥A∥.\frac{\|\delta x\|}{\|x + \delta x\|} \le \kappa(A)\,\frac{\|\delta A\|}{\|A\|}.

    To first order, ∥δx∥/∥x∥≲κ(A) ∥δA∥/∥A∥\|\delta x\|/\|x\| \lesssim \kappa(A)\,\|\delta A\|/\|A\|. So κ(A)\kappa(A) also measures the sensitivity to changes in the matrix.

    Theorem: κ2(A⊤A)=κ2(A)2\kappa_2(A^\top A) = \kappa_2(A)^2 #

    Let AA have full column rank and SVD A=UΣV⊤A = U\Sigma V^\top. Then A⊤A=VΣ⊤ΣV⊤=Vdiag⁡(σi2)V⊤A^\top A = V\Sigma^\top\Sigma V^\top = V\operatorname{diag}(\sigma_i^2)V^\top. This is an SVD (and an eigendecomposition) of the symmetric positive definite A⊤AA^\top A, so its singular values are σi2\sigma_i^2 and κ2(A⊤A)=σ12/σn2=κ2(A)2\kappa_2(A^\top A) = \sigma_1^2/\sigma_n^2 = \kappa_2(A)^2. ■\blacksquare

    Meaning for the normal equations. Any method that works with A⊤AA^\top A meets condition number κ2\kappa^2. With κ(A)=106\kappa(A) = 10^6, QR keeps about 10 digits and the normal equations about 4. For κ(A)≳1/u≈108\kappa(A) \gtrsim 1/\sqrt{u} \approx 10^8, the computed A⊤AA^\top A can be exactly singular (the Läuchli example of lesson 12). The digits are lost when A⊤AA^\top A is formed, before any solver runs.

    Quick check

    A least-squares problem has κ2(A)=105\kappa_2(A) = 10^5. You solve it in double precision (u≈10−16u \approx 10^{-16}) with the normal equations A⊤Ax=A⊤bA^\top A x = A^\top b. About how many correct digits can you expect?

    Backward error and stability #

    The picture first. A solver returns x^\hat x instead of the exact xx. Ask a different question: for which data is x^\hat x the exact answer? Put b^=Ax^\hat b = A\hat x. Then x^\hat x solves Ax^=b^A\hat x = \hat b exactly. If b^\hat b is very close to bb, the solver did its job well: it solved a nearby problem exactly. The relative size of that change of the data is the backward error.

    Interactive figure: loading…

    Drag the computed answer x^\hat x in the left panel. The right panel shows the data: bb and b^=Ax^\hat b = A\hat x, the data for which x^\hat x is exact. The violet ellipse holds every answer with the same backward error as x^\hat x. It is κ\kappa times longer than it is wide. Press error along v₂: a tiny change of the data explains a large error in the answer. Raise κ\kappa and watch the ellipse stretch.

    Now the definitions. For a computed x^\hat x:

    • the forward error is ∥x^−x∥/∥x∥\|\hat x - x\|/\|x\|: how wrong the answer is. It is usually unknown, because xx is unknown.
    • the backward error is the smallest relative change of the data that makes x^\hat x exact. It is computable from the residual r=b−Ax^r = b - A\hat x.
      • If only bb may change: b^−b=−r\hat b - b = -r, so the backward error is ∥r∥/∥b∥\|r\|/\|b\|.
      • If only AA may change: min⁡{∥δA∥2/∥A∥2:(A+δA)x^=b}=∥r∥2/(∥A∥2 ∥x^∥2)\min\{\|\delta A\|_2/\|A\|_2 : (A + \delta A)\hat x = b\} = \|r\|_2 / (\|A\|_2\,\|\hat x\|_2). This is the usual definition for linear solvers.
    • An algorithm is backward stable if its backward error is O(u)O(u) for every input. Householder QR and SVD solvers are backward stable; Gaussian elimination with partial pivoting is backward stable in practice. The normal equations are not backward stable for least squares.
    Why the smallest change of AA has size ∥r∥/∥x^∥\|r\|/\|\hat x\|
    1. Any δA\delta A with (A+δA)x^=b(A + \delta A)\hat x = b satisfies δA x^=b−Ax^=r\delta A\,\hat x = b - A\hat x = r.
    2. So ∥r∥=∥δA x^∥≤∥δA∥ ∥x^∥\|r\| = \|\delta A\,\hat x\| \le \|\delta A\|\,\|\hat x\|, that is, ∥δA∥≥∥r∥/∥x^∥\|\delta A\| \ge \|r\|/\|\hat x\|.
    3. The rank-1 matrix δA=rx^⊤/∥x^∥2\delta A = r\hat x^\top/\|\hat x\|^2 reaches this value: δA x^=r\delta A\,\hat x = r, and the 2-norm of uv⊤uv^\top is ∥u∥ ∥v∥\|u\|\,\|v\|, so ∥δA∥2=∥r∥ ∥x^∥/∥x^∥2=∥r∥/∥x^∥\|\delta A\|_2 = \|r\|\,\|\hat x\|/\|\hat x\|^2 = \|r\|/\|\hat x\|.
    4. Divide by ∥A∥2\|A\|_2. ■\blacksquare

    Forward error from backward error. The computed x^\hat x is the exact solution for the data b^=b−r\hat b = b - r. Apply the error bound theorem with δb=−r\delta b = -r:

    ∥x^−x∥∥x∥  ≤  κ(A) ∥r∥∥b∥.\frac{\|\hat x - x\|}{\|x\|} \;\le\; \kappa(A)\,\frac{\|r\|}{\|b\|}.

    The same holds to first order for changes in AA. This gives the rule of thumb:

    forward error  ≲  κ×backward error  ≈  κ u(backward-stable algorithm).\text{forward error} \;\lesssim\; \kappa \times \text{backward error} \;\approx\; \kappa\,u \quad\text{(backward-stable algorithm)}.

    Conditioning belongs to the problem, stability to the algorithm, and the error is roughly their product. In double precision you lose about log⁡10κ\log_{10}\kappa of the 16 digits. A small residual does not mean an accurate xx when κ\kappa is large.

    Answers to section 1. (1) s=2048s = 2048. From 2048 on, the gap between fp16 numbers is 2, so 2048+12048 + 1 is a tie and rounds back to 2048 (ties go to the even significand). (2) About 16−10=616 - 10 = 6 digits (forward error ≈κu≈10−6\approx \kappa u \approx 10^{-6}). Yes, the residual is tiny, about u ∥A∥ ∥x∥u\,\|A\|\,\|x\|, because the solver is backward stable.

    4. Worked example #

    Two nearly parallel columns.

    A=(1111.0001),b=(22.0001)  ⇒  x=(11).A = \begin{pmatrix} 1 & 1 \\ 1 & 1.0001\end{pmatrix}, \qquad b = \begin{pmatrix} 2 \\ 2.0001\end{pmatrix} \;\Rightarrow\; x = \begin{pmatrix} 1 \\ 1\end{pmatrix}.

    Change b2b_2 in its fifth digit, b′=(2,2.0002)b' = (2, 2.0002). Then x′=(0,2)x' = (0, 2).

    • det⁡A=10−4\det A = 10^{-4} and A−1=(10001−10000−1000010000)A^{-1} = \begin{pmatrix} 10001 & -10000 \\ -10000 & 10000\end{pmatrix}.
    • AA is symmetric positive definite, so its singular values are its eigenvalues, the roots of λ2−2.0001λ+10−4=0\lambda^2 - 2.0001\lambda + 10^{-4} = 0: σ1≈2.00005\sigma_1 \approx 2.00005 and σ2≈5.0×10−5\sigma_2 \approx 5.0\times10^{-5}. So κ2(A)≈4.0×104\kappa_2(A) \approx 4.0\times10^4.
    • Bound: ∥δb∥/∥b∥=10−4/2.83=3.5×10−5\|\delta b\|/\|b\| = 10^{-4}/2.83 = 3.5\times10^{-5}; times κ\kappa gives 1.411.41. Actual: ∥δx∥/∥x∥=∥(−1,1)∥/∥(1,1)∥=1\|\delta x\|/\|x\| = \|(-1, 1)\|/\|(1, 1)\| = 1. The bound holds and is nearly sharp.
    • Normal equations would face κ2(A⊤A)=κ2(A)2≈1.6×109\kappa_2(A^\top A) = \kappa_2(A)^2 \approx 1.6\times10^9.
    • In double, np.linalg.solve(A, b) has relative error 2.2×10−122.2\times10^{-12}, inside κu≈4×104×1.1×10−16=4.4×10−12\kappa u \approx 4\times10^4 \times 1.1\times10^{-16} = 4.4\times10^{-12}.

    In a regression the two columns are two almost identical features (say, temperature at two neighbouring grid points). The data cannot separate their effects, so the two coefficients follow the noise.

    The same example, read backwards. Suppose a solver had returned x^=(0,2)\hat x = (0, 2) for the original bb. Step by step:

    1. Residual: r=b−Ax^=(2−2,  2.0001−2.0002)=(0,−10−4)r = b - A\hat x = (2 - 2,\; 2.0001 - 2.0002) = (0, -10^{-4}).
    2. Backward error in bb: ∥r∥/∥b∥=10−4/2.83=3.5×10−5\|r\|/\|b\| = 10^{-4}/2.83 = 3.5\times10^{-5}. With changes in AA: ∥r∥/(∥A∥2∥x^∥)=10−4/(2.00005×2)=2.5×10−5\|r\|/(\|A\|_2\|\hat x\|) = 10^{-4}/(2.00005 \times 2) = 2.5\times10^{-5}.
    3. Forward error: ∥x^−x∥/∥x∥=1\|\hat x - x\|/\|x\| = 1, that is, 100% wrong.
    4. Check the bound: κ×3.5×10−5≈1.41≥1\kappa \times 3.5\times10^{-5} \approx 1.41 \ge 1. ✓

    So an answer that is completely wrong is the exact answer of a problem whose data differs by 0.0035%. If bb comes from a measurement with 4 digits, x^\hat x is as good an answer as xx.

    Quick check

    For the matrix above (κ2≈4×104\kappa_2 \approx 4\times10^4), a backward-stable solver in double precision returns x^\hat x. About how many correct digits does x^\hat x have?

    Cancellation and its fix. f(x)=(1−cos⁡x)/x2f(x) = (1 - \cos x)/x^2 tends to 1/21/2 as x→0x \to 0. It is very well conditioned there: f(x)≈12−x2/24f(x) \approx \tfrac12 - x^2/24, so κf(x)=∣xf′(x)/f(x)∣≈x2/6\kappa_f(x) = |xf'(x)/f(x)| \approx x^2/6.

    xx naive (1−cos⁡x)/x2(1 - \cos x)/x^2 rewrite 2sin⁡2(x/2)/x22\sin^2(x/2)/x^2
    10−410^{-4} 0.49999999696126450.4999999969612645 (rel. error 5×10−95\times10^{-9}) 0.49999999958333340.4999999995833334
    10−610^{-6} 0.50004445029117050.5000444502911705 (rel. error 9×10−59\times10^{-5}) 0.49999999999995830.4999999999999583
    10−810^{-8} 00 (rel. error 1) 0.50.5

    At x=10−8x = 10^{-8}, cos⁡x=1−5×10−17\cos x = 1 - 5\times10^{-17} rounds to exactly 1 (the gap below 1 is 1.1×10−161.1\times10^{-16}), so all information is gone before the subtraction. The rewrite has no subtraction and is correct to about 10−1610^{-16} for every xx. The problem was well conditioned; only the formula was unstable.

    5. In code #

    NumPy shows the facts of this lesson. NumPy has no bfloat16 type, so a three-line helper rounds float32 values to bf16 by cutting the last 16 bits with round-to-nearest-even.

    import numpy as np
    from decimal import Decimal
    
    def bf16(x):
        """Round to the nearest bfloat16 (ties to even); the result is a float32 that bf16 can store."""
        b = np.asarray(x, dtype=np.float32).view(np.uint32).astype(np.uint64)
        b = (b + 0x7FFF + ((b >> 16) & 1)) & 0xFFFF0000
        return b.astype(np.uint32).view(np.float32)
    
    for t in (np.float64, np.float32, np.float16):
        fi = np.finfo(t)
        print(t.__name__, fi.eps, fi.max, fi.smallest_normal)   # ε, largest, smallest normal
    print("bfloat16 eps", bf16(1 + 2**-7) - 1)            # 0.0078125 = 2^-7
    print(np.spacing(1.0), np.spacing(1024.0))            # 2.2e-16, 2.3e-13: the gap grows with |x|
    print(Decimal(0.1 + 0.2))                             # 0.3000000000000000444...
    print(Decimal(0.3))                                   # 0.2999999999999999888...: a different double
    
    bits = np.array([-6.5], dtype=np.float16).view(np.uint16)[0]
    print(f"{bits:016b}")                                 # 1100011010000000 = 1 | 10001 | 1010000000
    
    s = np.float16(0)
    for _ in range(10_000):
        s = np.float16(s + np.float16(1))
    print(s)                                              # 2.048e+03 (= 2048): swamping
    print(bf16(bf16(1.0) + bf16(1e-3)))                   # 1.0: the update is lost
    print(np.float16(1e-8), np.float16(1e-8 * 2**16))     # 0.0 (underflow), 0.000655 (after scaling)
    print(bf16(1e-8))                                     # 1.0011718e-08: bf16 has the range of fp32
    

    The cancellation table:

    import numpy as np
    
    np.set_printoptions(precision=16)
    x = np.array([1e-4, 1e-6, 1e-8])
    print((1 - np.cos(x)) / x**2)          # [0.4999999969612645 0.5000444502911705 0.]: wrong digits
    print(2 * np.sin(x / 2)**2 / x**2)     # [0.4999999995833334 0.4999999999999583 0.5]: all correct
    

    Conditioning, and a tiny backward error with a large forward error:

    import numpy as np
    from scipy.linalg import hilbert
    
    A = np.array([[1., 1.], [1., 1.0001]])
    b = np.array([2., 2.0001])
    print(np.linalg.cond(A), np.linalg.cond(A.T @ A))     # 4.0e4 and 1.6e9 = κ²
    print(np.linalg.solve(A, b))                          # [1. 1.]
    print(np.linalg.solve(A, [2., 2.0002]))               # [0. 2.] (up to ~1e-16)
    
    x_hat = np.array([0., 2.])                            # a wrong answer for the original b
    r = b - A @ x_hat
    print(f"{np.linalg.norm(r) / np.linalg.norm(b):.1e}")  # 3.5e-05: backward error in b; forward error is 1
    
    H = hilbert(10)                                       # κ ≈ 1.6e13
    x_true = np.ones(10)
    b = H @ x_true
    x = np.linalg.solve(H, b)                             # LU with partial pivoting
    backward = np.linalg.norm(b - H @ x) / (np.linalg.norm(H, 2) * np.linalg.norm(x))
    forward = np.linalg.norm(x - x_true) / np.linalg.norm(x_true)
    print(f"cond {np.linalg.cond(H):.1e}  backward {backward:.0e}  forward {forward:.0e}")
    # cond 1.6e+13  backward 1e-16  forward 4e-04: tiny residual, only about 3-4 correct digits
    

    6. Where it shows up #

    • Mixed-precision training. torch.autocast runs matmuls in bf16 or fp16 and keeps numerically sensitive ops such as softmax, layer norm and the losses in fp32; GradScaler is for fp16. fp8 formats (E4M3, E5M2) have even less range and precision, so fp8 training uses per-tensor or per-block scale factors: the loss-scaling idea, applied to every tensor.
    • Stable formulas in ML code. Log-sum-exp subtracts the maximum before exp (an overflow fix); log1p, expm1 and softplus avoid cancellation in losses and log-probabilities; normalization layers and streaming statistics use two-pass or Welford variance.
    • ML for science. Physical fields have large means and small anomalies: temperature near 288 K with anomalies of about 1 K, surface pressure near 10510^5 Pa. A variance computed as x2‾−xˉ2\overline{x^2} - \bar x^2 in fp32 loses about 5 of its 7 digits; work with anomalies. Kernel (Gram) matrices of smooth kernels are extremely ill-conditioned, which is why Gaussian processes add a small jitter δI\delta I. Discretized differential operators have κ\kappa growing like h−2h^{-2} with the grid spacing hh, which slows iterative solvers (lesson 14).
    • Regression diagnostics. A large κ(X)\kappa(X) means some combination of features is nearly zero. Rescale columns, remove collinear features, or use ridge: X⊤X+λIX^\top X + \lambda I has condition number (σ12+λ)/(σn2+λ)(\sigma_1^2 + \lambda)/(\sigma_n^2 + \lambda).

    7. Common mistakes #

    • Taking ε\varepsilon for the smallest positive float. ε\varepsilon is the gap at 1; the smallest positive double is about 5×10−3245\times10^{-324} (a subnormal). Also, books use "machine precision" for both ε\varepsilon and u=ε/2u = \varepsilon/2: say which one you mean.
    • Blaming the problem for an unstable formula, or the algorithm for an ill-conditioned problem. Ask both questions: how large is κ\kappa, and does the algorithm add error beyond κu\kappa u?
    • Reading a small residual as an accurate solution. The residual measures backward error; the forward error can be κ\kappa times larger.
    • Judging near-singularity by det⁡A\det A. Use κ\kappa, or σmin⁡/σmax⁡\sigma_{\min}/\sigma_{\max}.
    • Treating loss scaling as the bf16 fix while sums run in 16 bits. bf16's problem is precision, not range: accumulate in fp32.

    8. Check yourself #

    1. What is the gap between doubles near 10001000?
      Answer1000∈[29,210)1000 \in [2^9, 2^{10}), so the gap is ε⋅29=2−43≈1.14×10−13\varepsilon \cdot 2^9 = 2^{-43} \approx 1.14\times10^{-13}.
    2. Rewrite x+1−x\sqrt{x + 1} - \sqrt{x} for large xx. How bad is the naive formula at x=1012x = 10^{12}?
      Answer1/(x+1+x)1/(\sqrt{x + 1} + \sqrt{x}). The true value is ≈5.0×10−7\approx 5.0\times10^{-7}; the naive formula has relative error ≈7.6×10−6\approx 7.6\times10^{-6} (the two square roots agree in their first 13 digits), the rewrite ≈10−16\approx 10^{-16}.
    3. What are κ2(diag⁡(10,0.1))\kappa_2(\operatorname{diag}(10, 0.1)) and κ2(0.1 I100)\kappa_2(0.1\,I_{100})?
      Answer10/0.1=10010/0.1 = 100, and 11: all singular values of 0.1 I0.1\,I are equal, although det⁡=10−100\det = 10^{-100}.
    4. Why does bf16 training usually need no loss scaling, but still needs fp32 accumulation?
      Answerbf16 has 8 exponent bits like fp32, so small gradients do not underflow. It has only 8 bits of precision, so long sums swamp (256+1=256256 + 1 = 256) and updates below half the gap at ww vanish: accumulate and update in fp32.
    5. A solver returns x^\hat x with ∥b−Ax^∥/(∥A∥∥x^∥)=10−16\|b - A\hat x\|/(\|A\|\|\hat x\|) = 10^{-16}, and κ(A)=1013\kappa(A) = 10^{13}. Is x^\hat x accurate?
      AnswerThe backward error is tiny (the solver is backward stable), but the forward error can be up to κ×10−16=10−3\kappa \times 10^{-16} = 10^{-3}: only about 3 correct digits. The Hilbert matrix of size 10 shows exactly this (4×10−44\times10^{-4}).
    Recall cardsAnswer in your head first, then reveal. Add the cards to your review deck to see them again just before you would forget.
    1. What are machine epsilon ε\varepsilon and the unit roundoff uu in double precision?

    2. Decode the fp16 bits 0 10000 1000000000 (sign, exponent, fraction; bias 15).

    3. What is catastrophic cancellation?

    4. What does bf16 trade compared with fp16?

    5. Why do 16-bit matmuls and reductions accumulate in fp32?

    6. Write κ2(A)\kappa_2(A) with singular values.

    7. State the error bound for Ax=bAx = b when bb changes by δb\delta b.

    8. What is κ2(A⊤A)\kappa_2(A^\top A), and why does it matter?

    9. What is the backward error of a computed solution x^\hat x of Ax=bAx = b?

    10. Conditioning versus stability: which belongs to the problem, which to the algorithm, and how do they combine?

    Open the review →

    9. Interview angle #

    • "Why does fp16 training need loss scaling and bf16 does not?" — fp16 has 5 exponent bits (smallest subnormal 6×10−86\times10^{-8}, max 65504), so small gradients underflow; bf16 has fp32's 8 exponent bits. Both need fp32 accumulation and master weights.
    • "What is the condition number, and why should an ML engineer care?" — σmax⁡/σmin⁡\sigma_{\max}/\sigma_{\min}; you lose about log⁡10κ\log_{10}\kappa digits, κ(X⊤X)=κ(X)2\kappa(X^\top X) = \kappa(X)^2, and gradient descent on a quadratic converges at a rate set by the Hessian's κ\kappa.
    • "Your loss became NaN after switching to fp16. What do you check?" — overflow above 65504 in activations or logits, missing or badly tuned loss scaling, softmax or loss computed in 16 bits, and constants such as an ϵ=10−8\epsilon = 10^{-8} that become 0 in fp16.

    10. Practice #

    The notebook for this lesson runs in Colab. It has five exercises, each with a check cell and a folded solution:

    1. Find machine epsilon of float16, float32 and float64 by halving until 1+e=11 + e = 1, and compare with np.finfo.
    2. Rewrite (1−cos⁡x)/x2(1 - \cos x)/x^2 and x+1−x\sqrt{x + 1} - \sqrt{x} stably, and test them against exact values from Python's decimal module.
    3. Write a stable quadratic solver and compare it with the textbook formula on x2−108x+1x^2 - 10^8x + 1.
    4. Write Welford's one-pass variance and compare it with x2‾−xˉ2\overline{x^2} - \bar x^2 on data with a large mean.
    5. Write condition_number and backward_error, check κ(A⊤A)=κ(A)2\kappa(A^\top A) = \kappa(A)^2 on random matrices, and compare backward and forward error on the Hilbert matrix.

    11. Watch and read #

    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Open on YouTube · the player loads from youtube-nocookie.com when you press play