AI Knowledge Tree

Conditioning and numerical stability

Distinguish the conditioning of a problem from the stability of an algorithm, and bound error with the condition number.

A numerical answer can be wrong for two reasons: the problem itself amplifies input perturbations (it is ill-conditioned), or the algorithm introduces excess rounding error (it is unstable). These are independent. A well-conditioned problem solved by an unstable algorithm gives a wrong answer; an ill-conditioned problem solved by the perfect algorithm still gives a wrong answer. Knowing which is which tells you whether to change the algorithm or change the problem.

Conditioning: a property of the problem

The condition number of a problem measures how much the output changes when the input is perturbed. For a function f(x)f(x), the relative condition number is

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

It is the worst-case ratio of relative output change to relative input change[1]. If the input is known to floating-point precision εmachine\varepsilon_{\text{machine}}, the output is unavoidably uncertain by about κ⋅εmachine\kappa \cdot \varepsilon_{\text{machine}}, regardless of how the computation is performed.

The condition number of a linear system

For Ax=bAx = b with AA invertible, perturbing bb by Δb\Delta b perturbs xx by Δx=A−1Δb\Delta x = A^{-1}\Delta b. The condition number of solving the system is

κ(A)=∥A∥ ∥A−1∥,\kappa(A) = \lVert A\rVert\,\lVert A^{-1}\rVert,

and in the 2-norm equals the ratio of largest to smallest singular value:

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

Derivation. From b=Axb = Ax, ∥b∥≤∥A∥ ∥x∥\lVert b\rVert \le \lVert A\rVert\,\lVert x\rVert, so ∥x∥≥∥b∥/∥A∥\lVert x\rVert \ge \lVert b\rVert/\lVert A\rVert. From Δx=A−1Δb\Delta x = A^{-1}\Delta b, ∥Δx∥≤∥A−1∥ ∥Δb∥\lVert \Delta x\rVert \le \lVert A^{-1}\rVert\,\lVert\Delta b\rVert. Dividing,

∥Δx∥∥x∥≤∥A∥ ∥A−1∥ ∥Δb∥∥b∥,\frac{\lVert \Delta x\rVert}{\lVert x\rVert} \le \lVert A\rVert\,\lVert A^{-1}\rVert\,\frac{\lVert\Delta b\rVert}{\lVert b\rVert},

identifying κ(A)\kappa(A) as the amplification factor. ■\qquad\blacksquare

A matrix with κ=1012\kappa = 10^{12} loses 12 digits of precision: even the perfect solver, with double-precision input, returns only 4 correct digits.

Stability: a property of the algorithm

An algorithm is backward stable if the computed answer is the exact answer to a slightly perturbed problem: y^=f(x+Δx)\hat y = f(x + \Delta x) with ∥Δx∥=O(εmachine)∥x∥\lVert \Delta x\rVert = O(\varepsilon_{\text{machine}})\lVert x\rVert[2]. Backward stability is the strongest practical guarantee, because combined with conditioning it yields the forward error bound

∥y^−y∥∥y∥≲κ(problem)⋅εmachine.\frac{\lVert \hat y - y\rVert}{\lVert y\rVert} \lesssim \kappa(\text{problem}) \cdot \varepsilon_{\text{machine}}.

The total error is the product of problem conditioning and algorithm stability. A stable algorithm cannot improve a bad condition number; an unstable algorithm contaminates even a perfectly conditioned problem.

Two routes for least squares

The normal equations A⊤Ax=A⊤bA^\top A x = A^\top b have condition number κ(A⊤A)=κ(A)2\kappa(A^\top A) = \kappa(A)^2: squaring the conditioning of the original problem. The QR or SVD routes (from the A1 least-squares lesson) keep the conditioning at κ(A)\kappa(A) rather than κ(A)2\kappa(A)^2[3]. Both routes solve the same problem; their stability differs.

Solving Ax = b in double precision: the relative error grows in proportion to the condition number κ, staying under the κ·ε worst-case bound (dashed): each extra factor of 10 in κ costs roughly one more digit of accuracy.
Solving Ax = b in double precision: the relative error grows in proportion to the condition number κ, staying under the κ·ε worst-case bound (dashed): each extra factor of 10 in κ costs roughly one more digit of accuracy.

Check it on arrays

python · numpy
import numpy as np

rng = np.random.default_rng(0)
eps = np.finfo(float).eps

# Build a matrix with controlled condition number via the SVD.
def make_A(n, kappa):
    U = np.linalg.qr(rng.normal(size=(n, n)))[0]
    V = np.linalg.qr(rng.normal(size=(n, n)))[0]
    s = np.geomspace(1.0, 1.0 / kappa, n)
    return U @ np.diag(s) @ V.T

n = 50
for kappa in [1e2, 1e6, 1e12]:
    A = make_A(n, kappa)
    x_true = rng.normal(size=n)
    b = A @ x_true
    x_hat = np.linalg.solve(A, b)
    err = np.linalg.norm(x_hat - x_true) / np.linalg.norm(x_true)
    bound = kappa * eps
    print(f"kappa={kappa:>.0e}: rel error {err:.2e}, bound kappa*eps {bound:.2e}")

# Squaring the conditioning: solve via normal equations vs QR
A = make_A(n, 1e6)
b = A @ rng.normal(size=n)
x_normal = np.linalg.solve(A.T @ A, A.T @ b)             # uses kappa^2
Q, R = np.linalg.qr(A); x_qr = np.linalg.solve(R, Q.T @ b)  # uses kappa
truth = np.linalg.solve(A, b)
print("normal-eq error:", np.linalg.norm(x_normal - truth))
print("QR-route error: ", np.linalg.norm(x_qr - truth))

The relative error grows with the condition number as predicted, and the normal- equations route loses precision the QR route preserves.

Exercises

Exercise 1. A matrix AA has σmax⁡=10\sigma_{\max} = 10 and σmin⁡=10−5\sigma_{\min} = 10^{-5}. Compute κ2(A)\kappa_2(A), and state how many correct digits a backward-stable solver of Ax=bAx = b returns when the input is given to double precision (εmachine≈2.2×10−16\varepsilon_{\text{machine}} \approx 2.2 \times 10^{-16}).

Solution

Step 1. The 2-norm condition number is the ratio of extreme singular values:

κ2(A)=σmax⁡σmin⁡=1010−5=106.\kappa_2(A) = \frac{\sigma_{\max}}{\sigma_{\min}} = \frac{10}{10^{-5}} = 10^{6}.

Step 2. A backward-stable solver achieves the forward-error bound ∥x^−x∥∥x∥≲κ2(A)⋅εmachine≈106⋅2.2×10−16=2.2×10−10\frac{\lVert \hat x - x\rVert}{\lVert x\rVert} \lesssim \kappa_2(A) \cdot \varepsilon_{\text{machine}} \approx 10^{6} \cdot 2.2 \times 10^{-16} = 2.2 \times 10^{-10}.

Step 3. A relative error near 10−1010^{-10} leaves about 1010 correct digits. Of the 1616 digits double precision supplies, the condition number 10610^6 consumes 66, matching the rule that each factor of 1010 in κ\kappa costs one digit.

Exercise 2. Show that for the normal equations A⊤Ax=A⊤bA^\top A x = A^\top b, the condition number satisfies κ2(A⊤A)=κ2(A)2\kappa_2(A^\top A) = \kappa_2(A)^2. Use the SVD A=UΣV⊤A = U \Sigma V^\top.

Solution

Step 1. Write A=UΣV⊤A = U \Sigma V^\top with U,VU, V orthogonal and Σ\Sigma diagonal holding the singular values σi\sigma_i. Then

A⊤A=VΣ⊤U⊤UΣV⊤=VΣ2V⊤,A^\top A = V \Sigma^\top U^\top U \Sigma V^\top = V \Sigma^2 V^\top,

using U⊤U=IU^\top U = I. This is the eigendecomposition of A⊤AA^\top A, so its singular values are the diagonal entries σi2\sigma_i^2.

Step 2. The extreme singular values of A⊤AA^\top A are therefore σmax⁡2\sigma_{\max}^2 and σmin⁡2\sigma_{\min}^2, giving

κ2(A⊤A)=σmax⁡2σmin⁡2=(σmax⁡σmin⁡)2=κ2(A)2.\kappa_2(A^\top A) = \frac{\sigma_{\max}^2}{\sigma_{\min}^2} = \left(\frac{\sigma_{\max}}{\sigma_{\min}}\right)^2 = \kappa_2(A)^2.

Step 3. Forming A⊤AA^\top A squares the conditioning, so a problem with κ(A)=106\kappa(A) = 10^6 becomes κ(A⊤A)=1012\kappa(A^\top A) = 10^{12}: the normal equations can lose twice as many digits as a route that keeps κ(A)\kappa(A), even though both solve the same least-squares problem.

Exercise 3 (code). Build a matrix with controlled κ2=108\kappa_2 = 10^8 via its SVD, solve Ax=bAx = b for a known xx, and confirm the relative error stays under the bound κ2⋅εmachine\kappa_2 \cdot \varepsilon_{\text{machine}}.

Solution
python · numpy
import numpy as np

rng = np.random.default_rng(0)
eps = np.finfo(float).eps
n, kappa = 40, 1e8

# Controlled condition number: singular values geomspaced from 1 to 1/kappa.
U = np.linalg.qr(rng.normal(size=(n, n)))[0]
V = np.linalg.qr(rng.normal(size=(n, n)))[0]
s = np.geomspace(1.0, 1.0 / kappa, n)
A = U @ np.diag(s) @ V.T

assert np.isclose(np.linalg.cond(A), kappa, rtol=1e-3)

x_true = rng.normal(size=n)
b = A @ x_true
x_hat = np.linalg.solve(A, b)
rel_err = np.linalg.norm(x_hat - x_true) / np.linalg.norm(x_true)

assert rel_err <= kappa * eps          # forward-error bound holds
print("rel error:", rel_err, "  bound kappa*eps:", kappa * eps)

The relative error lands below κ2⋅εmachine≈2.2×10−8\kappa_2 \cdot \varepsilon_{\text{machine}} \approx 2.2 \times 10^{-8}: the backward-stable solve is as accurate as the conditioning of the problem allows, no better and no worse.

The next lesson zooms in on the most common source of stability loss: subtracting near-equal numbers.


References

  1. [1]Trefethen, L. N., & Bau, D. (1997). Numerical Linear Algebra. SIAM. Conditioning, stability, and the condition number (Lectures 12-15).↩
  2. [2]Higham, N. J. (2002). Accuracy and Stability of Numerical Algorithms (2nd ed.). SIAM. The definitive treatment of backward stability and forward error (doi:10.1137/1.9780898718027).↩
  3. [3]Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations (4th ed.). Johns Hopkins University Press. Normal equations versus QR for least squares.↩