Skip to content
The Lumenary
Advanced undergraduateUndergraduate 120 min

Numerical analysis

A computer does arithmetic with about sixteen significant digits. When can you trust its answers? The condition number of the problem, the stability of the algorithm and the order of the method decide.

Formally verified · 16 LeanComputationally tested · 2 PythonAI-drafted · awaiting human reviewMathematically reviewed: Awaiting human review
MotivationH

Sixteen digits are not enough — or are they?

On 25 February 1991 a Patriot missile battery at Dhahran failed to intercept an incoming missile, and 28 soldiers died. The system counted time in tenths of a second and converted to seconds by multiplying by 0.10.1, stored in a 24-bit register. But 0.10.1 has no finite binary expansion, and the stored value was too small by about 9.5×10−89.5 \times 10^{-8}. After 100 hours of continuous operation the clock had drifted by 0.340.34 seconds, during which the target travelled more than half a kilometre (US General Accounting Office report, 1992).

Rounding errors are usually harmless. Every weather forecast, every bridge designed by finite elements and every GPS fix solves enormous systems of equations in floating point, and the answers are excellent. Numerical analysis is the mathematics that explains when and why, and it rests on three questions.

  • Is the problem sensitive? Some problems amplify any small change in their data. That is a property of the problem — its condition number — and no algorithm can repair it.
  • Is the algorithm stable? A good algorithm adds no more error than the inevitable rounding of the data. Gaussian elimination with pivoting is good; without pivoting it can fail on an innocent 2×22 \times 2 system.
  • How fast does the method converge? Discretising a differential equation with step hh gives an error like hph^p, where pp is the order, provided the method is stable — and for "stiff" problems, stability can matter far more than order.

This lesson develops these ideas for the two workhorses of scientific computing: linear algebra (linear systems, least squares and eigenvalues) and ordinary differential equations.

IntuitionH

Problems, algorithms, and who is to blame

Think of a problem as a function ff from data to answers, and of an algorithm as a computed approximation f~\tilde f. The forward error ∣f~(x)−f(x)∣|\tilde f(x) - f(x)| is what we care about but usually cannot measure. The backward error asks a cleverer question: for which nearby data x+δxx + \delta x is the computed answer exact? If f~(x)=f(x+δx)\tilde f(x) = f(x + \delta x) with δx\delta x as small as the rounding of the data, the algorithm is backward stable: it has answered a question we could not distinguish from ours.

Then the error splits into two factors:

forward error  ≲  condition number×backward error.\text{forward error} \;\lesssim\; \text{condition number} \times \text{backward error}.

The algorithm controls the second factor and the problem controls the first. Geometrically, a matrix AA maps the unit sphere to an ellipsoid. If the ellipsoid is very thin, two very different inputs have nearly the same image, and inverting AA magnifies errors by the ratio of its longest to its shortest axis. That ratio is the condition number.

unit circleκ ≈ 1.3κ ≈ 9
A well-conditioned matrix maps the unit circle to a round ellipse; an ill-conditioned one squashes it into a thin ellipse. The ratio of the semi-axes, $\sigma_{\max}/\sigma_{\min}$, is the 2-norm condition number $\kappa_2(A)$.

For differential equations the analogue of backward stability is stability of the scheme: errors made in early steps must not be amplified by later ones. A method that is consistent (each step is accurate to some order) and stable converges. Stiff problems contain a rapidly decaying component that is invisible in the solution, but an explicit method must still take tiny steps to keep that component from exploding.

DefinitionsH

Definitions

Definition— floating-point model

A floating-point system with base 2 and tt significant bits has unit roundoff u=2−tu = 2^{-t} (u=2−53≈1.1×10−16u = 2^{-53} \approx 1.1 \times 10^{-16} in IEEE double precision). Every real xx in range is rounded to fl(x)=x(1+δ)\mathrm{fl}(x) = x(1 + \delta) with ∣δ∣≤u|\delta| \le u, and the basic operations satisfy fl(x∘y)=(x∘y)(1+δ)\mathrm{fl}(x \circ y) = (x \circ y)(1 + \delta), ∣δ∣≤u|\delta| \le u, for ∘∈{+,−,×,÷}\circ \in \{+, -, \times, \div\}.

Definition— norms and condition number

For a vector norm ∥⋅∥\|\cdot\| on Rn\R^n, the induced matrix norm is ∥A∥=max⁡x≠0∥Ax∥/∥x∥\|A\| = \max_{x \ne 0}\|Ax\|/\|x\|, so that ∥Ax∥≤∥A∥∥x∥\|Ax\| \le \|A\|\|x\| and ∥AB∥≤∥A∥∥B∥\|AB\| \le \|A\|\|B\|. The condition number of an invertible matrix is κ(A)=∥A∥ ∥A−1∥\kappa(A) = \|A\|\,\|A^{-1}\|. In the 2-norm, κ2(A)=σmax⁡/σmin⁡\kappa_2(A) = \sigma_{\max}/\sigma_{\min}, the ratio of the largest and smallest singular values.

Definition— forward and backward error, backward stability

Let an algorithm compute y~\tilde y as an approximation to y=f(x)y = f(x). The forward error is ∥y~−y∥\|\tilde y - y\| (relative: divided by ∥y∥\|y\|). The backward error is the smallest ∥δx∥/∥x∥\|\delta x\|/\|x\| such that y~=f(x+δx)\tilde y = f(x + \delta x). The algorithm is backward stable if its backward error is O(u)O(u) for all data.

Definition— LU and QR factorisations

An LU factorisation writes A=LUA = LU with LL unit lower triangular and UU upper triangular. With partial pivoting, PA=LUPA = LU for a permutation matrix PP, where at step kk the row with the largest ∣aik∣|a_{ik}|, i≥ki \ge k, is swapped into the pivot position. The growth factor is ρ=max⁡i,j∣uij∣/max⁡i,j∣aij∣\rho = \max_{i,j}|u_{ij}| / \max_{i,j}|a_{ij}|. A QR factorisation of A∈Rm×nA \in \R^{m\times n} (m≥nm \ge n, full rank) writes A=QRA = QR with Q∈Rm×nQ \in \R^{m\times n} having orthonormal columns and RR upper triangular with positive diagonal (the reduced form).

Definition— one-step methods, order, stability function

For y′=f(t,y)y' = f(t, y), a one-step method computes yn+1=yn+h Φ(tn,yn;h)y_{n+1} = y_n + h\,\Phi(t_n, y_n; h). Its local truncation error is τn=y(tn+1)−y(tn)−hΦ(tn,y(tn);h)\tau_{n} = y(t_{n+1}) - y(t_n) - h\Phi(t_n, y(t_n); h) for the exact solution yy. It has order pp if the global error max⁡nh≤T∣y(tn)−yn∣\max_{n h \le T}|y(t_n) - y_n| is O(hp)O(h^p) for smooth problems. Applied to the test equation y′=λyy' = \lambda y, a Runge–Kutta method gives yn+1=R(hλ)yny_{n+1} = R(h\lambda)y_n, and RR is its stability function. The method is A-stable if ∣R(z)∣≤1|R(z)| \le 1 whenever Re⁡z≤0\operatorname{Re} z \le 0.

Worked examplesH

Examples

Example— A nearly singular system

The system x+y=2x + y = 2, x+1.0001y=2.0001x + 1.0001y = 2.0001 has solution (1,1)(1, 1). Change the right-hand side to (2,2.0002)(2, 2.0002), a relative change of about 3.5×10−53.5 \times 10^{-5}, and the solution becomes (0,2)(0, 2). Here A=(1111.0001)A = \begin{pmatrix}1 & 1\\1 & 1.0001\end{pmatrix}, A−1=104(1.0001−1−11)A^{-1} = 10^4\begin{pmatrix}1.0001 & -1\\-1 & 1\end{pmatrix}, and κ∞(A)=2.0001×2.0001×104≈4×104\kappa_\infty(A) = 2.0001 \times 2.0001 \times 10^4 \approx 4 \times 10^4. The two equations describe almost parallel lines, and a tiny shift of one moves their intersection a long way.

Example— Condition numbers to know

  • Orthogonal matrices: κ2(Q)=1\kappa_2(Q) = 1, the best possible. Multiplying by them never amplifies errors, which is why QR is so stable.
  • Diagonal matrices: κ(diag(di))=max⁡∣di∣/min⁡∣di∣\kappa(\mathrm{diag}(d_i)) = \max|d_i| / \min|d_i| in any pp-norm.
  • Hilbert matrices Hij=1i+j−1H_{ij} = \frac{1}{i + j - 1}: κ2(Hn)\kappa_2(H_n) grows roughly like e3.5ne^{3.5n}, so already κ2(H12)≈2×1016\kappa_2(H_{12}) \approx 2 \times 10^{16} — numerically singular in double precision.

Example— Euler's method on y′ = λy

Explicit Euler gives yn+1=yn+hλyn=(1+hλ)yny_{n+1} = y_n + h\lambda y_n = (1 + h\lambda)y_n, so R(z)=1+zR(z) = 1 + z, while the exact solution is multiplied by ehλe^{h\lambda} per step. For λ=−50\lambda = -50 and h=0.05h = 0.05, R=−1.5R = -1.5: the numerical solution oscillates with growing amplitude while the true one decays like e−50te^{-50t}. Backward Euler, yn+1=yn+hλyn+1y_{n+1} = y_n + h\lambda y_{n+1}, gives R(z)=11−z=13.5R(z) = \frac{1}{1 - z} = \frac{1}{3.5} here, decaying as it should.

ProofH

Conditioning of linear systems

Theorem— Theorem 1 (perturbing the right-hand side)

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 for each AA there are bb and δb\delta b for which equality holds.

Proof

Subtracting, A δx=δbA\,\delta x = \delta b, so δx=A−1δb\delta x = A^{-1}\delta b and ∥δx∥≤∥A−1∥∥δb∥\|\delta x\| \le \|A^{-1}\|\|\delta b\|. Also ∥b∥=∥Ax∥≤∥A∥∥x∥\|b\| = \|Ax\| \le \|A\|\|x\|, so 1∥x∥≤∥A∥∥b∥\frac{1}{\|x\|} \le \frac{\|A\|}{\|b\|}. Multiply the two inequalities. For equality, choose xx with ∥Ax∥=∥A∥∥x∥\|Ax\| = \|A\|\|x\| and δb\delta b with ∥A−1δb∥=∥A−1∥∥δb∥\|A^{-1}\delta b\| = \|A^{-1}\|\|\delta b\|; both exist because the maximum defining an induced norm is attained.

∎

Theorem— Theorem 2 (perturbing the matrix)

Let AA be invertible and ∥A−1∥∥δA∥<1\|A^{-1}\|\|\delta A\| < 1. If Ax=bAx = b and (A+δA)(x+δx)=b+δb(A + \delta A)(x + \delta x) = b + \delta b with b≠0b \ne 0, then A+δAA + \delta A is invertible and

∥δx∥∥x∥≤κ(A)1−κ(A)∥δA∥∥A∥(∥δA∥∥A∥+∥δb∥∥b∥).\frac{\|\delta x\|}{\|x\|} \le \frac{\kappa(A)}{1 - \kappa(A)\frac{\|\delta A\|}{\|A\|}}\left(\frac{\|\delta A\|}{\|A\|} + \frac{\|\delta b\|}{\|b\|}\right).

To first order: relative forward error ≤κ(A)×\le \kappa(A) \times relative backward error.

Proof

If (A+δA)v=0(A + \delta A)v = 0 then v=−A−1δA vv = -A^{-1}\delta A\,v, so ∥v∥≤∥A−1∥∥δA∥∥v∥\|v\| \le \|A^{-1}\|\|\delta A\|\|v\|, forcing v=0v = 0: A+δAA + \delta A is invertible. Expanding and using Ax=bAx = b, we get A δx=δb−δA (x+δx)A\,\delta x = \delta b - \delta A\,(x + \delta x), so

∥δx∥≤∥A−1∥(∥δb∥+∥δA∥∥x∥+∥δA∥∥δx∥).\|\delta x\| \le \|A^{-1}\|\big(\|\delta b\| + \|\delta A\|\|x\| + \|\delta A\|\|\delta x\|\big).

Move the last term to the left: (1−∥A−1∥∥δA∥)∥δx∥≤∥A−1∥(∥δb∥+∥δA∥∥x∥)(1 - \|A^{-1}\|\|\delta A\|)\|\delta x\| \le \|A^{-1}\|(\|\delta b\| + \|\delta A\|\|x\|). Divide by ∥x∥\|x\|, and use ∥δb∥/∥x∥≤∥A∥∥δb∥/∥b∥\|\delta b\|/\|x\| \le \|A\|\|\delta b\|/\|b\| and ∥A−1∥∥δA∥=κ(A)∥δA∥/∥A∥\|A^{-1}\|\|\delta A\| = \kappa(A)\|\delta A\|/\|A\|.

∎

Since rounding the data alone causes relative perturbations of size uu, no algorithm can promise relative accuracy better than about κ(A) u\kappa(A)\,u. The rule of thumb: with κ≈10k\kappa \approx 10^k, expect to lose about kk of the 1616 significant decimal digits.

ProofH

Gaussian elimination as LU factorisation

Gaussian elimination subtracts multiples ℓik=aik(k)/akk(k)\ell_{ik} = a^{(k)}_{ik}/a^{(k)}_{kk} of the pivot row kk from the rows below it. Each step is multiplication by a unit lower triangular matrix LkL_k, and the product of their inverses is the unit lower triangular matrix LL of multipliers. So elimination computes A=LUA = LU. Solving Ax=bAx = b then takes a forward substitution Ly=bLy = b and a back substitution Ux=yUx = y.

Theorem— Theorem 3 (existence and uniqueness of LU)

An invertible A∈Rn×nA \in \R^{n\times n} has a factorisation A=LUA = LU (LL unit lower triangular, UU upper triangular) if and only if all its leading principal submatrices Ak=A[1..k,1..k]A_k = A[1..k, 1..k] are invertible. The factorisation is then unique.

Proof

Necessity. If A=LUA = LU, then Ak=LkUkA_k = L_kU_k (the leading blocks multiply, because LL is lower and UU upper triangular). Since det⁡A=det⁡U=∏uii≠0\det A = \det U = \prod u_{ii} \ne 0, every det⁡Ak=∏i≤kuii≠0\det A_k = \prod_{i\le k}u_{ii} \ne 0.

Existence, by induction on nn. Write A=(An−1crTa)A = \begin{pmatrix}A_{n-1} & c\\ r^T & a\end{pmatrix} and, by induction, An−1=L′U′A_{n-1} = L'U' with U′U' invertible. Seek L=(L′0ℓT1)L = \begin{pmatrix}L' & 0\\ \ell^T & 1\end{pmatrix} and U=(U′w0υ)U = \begin{pmatrix}U' & w\\ 0 & \upsilon\end{pmatrix}. Comparing blocks requires L′w=cL'w = c, ℓTU′=rT\ell^TU' = r^T and ℓTw+υ=a\ell^Tw + \upsilon = a, which have the unique solutions w=L′−1cw = L'^{-1}c, ℓT=rTU′−1\ell^T = r^TU'^{-1} and υ=a−ℓTw\upsilon = a - \ell^Tw.

Uniqueness. If L1U1=L2U2L_1U_1 = L_2U_2, then L2−1L1=U2U1−1L_2^{-1}L_1 = U_2U_1^{-1} is both unit lower triangular and upper triangular, hence the identity.

∎

The matrix (0111)\begin{pmatrix}0 & 1\\1 & 1\end{pmatrix} is perfectly well conditioned but has no LU factorisation, since A1=(0)A_1 = (0). A nearly zero pivot is just as bad in floating point. For (ε111)\begin{pmatrix}\varepsilon & 1\\1 & 1\end{pmatrix} the multiplier is 1/ε1/\varepsilon and u22=1−1/εu_{22} = 1 - 1/\varepsilon. With ε=10−20\varepsilon = 10^{-20}, fl(1−1020)=−1020\mathrm{fl}(1 - 10^{20}) = -10^{20}, and the computed solution of Ax=(1,2)TAx = (1, 2)^T is (0,1)(0, 1) instead of (1,1)(1, 1). Swapping the rows first gives multiplier ε\varepsilon and an accurate answer. Partial pivoting always selects the largest available pivot. Then every ∣ℓik∣≤1|\ell_{ik}| \le 1, and every invertible AA has a factorisation PA=LUPA = LU: at each step the pivot column below the diagonal contains a non-zero entry, since otherwise the matrix would be singular.

Theorem— Theorem 4 (Wilkinson's backward error analysis; stated)

Gaussian elimination with partial pivoting, in floating point, computes x^\hat x with (A+δA)x^=b(A + \delta A)\hat x = b, where ∥δA∥∞≤cn ρ u ∥A∥∞\|\delta A\|_\infty \le c_n\,\rho\,u\,\|A\|_\infty for a modest constant cnc_n (a low-degree polynomial in nn) and the growth factor ρ\rho. Moreover ρ≤2n−1\rho \le 2^{n-1}, with equality for a matrix of Wilkinson's (11 on the diagonal and in the last column, −1-1 below the diagonal).

(Wilkinson, 1961; see Higham, Accuracy and Stability of Numerical Algorithms (2002), chapter 9.) The bound 2n−12^{n-1} is attained, so partial pivoting is not backward stable in the worst case. In practice growth factors are small — of order n2/3n^{2/3} on average for random matrices, according to experiments of Trefethen and Schreiber (1990) — and matrices with large growth seem almost never to arise. Why this is so is still not fully understood. It is an empirical fact supported by probabilistic analysis, not a theorem about all matrices. LU costs about 23n3\frac23n^3 flops; each additional right-hand side costs only 2n22n^2.

Do not compute A−1A^{-1} to solve Ax=bAx = b. Forming A−1A^{-1} costs about 2n32n^3 flops, three times as much as LU. The computed fl(A−1)b\mathrm{fl}(A^{-1})b is also not backward stable: its residual ∥b−Ax^∥\|b - A\hat x\| can be about κ(A)\kappa(A) times larger than the residual of the LU solution, although both forward errors are of size κ(A)u\kappa(A)u (Higham 2002, §14.1). And for sparse AA, the inverse is typically dense while the LU factors often stay sparse.

ProofH

Least squares, QR and eigenvalues

Given A∈Rm×nA \in \R^{m\times n} with m>nm > n and full column rank, and b∈Rmb \in \R^m, the least-squares problem asks for xx minimising ∥Ax−b∥2\|Ax - b\|_2 — for example, fitting a line to mm noisy measurements.

Theorem— Theorem 5 (normal equations and QR)

(a) xx minimises ∥Ax−b∥2\|Ax - b\|_2 if and only if ATAx=ATbA^TAx = A^Tb, and the minimiser is unique. (b) If A=QRA = QR (reduced QR factorisation), the minimiser is the solution of the triangular system Rx=QTbRx = Q^Tb. (c) κ2(ATA)=κ2(A)2\kappa_2(A^TA) = \kappa_2(A)^2.

Proof

(a) For any xx and vv, ∥A(x+v)−b∥2=∥Ax−b∥2+2vTAT(Ax−b)+∥Av∥2\|A(x + v) - b\|^2 = \|Ax - b\|^2 + 2v^TA^T(Ax - b) + \|Av\|^2. If AT(Ax−b)=0A^T(Ax - b) = 0, the middle term vanishes and xx is a minimiser, unique because Av≠0Av \ne 0 for v≠0v \ne 0 (full rank). Conversely, at a minimiser the derivative 2AT(Ax−b)2A^T(Ax - b) of the quadratic must vanish.

(b) ATA=RTQTQR=RTRA^TA = R^TQ^TQR = R^TR and ATb=RTQTbA^Tb = R^TQ^Tb, so the normal equations read RT(Rx−QTb)=0R^T(Rx - Q^Tb) = 0. Since RR is invertible, this means Rx=QTbRx = Q^Tb.

(c) With the singular value decomposition A=UΣVTA = U\Sigma V^T, ATA=VΣ2VTA^TA = V\Sigma^2V^T has singular values σi2\sigma_i^2.

∎

So solving the normal equations in floating point loses twice as many digits as the problem deserves. In Läuchli's example A=(11ε00ε)A = \begin{pmatrix}1 & 1\\ \varepsilon & 0\\ 0 & \varepsilon\end{pmatrix} with ε=10−8\varepsilon = 10^{-8}, ATA=(1+ε2111+ε2)A^TA = \begin{pmatrix}1 + \varepsilon^2 & 1\\ 1 & 1 + \varepsilon^2\end{pmatrix}, and fl(1+10−16)=1\mathrm{fl}(1 + 10^{-16}) = 1 makes it exactly singular. QR computed by Householder reflections works on AA itself and is backward stable (Higham 2002, chapter 19). Orthogonal transformations do not amplify errors, since κ2(Q)=1\kappa_2(Q) = 1.

Eigenvalues by the QR algorithm. Starting from A0=AA_0 = A, factor Ak=QkRkA_k = Q_kR_k and set Ak+1=RkQkA_{k+1} = R_kQ_k. Since Ak+1=QkTAkQkA_{k+1} = Q_k^TA_kQ_k, every AkA_k is orthogonally similar to AA and has the same eigenvalues. Theorem (stated): if the eigenvalues of AA have distinct absolute values ∣λ1∣>∣λ2∣>⋯>∣λn∣|\lambda_1| > |\lambda_2| > \cdots > |\lambda_n| (plus a mild condition on the eigenvectors), then AkA_k converges to an upper triangular matrix with the λi\lambda_i on the diagonal. The subdiagonal entry (i+1,i)(i+1, i) decays like ∣λi+1/λi∣k|\lambda_{i+1}/\lambda_i|^k. The reason is that Q0Q1⋯Qk−1Q_0Q_1\cdots Q_{k-1} is the orthonormalisation of AkA^k — simultaneous power iteration on all columns at once. Practical versions first reduce AA to Hessenberg form (cost O(n3)O(n^3) once, then O(n2)O(n^2) per step) and use shifts Ak−μkIA_k - \mu_kI, which make convergence typically quadratic, or cubic for symmetric matrices (Trefethen and Bau 1997, lectures 28–29).

ProofH

Numerical solution of ODEs: order and stability

Consider y′=f(t,y)y' = f(t, y), y(0)=y0y(0) = y_0 on [0,T][0, T], with ff continuous and LL-Lipschitz in yy: ∣f(t,u)−f(t,v)∣≤L∣u−v∣|f(t, u) - f(t, v)| \le L|u - v|. Take steps h=T/Nh = T/N, tn=nht_n = nh.

Theorem— Theorem 6 (Euler's method converges with order 1)

If the exact solution yy is C2C^2 with ∣y′′∣≤M|y''| \le M on [0,T][0, T], the Euler iterates yn+1=yn+hf(tn,yn)y_{n+1} = y_n + hf(t_n, y_n) satisfy

∣y(tn)−yn∣≤Mh2⋅tn eLtn≤MTeLT2 h(0≤n≤N).|y(t_n) - y_n| \le \frac{Mh}{2}\cdot t_n\,e^{Lt_n} \le \frac{MT e^{LT}}{2}\,h \qquad (0 \le n \le N).

Proof

Consistency. By Taylor's theorem, y(tn+1)=y(tn)+hy′(tn)+h22y′′(ξn)=y(tn)+hf(tn,y(tn))+τny(t_{n+1}) = y(t_n) + hy'(t_n) + \frac{h^2}{2}y''(\xi_n) = y(t_n) + hf(t_n, y(t_n)) + \tau_n with ∣τn∣≤Mh22|\tau_n| \le \frac{Mh^2}{2}.

Error recursion. Subtract the Euler step. For en=y(tn)−yne_n = y(t_n) - y_n, the Lipschitz condition gives

∣en+1∣≤∣en∣+h∣f(tn,y(tn))−f(tn,yn)∣+∣τn∣≤(1+hL)∣en∣+M2h2.|e_{n+1}| \le |e_n| + h|f(t_n, y(t_n)) - f(t_n, y_n)| + |\tau_n| \le (1 + hL)|e_n| + \tfrac{M}{2}h^2.

Stability (discrete Gronwall). By induction, ∣en∣≤(1+hL)n∣e0∣+n⋅M2h2(1+hL)n|e_n| \le (1 + hL)^n|e_0| + n\cdot\frac{M}{2}h^2(1 + hL)^n. With e0=0e_0 = 0 and (1+hL)n≤enhL=eLtn(1 + hL)^n \le e^{nhL} = e^{Lt_n} (from 1+x≤ex1 + x \le e^x), this gives ∣en∣≤M2h (nh) eLtn|e_n| \le \frac{M}{2}h\,(nh)\,e^{Lt_n}.

∎

The proof has the shape of every convergence proof in numerical analysis: consistency (small local errors) plus stability (errors are not amplified too much) gives convergence. The whole argument is formalised in Lean below.

Runge–Kutta methods evaluate ff several times per step to cancel more terms of the Taylor expansion. Heun's method yn+1=yn+h2(k1+k2)y_{n+1} = y_n + \frac h2\big(k_1 + k_2\big), with k1=f(tn,yn)k_1 = f(t_n, y_n) and k2=f(tn+h,yn+hk1)k_2 = f(t_n + h, y_n + hk_1), has order 2. The classical fourth-order method of Kutta (1901),

k1=f(tn,yn),k2=f(tn+h2,yn+h2k1),k3=f(tn+h2,yn+h2k2),k4=f(tn+h,yn+hk3),yn+1=yn+h6(k1+2k2+2k3+k4),\begin{aligned} k_1 &= f(t_n, y_n), & k_2 &= f\big(t_n + \tfrac h2, y_n + \tfrac h2k_1\big),\\ k_3 &= f\big(t_n + \tfrac h2, y_n + \tfrac h2k_2\big), & k_4 &= f(t_n + h, y_n + hk_3), \end{aligned} \qquad y_{n+1} = y_n + \tfrac h6(k_1 + 2k_2 + 2k_3 + k_4),

has order 4. Theorem (stated): a Runge–Kutta method whose local truncation error is O(hp+1)O(h^{p+1}) for smooth ff (equivalently, whose coefficients satisfy Butcher's order conditions up to order pp) has global error O(hp)O(h^p), by the same consistency-plus-stability argument as Theorem 6. Checking that the classical method satisfies the eight order conditions for p=4p = 4 is a finite computation (Hairer, Nørsett and Wanner, Solving Ordinary Differential Equations I, §II.2). The experiment below measures the orders 1,2,41, 2, 4.

Absolute stability. On the test equation y′=λyy' = \lambda y with Re⁡λ<0\operatorname{Re}\lambda < 0, whose solutions decay, we want the numerical solution yn=R(hλ)ny0y_n = R(h\lambda)^ny_0 to decay too, i.e. ∣R(hλ)∣<1|R(h\lambda)| < 1. The stability functions are:

methodR(z)R(z)stable for real z<0z < 0
explicit Euler1+z1 + z−2<z<0-2 < z < 0
classical RK41+z+z22+z36+z4241 + z + \frac{z^2}{2} + \frac{z^3}{6} + \frac{z^4}{24}−2.785…<z<0-2.785\ldots < z < 0
backward Euler11−z\dfrac{1}{1 - z}all z<0z < 0
trapezoidal rule1+z/21−z/2\dfrac{1 + z/2}{1 - z/2}all z<0z < 0

Theorem— Theorem 7 (explicit methods are never A-stable)

(a) Backward Euler and the trapezoidal rule are A-stable. (b) No explicit Runge–Kutta method is A-stable.

Proof

(a) For Re⁡z≤0\operatorname{Re}z \le 0: ∣1−z∣≥1|1 - z| \ge 1, so ∣11−z∣≤1\left|\frac{1}{1 - z}\right| \le 1; and ∣1+z/2∣≤∣1−z/2∣|1 + z/2| \le |1 - z/2|, because z/2z/2 is at least as close to −1-1 as to 11. (b) For an explicit method with ss stages, RR is a polynomial of degree at most ss, non-constant since R(z)=1+z+O(z2)R(z) = 1 + z + O(z^2). A non-constant polynomial is unbounded on the half-line z→−∞z \to -\infty.

∎

A problem is stiff when it contains decay rates far faster than the time scale of interest. The equation y′=−50(y−cos⁡t)y' = -50(y - \cos t) has the smooth solution y≈cos⁡ty \approx \cos t after a transient of length ≈0.1\approx 0.1, yet explicit Euler needs 50h<250h < 2 for ever after. In chemical kinetics or circuit simulation, rates can differ by factors of 101010^{10}, and only implicit methods are practical. Germund Dahlquist (1963) proved the second Dahlquist barrier: an A-stable linear multistep method has order at most 22. Among the A-stable multistep methods of order 2, the trapezoidal rule has the smallest error constant.

Smaller is not always better. Each step makes a rounding error of order uu, and N=T/hN = T/h steps accumulate roughly uT/huT/h of it. So the total error behaves like Chp+uT/hCh^p + uT/h, which is smallest at a moderate hh and grows as h→0h \to 0. The same trade-off appears in a single difference quotient: (f(x+h)−f(x))/h(f(x + h) - f(x))/h has truncation error ≈h∣f′′∣/2\approx h|f''|/2 and rounding error ≈2u∣f∣/h\approx 2u|f|/h, optimal near h≈u≈10−8h \approx \sqrt u \approx 10^{-8}.

Formal mathematicsF

Numerical analysis in Lean and Mathlib

Mathlib has operator norms, matrices and determinants, but no model of floating-point arithmetic and no library of numerical algorithms. So what can be formalised is the exact mathematics behind the error analysis: perturbation bounds, factorisations, and the convergence and stability estimates. The rounding-error statements (Theorem 4, backward stability of Householder QR) are not formalised.

The condition number of an invertible bounded linear map between normed spaces, and Theorem 1 in multiplied-out form (which avoids dividing by ∥x∥\|x\| and ∥b∥\|b\|):

condNumLean 4Formally verified
/-- The **condition number** `κ(A) = ‖A‖ ‖A⁻¹‖` of an invertible bounded linear map between normed
spaces (for matrices, with the operator norm induced by the chosen vector norm). -/
noncomputable def condNum {E F : Type*} [NormedAddCommGroup E] [NormedSpace ℝ E]
    [NormedAddCommGroup F] [NormedSpace ℝ F] (A : E ≃L[ℝ] F) : ℝ :=
  ‖(A : E →L[ℝ] F)‖ * ‖(A.symm : F →L[ℝ] E)‖
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
op_norm_boundLean 4Applies a library theoremFormally verified
/-- The defining property of the operator norm: `‖A x‖ ≤ ‖A‖ ‖x‖`. This applies Mathlib's
`ContinuousLinearMap.le_opNorm`. -/
theorem op_norm_bound {E F : Type*} [NormedAddCommGroup E] [NormedSpace ℝ E]
    [NormedAddCommGroup F] [NormedSpace ℝ F] (A : E →L[ℝ] F) (x : E) : ‖A x‖ ≤ ‖A‖ * ‖x‖ :=
  A.le_opNorm x
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
one_le_condNumLean 4Proved here from basic factsFormally verified
/-- **`κ(A) ≥ 1`.** For `x ≠ 0`, `‖x‖ = ‖A⁻¹(A x)‖ ≤ ‖A⁻¹‖ ‖A‖ ‖x‖`; divide by `‖x‖`. A condition
number near `1` (an orthogonal matrix in the 2-norm) is as good as it gets. -/
theorem one_le_condNum {E F : Type*} [NormedAddCommGroup E] [NormedSpace ℝ E]
    [NormedAddCommGroup F] [NormedSpace ℝ F] (A : E ≃L[ℝ] F) {x : E} (hx : x ≠ 0) :
    1 ≤ condNum A := by
  have hxpos : 0 < ‖x‖ := norm_pos_iff.mpr hx
  have h1 : ‖x‖ ≤ ‖(A.symm : F →L[ℝ] E)‖ * ‖A x‖ := by
    have := (A.symm : F →L[ℝ] E).le_opNorm (A x)
    simpa using this
  have h2 : ‖A x‖ ≤ ‖(A : E →L[ℝ] F)‖ * ‖x‖ := (A : E →L[ℝ] F).le_opNorm x
  have h3 : ‖x‖ ≤ condNum A * ‖x‖ := by
    calc ‖x‖ ≤ ‖(A.symm : F →L[ℝ] E)‖ * ‖A x‖ := h1
      _ ≤ ‖(A.symm : F →L[ℝ] E)‖ * (‖(A : E →L[ℝ] F)‖ * ‖x‖) :=
          mul_le_mul_of_nonneg_left h2 (norm_nonneg _)
      _ = condNum A * ‖x‖ := by unfold condNum; ring
  nlinarith
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
relative_error_boundLean 4Proved here from basic factsFormally verified
/-- **The perturbation bound.** If `A x = b` and `A (x + δx) = b + δb`, then
`‖δx‖ · ‖b‖ ≤ κ(A) · ‖δb‖ · ‖x‖`, i.e. `‖δx‖/‖x‖ ≤ κ(A) ‖δb‖/‖b‖`: the relative error in the
solution is at most the condition number times the relative error in the data. Proof: by
linearity `A δx = δb`, so `δx = A⁻¹ δb` and `‖δx‖ ≤ ‖A⁻¹‖ ‖δb‖`; and `‖b‖ = ‖A x‖ ≤ ‖A‖ ‖x‖`.
Multiply the two inequalities. -/
theorem relative_error_bound {E F : Type*} [NormedAddCommGroup E] [NormedSpace ℝ E]
    [NormedAddCommGroup F] [NormedSpace ℝ F] (A : E ≃L[ℝ] F) {x δx : E} {b δb : F}
    (hb : A x = b) (hpert : A (x + δx) = b + δb) :
    ‖δx‖ * ‖b‖ ≤ condNum A * ‖δb‖ * ‖x‖ := by
  have hAδ : A δx = δb := by
    rw [map_add, hb] at hpert
    exact add_left_cancel hpert
  have h1 : ‖δx‖ ≤ ‖(A.symm : F →L[ℝ] E)‖ * ‖δb‖ := by
    have := (A.symm : F →L[ℝ] E).le_opNorm δb
    rw [← hAδ] at this ⊢
    simpa using this
  have h2 : ‖b‖ ≤ ‖(A : E →L[ℝ] F)‖ * ‖x‖ := by
    rw [← hb]; exact (A : E →L[ℝ] F).le_opNorm x
  calc ‖δx‖ * ‖b‖ ≤ (‖(A.symm : F →L[ℝ] E)‖ * ‖δb‖) * (‖(A : E →L[ℝ] F)‖ * ‖x‖) :=
        mul_le_mul h1 h2 (norm_nonneg _) (by positivity)
    _ = condNum A * ‖δb‖ * ‖x‖ := by unfold condNum; ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor

An LU factorisation produced by elimination, and the determinant as the product of the pivots:

lu_exampleLean 4Proved here from basic factsFormally verified
/-- **An LU factorisation**, as produced by Gaussian elimination: subtract `2 ×` row 1 from row 2
and `4 ×` row 1 from row 3, then `3 ×` the new row 2 from row 3. The multipliers `2, 4, 3` fill
the unit lower triangular factor `L`; the result of elimination is the upper triangular `U`.
Checked by exact rational arithmetic. -/
theorem lu_example :
    !![(2 : ℚ), 1, 1; 4, 3, 3; 8, 7, 9] = !![(1 : ℚ), 0, 0; 2, 1, 0; 4, 3, 1] * !![(2 : ℚ), 1, 1; 0, 1, 1; 0, 0, 2] := by
  ext i j
  fin_cases i <;> fin_cases j <;> simp [Matrix.mul_apply, Fin.sum_univ_succ] <;> norm_num
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
det_mul_luLean 4Applies a library theoremFormally verified
/-- `det (L U) = det L · det U`. This applies Mathlib's `Matrix.det_mul`. With `L` unit lower
triangular (`det L = 1`) and `U` upper triangular, `det A` is the product of the pivots — the
cheapest way to compute a determinant (about `2n³/3` operations instead of `n!` terms). -/
theorem det_mul_lu {n : ℕ} (L U : Matrix (Fin n) (Fin n) ℚ) : (L * U).det = L.det * U.det :=
  Matrix.det_mul L U
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
det_via_luLean 4Proved here from basic factsFormally verified
/-- The determinant of the example matrix is the product of the pivots `2 · 1 · 2 = 4`, computed
through the factorisation `lu_example` (`det L = 1` and `det U = 4`, each by the formula for a
3 × 3 determinant). -/
theorem det_via_lu : (!![(2 : ℚ), 1, 1; 4, 3, 3; 8, 7, 9]).det = 4 := by
  rw [lu_example, Matrix.det_mul, Matrix.det_fin_three, Matrix.det_fin_three]
  simp
  norm_num
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor

The pivoting example, exactly, with and without the row swap:

no_pivot_luLean 4Proved here from basic factsFormally verified
/-- **Why pivot: elimination without a row swap.** For `ε ≠ 0`,
`[[ε, 1], [1, 1]] = [[1, 0], [1/ε, 1]] · [[ε, 1], [0, 1 - 1/ε]]`. When `ε` is tiny the multiplier
`1/ε` and the entry `1 - 1/ε` are huge, and in floating point `1 - 1/ε` rounds to `-1/ε`,
destroying the information in the `(2, 2)` entry: the computed solution can be completely wrong
although the matrix is perfectly well conditioned. -/
theorem no_pivot_lu {ε : ℝ} (hε : ε ≠ 0) :
    !![ε, 1; 1, 1] = !![1, 0; 1 / ε, 1] * !![ε, 1; 0, 1 - 1 / ε] := by
  ext i j
  fin_cases i <;> fin_cases j <;> simp [Matrix.mul_apply, Fin.sum_univ_succ]
  field_simp
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
pivot_luLean 4Proved here from basic factsFormally verified
/-- **With partial pivoting.** Swapping the rows first (`P A` with `P` the swap) gives
`[[1, 1], [ε, 1]] = [[1, 0], [ε, 1]] · [[1, 1], [0, 1 - ε]]`: the multiplier is `ε`, at most `1`
in absolute value, and no entry grows. Partial pivoting always chooses the largest available
pivot, so every multiplier satisfies `|ℓᵢⱼ| ≤ 1`. -/
theorem pivot_lu (ε : ℝ) :
    !![1, 1; ε, 1] = !![1, 0; ε, 1] * !![(1 : ℝ), 1; 0, 1 - ε] := by
  ext i j
  fin_cases i <;> fin_cases j <;> simp [Matrix.mul_apply, Fin.sum_univ_succ]
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor

Theorem 6, Euler's method has order 1, in three steps that follow the human proof — the error recursion, the discrete Gronwall inequality, and the global bound (with 1+x≤ex1 + x \le e^x from Mathlib):

euler_error_stepLean 4Proved here from basic factsFormally verified
/-- **One step of the error recursion.** Let `yₙ₊₁ = yₙ + h f(tₙ, yₙ)` (Euler) and let the exact
solution satisfy `y(tₙ₊₁) = y(tₙ) + h f(tₙ, y(tₙ)) + τₙ` (`τₙ` is the local truncation error).
If `f` is `L`-Lipschitz in `y`, the error `eₙ = y(tₙ) - yₙ` satisfies
`|eₙ₊₁| ≤ (1 + h L) |eₙ| + |τₙ|`. -/
theorem euler_error_step {f : ℝ → ℝ → ℝ} {L h t yn Y τ : ℝ} (hh : 0 ≤ h)
    (hlip : ∀ s u v, |f s u - f s v| ≤ L * |u - v|) :
    |(Y + h * f t Y + τ) - (yn + h * f t yn)| ≤ (1 + h * L) * |Y - yn| + |τ| := by
  have hf := hlip t Y yn
  calc |(Y + h * f t Y + τ) - (yn + h * f t yn)|
      = |(Y - yn) + h * (f t Y - f t yn) + τ| := by ring_nf
    _ ≤ |Y - yn| + |h * (f t Y - f t yn)| + |τ| := abs_add_three _ _ _
    _ = |Y - yn| + h * |f t Y - f t yn| + |τ| := by rw [abs_mul, abs_of_nonneg hh]
    _ ≤ |Y - yn| + h * (L * |Y - yn|) + |τ| := by gcongr
    _ = (1 + h * L) * |Y - yn| + |τ| := by ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
discrete_gronwallLean 4Proved here from basic factsFormally verified
/-- **Discrete Gronwall inequality.** If `eₙ₊₁ ≤ (1 + h L) eₙ + τ` with `h L ≥ 0` and `τ ≥ 0`,
then `eₙ ≤ (1 + h L)ⁿ e₀ + n τ (1 + h L)ⁿ`. Errors committed at each step are amplified by at most
`(1 + h L)` per subsequent step. Induction on `n`. -/
theorem discrete_gronwall {e : ℕ → ℝ} {h L τ : ℝ} (hhL : 0 ≤ h * L) (hτ : 0 ≤ τ)
    (hstep : ∀ n, e (n + 1) ≤ (1 + h * L) * e n + τ) (n : ℕ) :
    e n ≤ (1 + h * L) ^ n * e 0 + n * τ * (1 + h * L) ^ n := by
  have hq : 1 ≤ 1 + h * L := by linarith
  induction n with
  | zero => simp
  | succ n ih =>
    have hpow : 1 ≤ (1 + h * L) ^ (n + 1) := one_le_pow₀ hq
    calc e (n + 1) ≤ (1 + h * L) * e n + τ := hstep n
      _ ≤ (1 + h * L) * ((1 + h * L) ^ n * e 0 + n * τ * (1 + h * L) ^ n) + τ := by
          gcongr
      _ = (1 + h * L) ^ (n + 1) * e 0 + n * τ * (1 + h * L) ^ (n + 1) + τ := by ring
      _ ≤ (1 + h * L) ^ (n + 1) * e 0 + n * τ * (1 + h * L) ^ (n + 1)
            + τ * (1 + h * L) ^ (n + 1) := by nlinarith
      _ = (1 + h * L) ^ (n + 1) * e 0 + ((n + 1 : ℕ) : ℝ) * τ * (1 + h * L) ^ (n + 1) := by
          push_cast; ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
one_add_le_expLean 4Applies a library theoremFormally verified
/-- `1 + x ≤ eˣ` for every real `x`, so `(1 + h L)ⁿ ≤ e^{n h L}`. This applies Mathlib's
`Real.add_one_le_exp`. -/
theorem one_add_le_exp (x : ℝ) : 1 + x ≤ Real.exp x := by
  rw [add_comm]; exact Real.add_one_le_exp x
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
euler_global_errorLean 4Proved here from basic factsFormally verified
/-- **Euler's method has order 1.** Suppose the errors satisfy `e₀ = 0` and
`eₙ₊₁ ≤ (1 + h L) eₙ + C h²` (the recursion of `euler_error_step`, with local truncation error
`|τₙ| ≤ C h²`, e.g. `C = max |y''| / 2`). Then at time `T = n h`,
`eₙ ≤ C h · T · e^{L T}`. For fixed `T`, the error is `O(h)`: halving the step halves the error. -/
theorem euler_global_error {e : ℕ → ℝ} {h L C : ℝ} (hh : 0 ≤ h) (hL : 0 ≤ L) (hC : 0 ≤ C)
    (h0 : e 0 = 0) (hstep : ∀ n, e (n + 1) ≤ (1 + h * L) * e n + C * h ^ 2) (n : ℕ) :
    e n ≤ C * h * (n * h) * Real.exp (L * (n * h)) := by
  have hhL : 0 ≤ h * L := mul_nonneg hh hL
  have hg := discrete_gronwall hhL (by positivity) hstep n
  rw [h0, mul_zero, zero_add] at hg
  have hpow : (1 + h * L) ^ n ≤ Real.exp (L * (n * h)) := by
    calc (1 + h * L) ^ n ≤ Real.exp (h * L) ^ n :=
          pow_le_pow_left₀ (by linarith) (one_add_le_exp _) n
      _ = Real.exp (L * (n * h)) := by rw [← Real.exp_nat_mul]; ring_nf
  calc e n ≤ n * (C * h ^ 2) * (1 + h * L) ^ n := hg
    _ ≤ n * (C * h ^ 2) * Real.exp (L * (n * h)) := by
        apply mul_le_mul_of_nonneg_left hpow; positivity
    _ = C * h * (n * h) * Real.exp (L * (n * h)) := by ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor

Stability on the test equation: explicit Euler multiplies by 1+hλ1 + h\lambda and is stable exactly when h∣λ∣<2h|\lambda| < 2, while backward Euler contracts for every step size:

explicit_euler_iterateLean 4Proved here from basic factsFormally verified
/-- **Explicit Euler on `y' = λ y`** is `yₙ₊₁ = yₙ + h λ yₙ = (1 + hλ) yₙ`, so after `n` steps
`yₙ = (1 + hλ)ⁿ y₀`. The exact solution is `e^{λ n h} y₀`. -/
theorem explicit_euler_iterate (h lam y₀ : ℝ) (n : ℕ) :
    (fun y => y + h * (lam * y))^[n] y₀ = (1 + h * lam) ^ n * y₀ := by
  induction n with
  | zero => simp
  | succ n ih => rw [Function.iterate_succ_apply', ih]; ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
explicit_euler_stable_iffLean 4Proved here from basic factsFormally verified
/-- **Stability of explicit Euler.** For a decaying equation (`λ < 0`) and step `h > 0`, the
numerical solution decays (`|1 + hλ| < 1`) exactly when `h |λ| < 2`. For a stiff equation with
`λ = -1000` this forces `h < 0.002`, whatever accuracy is wanted. -/
theorem explicit_euler_stable_iff {h lam : ℝ} (hh : 0 < h) (hlam : lam < 0) :
    |1 + h * lam| < 1 ↔ h * (-lam) < 2 := by
  have : 0 < h * (-lam) := mul_pos hh (by linarith)
  rw [abs_lt]
  constructor
  · rintro ⟨h1, -⟩; linarith
  · intro h2; constructor <;> nlinarith
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor
backward_euler_contractsLean 4Proved here from basic factsFormally verified
/-- **Backward Euler is unconditionally stable on decaying modes.** The implicit step
`yₙ₊₁ = yₙ + h λ yₙ₊₁` gives `yₙ₊₁ = yₙ / (1 - hλ)`, and for `λ < 0` and every `h > 0` the factor
`1/(1 - hλ)` lies strictly between `0` and `1`: the numerical solution decays, like the true
one, with no restriction on the step size (A-stability, on the negative real axis). -/
theorem backward_euler_contracts {h lam : ℝ} (hh : 0 < h) (hlam : lam < 0) :
    0 < 1 / (1 - h * lam) ∧ 1 / (1 - h * lam) < 1 := by
  have hpos : 1 < 1 - h * lam := by nlinarith
  constructor
  · positivity
  · rw [div_lt_one (by linarith)]; exact hpos
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/NumericalAnalysis.leanTry it in the Lean web editor

What is and is not formalised. Proved here: κ≥1\kappa \ge 1, Theorem 1, the example factorisations, the Euler error recursion, discrete Gronwall and Theorem 6 (given the one-step recursion), and Theorem 7 on the negative real axis for the two Euler methods. Cited from Mathlib: the operator-norm inequality, det⁡(LU)=det⁡Ldet⁡U\det(LU) = \det L\det U and 1+x≤ex1 + x \le e^x. Not formalised: Theorem 2, Theorems 3–5 in general, the QR algorithm, the order of RK4, and anything about floating-point rounding.

ComputationC

Experiments in double precision

The first program puts the linear-algebra theorems to work in IEEE double precision. Hilbert systems are solved with relative error close to κ2(H)u\kappa_2(H)u, as Theorems 1–2 predict for a backward-stable solver. (The right-hand side b=H1b = H\mathbf{1} is itself rounded, which already causes errors of this size.) Elimination without pivoting fails on the ε\varepsilon-matrix. Wilkinson's matrix achieves the worst-case growth 2n−12^{n-1} and destroys all accuracy at n=55n = 55, although its condition number is modest. And the normal equations for Läuchli's problem are exactly singular in floating point, while QR solves it.

Conditioning, pivoting and least squares in floating pointPythonNumerical approximationCounterexampleComputationally tested
"""Conditioning, pivoting and least squares in floating point.

Four numerical experiments in IEEE double precision (unit roundoff
u = 2^-53, about 1.1e-16). (1) Hilbert matrices H_ij = 1/(i + j - 1) are
notoriously ill conditioned: solving H x = b with a backward-stable solver
gives a relative error of roughly kappa(H) u, as the perturbation bound
predicts. (2) Gaussian elimination without pivoting on [[1e-20, 1], [1, 1]]
returns a wrong answer although the matrix is well conditioned; with
partial pivoting the answer is right. (3) Wilkinson's matrix makes partial
pivoting's growth factor equal 2^(n-1), the worst case. (4) For Lauchli's
least-squares problem the normal equations A^T A x = A^T b are exactly
singular in floating point, while a QR factorisation solves it accurately.

Evidence: numerical (floating point), compared with exact solutions.
"""

import numpy as np

UNIT_ROUNDOFF = 2.0 ** -53

def hilbert(n):
    """The n x n Hilbert matrix."""
    i = np.arange(1, n + 1)
    return 1.0 / (i[:, None] + i[None, :] - 1)

def lu_solve(a, b, pivot=True):
    """Solve a x = b by Gaussian elimination (optionally partial pivoting).

    Returns (x, growth) where growth = max |U_ij| / max |A_ij|.
    """
    a = np.array(a, dtype=float)
    b = np.array(b, dtype=float)
    n = len(b)
    scale = np.max(np.abs(a))
    for k in range(n - 1):
        if pivot:
            p = k + int(np.argmax(np.abs(a[k:, k])))
            a[[k, p]] = a[[p, k]]
            b[[k, p]] = b[[p, k]]
        for i in range(k + 1, n):
            m = a[i, k] / a[k, k]
            a[i, k:] -= m * a[k, k:]
            b[i] -= m * b[k]
    x = np.zeros(n)
    for i in range(n - 1, -1, -1):
        x[i] = (b[i] - a[i, i + 1:] @ x[i + 1:]) / a[i, i]
    growth = np.max(np.abs(np.triu(a))) / scale
    return x, growth

def wilkinson(n):
    """1 on the diagonal and last column, -1 below the diagonal."""
    a = np.eye(n) - np.tril(np.ones((n, n)), -1)
    a[:, -1] = 1.0
    return a

def lauchli(eps):
    """Lauchli's 3 x 2 matrix [[1, 1], [eps, 0], [0, eps]]."""
    return np.array([[1.0, 1.0], [eps, 0.0], [0.0, eps]])

def rel_err(x, exact):
    """Relative error in the max norm."""
    return np.max(np.abs(x - exact)) / np.max(np.abs(exact))

def main():
    print("1. Hilbert systems H x = b with exact x = (1, ..., 1)")
    print("    n     kappa(H)   rel. error   kappa * u")
    for n in [4, 6, 8, 10, 12]:
        h = hilbert(n)
        x = np.linalg.solve(h, h @ np.ones(n))
        k = np.linalg.cond(h)
        print(f"   {n:2d}   {k:10.2e}   {rel_err(x, np.ones(n)):10.2e}"
              f"   {k * UNIT_ROUNDOFF:9.2e}")
    print()
    a, b = [[1e-20, 1.0], [1.0, 1.0]], [1.0, 2.0]
    x0, _ = lu_solve(a, b, pivot=False)
    x1, _ = lu_solve(a, b, pivot=True)
    print("2. [[1e-20, 1], [1, 1]] x = (1, 2); exact x = (1, 1) to 20 digits")
    print(f"   no pivoting:      x = ({x0[0]:.6f}, {x0[1]:.6f})")
    print(f"   partial pivoting: x = ({x1[0]:.6f}, {x1[1]:.6f})")
    print()
    print("3. Growth factor of partial pivoting on Wilkinson's matrix")
    for n in [10, 30, 55]:
        w = wilkinson(n)
        xs = np.linspace(1.0, 2.0, n)
        x, g = lu_solve(w, w @ xs)
        print(f"   n = {n:2d}: growth {g:9.3e} = 2^{np.log2(g):.0f},"
              f" rel. error {rel_err(x, xs):.1e}")
    print()
    eps = 1e-8
    m = lauchli(eps)
    rhs = m @ np.array([1.0, 1.0])
    gram = m.T @ m
    q, r = np.linalg.qr(m)
    xq = np.linalg.solve(r, q.T @ rhs)
    print("4. Lauchli least squares, eps = 1e-8, exact x = (1, 1)")
    print(f"   computed A^T A = {gram.tolist()}: det = {np.linalg.det(gram):.1e}")
    print(f"   QR solution: ({xq[0]:.8f}, {xq[1]:.8f})")
    print()
    print("Epistemic status: numerical experiments in IEEE double precision.")
    print("They illustrate the perturbation bound, the need for pivoting, the")
    print("worst-case growth factor and the squaring of the condition number")
    print("by the normal equations; the theorems themselves are in the lesson.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

The second program measures the order of three one-step methods by halving hh — the error ratios approach 22, 44 and 1616 — then shows stiffness: explicit Euler on y′=−50(y−cos⁡t)y' = -50(y - \cos t) explodes for 50h=2.550h = 2.5, is neutral at 50h=250h = 2, and is accurate for 50h≤1.2550h \le 1.25, while backward Euler is accurate for every step. Finally it shows the U-shaped total error of a difference quotient as hh shrinks past u\sqrt u.

One-step ODE methods: order, stiffness and roundingPythonNumerical approximationComputationally tested
"""Order, stiffness and rounding: one-step methods for ODEs.

(1) Observed order: solve y' = y, y(0) = 1 on [0, 1] with Euler, Heun
(a second-order Runge-Kutta method) and classical RK4, halving the step
each time. If the global error is C h^p, halving h divides it by 2^p, so
log2 of successive error ratios estimates the order p: we see 1, 2 and 4.
(2) Stiffness: y' = -50 (y - cos t), y(0) = 0 has a smooth solution, but
explicit Euler is stable only for h * 50 < 2. With h = 0.05 it explodes;
backward Euler is stable for every h. (3) Rounding: the difference
quotient (e^(1+h) - e)/h approximates e with truncation error ~ h e/2 and
rounding error ~ u e/h, so the total error is smallest near h = sqrt(u)
and grows again for smaller h.

Evidence: numerical, compared with exact solutions.
"""

import math

def euler_step(f, t, y, h):
    """Explicit Euler."""
    return y + h * f(t, y)

def heun_step(f, t, y, h):
    """Heun's method (explicit trapezoid rule), order 2."""
    k1 = f(t, y)
    k2 = f(t + h, y + h * k1)
    return y + h * (k1 + k2) / 2

def rk4_step(f, t, y, h):
    """Classical fourth-order Runge-Kutta method."""
    k1 = f(t, y)
    k2 = f(t + h / 2, y + h * k1 / 2)
    k3 = f(t + h / 2, y + h * k2 / 2)
    k4 = f(t + h, y + h * k3)
    return y + h * (k1 + 2 * k2 + 2 * k3 + k4) / 6

def integrate(step, f, y0, t_end, n):
    """Take n equal steps of a one-step method from t = 0."""
    h = t_end / n
    t, y = 0.0, y0
    for _ in range(n):
        y = step(f, t, y, h)
        t += h
    return y

def backward_euler_stiff(lam, y0, t_end, n):
    """Backward Euler for y' = lam (y - cos t): solve the linear step exactly."""
    h = t_end / n
    t, y = 0.0, y0
    for _ in range(n):
        t += h
        y = (y - h * lam * math.cos(t)) / (1 - h * lam)
    return y

def stiff_exact(t):
    """Exact solution of y' = -50 (y - cos t), y(0) = 0."""
    a, b = 2500 / 2501, 50 / 2501
    return a * math.cos(t) + b * math.sin(t) - a * math.exp(-50 * t)

def main():
    f = lambda t, y: y
    print("1. y' = y on [0, 1]: error at t = 1 and observed order")
    print("     n      Euler   order       Heun   order        RK4   order")
    prev = None
    for n in [10, 20, 40, 80]:
        errs = [abs(integrate(s, f, 1.0, 1.0, n) - math.e)
                for s in (euler_step, heun_step, rk4_step)]
        cells = []
        for k, e in enumerate(errs):
            order = "" if prev is None else f"{math.log2(prev[k] / e):.2f}"
            cells.append(f"{e:9.2e}  {order:>5}")
        print((f"  {n:4d}   " + "   ".join(cells)).rstrip())
        prev = errs
    print()
    g = lambda t, y: -50 * (y - math.cos(t))
    print("2. Stiff: y' = -50 (y - cos t), y(0) = 0; error at t = 1")
    for n in [20, 25, 40, 100]:
        e1 = abs(integrate(euler_step, g, 0.0, 1.0, n) - stiff_exact(1.0))
        e2 = abs(backward_euler_stiff(-50, 0.0, 1.0, n) - stiff_exact(1.0))
        print(f"   h = {1 / n:5.3f} (50h = {50 / n:4.2f}): explicit {e1:9.2e},"
              f"  backward {e2:8.2e}")
    print()
    print("3. Forward difference for (e^x)' at x = 1: total error")
    for k in [2, 4, 6, 8, 10, 12, 14]:
        h = 10.0 ** -k
        err = abs((math.exp(1 + h) - math.e) / h - math.e)
        print(f"   h = 1e-{k:02d}: error {err:.1e}")
    print()
    print("Epistemic status: numerical experiments. The orders 1, 2, 4, the")
    print("stability limit 50h < 2 and the optimal h near sqrt(u) are")
    print("theorems or estimates proved in the lesson; these runs illustrate")
    print("them on single examples and prove nothing in general.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

These are numerical experiments on single examples. They illustrate the theorems, and the agreement with the predicted rates is good evidence that the code is right, but they prove nothing about other matrices or equations.

Explore

Explore: thin ellipses, stability regions and stiffness

Ill-conditioning. This matrix has determinant 0.050.05: it squashes the unit square into a sliver, and its condition number is about 8080. Move the (2,2)(2, 2) entry towards 11 and watch the parallelogram collapse onto a line — the matrix becomes singular, and κ→∞\kappa \to \infty.

An ill-conditioned matrix

A thin image means a large condition number.

Your own matrix
λ₁ = 2.03λ₂ = 0.02Ae₁Ae₂
  • image of the grid
  • image of the unit square
  • image of the flag
  • before the transformation
  • invariant line
Entries of the matrix A
A =

The columns of A are the images of e₁ = (1, 0) and e₂ = (0, 1): Ae₁ = (1, 1), Ae₂ = (1, 1.05)

det A = ad − bc = 0.05

The unit square (area 1) becomes a parallelogram of area |det A| = 0.05. Since det A > 0, orientation is preserved.

Invariant lines (real eigenvectors)

  • λ₁ = 2.025, v₁ = (0.975, 1)
  • λ₂ = 0.025, v₂ = (1, −0.975)

Vectors on the two dashed lines are only stretched, by the factors λ₁ = 2.025 and λ₂ = 0.025; every other direction is turned.

Stability functions on the negative real axis. Each curve is ∣R(z)∣|R(z)| for z=hλ<0z = h\lambda < 0. A method is stable where its curve lies below the line y=1y = 1: explicit Euler on (−2,0)(-2, 0), RK4 on about (−2.79,0)(-2.79, 0), and backward Euler everywhere.

|R(hλ)| for three methods

Move the sliders and trace along the curves to read off values.

  • explicit Euler
  • RK4
  • backward Euler
  • y = 1
  • explicit Euler: y = 1.5
  • RK4: y = 1.6484
  • backward Euler: y = 2
  • y = 1: y = 1

Stiffness in the slope field. Solutions of y′=−50(y−cos⁡x)y' = -50(y - \cos x) from any starting value collapse within x≈0.1x \approx 0.1 onto the slow solution y≈cos⁡xy \approx \cos x. An accurate method only needs to follow that smooth curve, but an explicit method must still respect the fast decay rate 5050.

A stiff equation: y′ = −50(y − cos x)

Each short segment has the slope dy/dx at its point. Click anywhere to draw the solution through that point.

Examples

dy/dx = −50·(y − cos(x))

Click the field to draw a solution, or type a start point below.

Start point

Solution curves: 3. They are numerical approximations (fourth-order Runge–Kutta), not exact formulas.

Latest curve through (0, 0).

Exercises

Exercises

1. Numerical analysis

Warm-upHuman

What is the condition number κ∞(A)=∥A∥∞∥A−1∥∞\kappa_\infty(A) = \|A\|_\infty\|A^{-1}\|_\infty of A=(4000.004)A = \begin{pmatrix} 4 & 0 \\ 0 & 0.004 \end{pmatrix}?

2. Numerical analysis

Warm-upHuman

A solver returns x^\hat x for Ax=bAx = b, and you find that x^\hat x is the exact solution of (A+δA)x^=b(A + \delta A)\hat x = b with ∥δA∥/∥A∥≈10−16\|\delta A\| / \|A\| \approx 10^{-16}. What can you conclude?

Choose one

3. Numerical analysis

CoreHuman

You solve Ax=bAx = b in IEEE double precision (u≈10−16u \approx 10^{-16}) with a backward-stable method, and κ(A)≈1010\kappa(A) \approx 10^{10}. Roughly how many correct significant decimal digits can you expect in the solution?

4. Numerical analysis

CoreHuman

Which of these invertible matrices has no factorisation A=LUA = LU (with LL unit lower triangular and UU upper triangular) unless rows are swapped?

Choose one

5. Numerical analysis

CoreHuman

Why should you solve Ax=bAx = b by LU with partial pivoting rather than by computing A−1A^{-1} and multiplying? Choose all correct reasons.

Choose all that apply

6. Numerical analysis

CoreFormal

Which Lean conclusion correctly expresses Theorem 1, ∥δx∥∥x∥≤κ(A)∥δb∥∥b∥\frac{\|\delta x\|}{\|x\|} \le \kappa(A)\frac{\|\delta b\|}{\|b\|}, without dividing by norms that might be zero?

Choose one

7. Numerical analysis

CoreHuman

Explicit Euler is applied to y′=−200yy' = -200y on [0,1][0, 1] with NN equal steps (h=1/Nh = 1/N). What is the smallest NN for which the numerical solution decays, i.e. ∣1+hλ∣<1|1 + h\lambda| < 1?

8. Numerical analysis

CoreComputational

Write rk4(f, y0, t_end, n) that applies nn steps of the classical fourth-order Runge–Kutta method to y′=f(t,y)y' = f(t, y), y(0)=y0y(0) = y_0, with step h=tend/nh = t_{\text{end}}/n, and returns the approximation to y(tend)y(t_{\text{end}}).

Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

9. Numerical analysis

StretchHuman

Let A∈Rm×nA \in \R^{m \times n} (m>nm > n) have full column rank and 2-norm condition number κ2(A)=108\kappa_2(A) = 10^8. Which statements are true? Choose all that apply.

Choose all that apply

10. Numerical analysis

StretchHuman

Which of these methods are A-stable, i.e. ∣R(z)∣≤1|R(z)| \le 1 for every complex zz with Re⁡z≤0\operatorname{Re} z \le 0? Choose all that apply.

Choose all that apply
Common misconceptionsH

Common misconceptions

Misconception

"To solve Ax=bAx = b, compute A−1A^{-1} and multiply." It costs about three times as much as LU with partial pivoting, it destroys sparsity, and it is less accurate in the backward sense: the residual of fl(A−1)b\mathrm{fl}(A^{-1})b can be κ(A)\kappa(A) times larger. In MATLAB, Julia or NumPy, write A \ b or numpy.linalg.solve(A, b), which use LU. You almost never need A−1A^{-1} itself; if you need A−1BA^{-1}B, solve AX=BAX = B.

Misconception

"A smaller step size always reduces the error." Truncation error falls like hph^p, but rounding error accumulates over T/hT/h steps and grows like u/hu/h. Below an optimal hh the total error increases, which is visible in the difference-quotient experiment, where the best hh is near 10−810^{-8}. For stiff problems the opposite surprise occurs: a larger step with an explicit method does not just lose accuracy, it explodes.

Misconception

"A small residual means an accurate solution." A backward-stable solver produces a residual ∥b−Ax^∥\|b - A\hat x\| of order u∥A∥∥x^∥u\|A\|\|\hat x\| — tiny — even when the forward error is of order κ(A)u\kappa(A)u and therefore large. The Hilbert matrix of order 1212 gives residuals near 10−1610^{-16} and errors near 11. The residual measures the backward error, and accuracy needs the condition number too.

Misconception

"Ill-conditioning is the algorithm's fault." Conditioning belongs to the problem. If κ(A)=1012\kappa(A) = 10^{12}, then perturbing the data in the 16th digit changes the answer in the 4th, and no algorithm working with rounded data can do better. The cure is to reformulate the problem (rescale, choose a better basis, add regularisation) or to compute with more digits, not to look for a cleverer solver.

Misconception

"Higher order is always better." Order describes the error as h→0h \to 0 for smooth problems. For stiff problems the explicit RK4 method, whatever its order, is limited by its stability interval of about 2.79/∣λ∣2.79/|\lambda|, while the first-order backward Euler method can take steps a thousand times larger. For non-smooth problems, high-order methods lose their advantage.

ViewpointsH

Many ways of seeing

Geometry— Distance to singularity, and integrators that respect geometry

The condition number is a distance. Theorem (Eckart–Young, Gastinel–Kahan): min⁡{∥δA∥2:A+δA singular}=σmin⁡(A)=1/∥A−1∥2\min\{\|\delta A\|_2 : A + \delta A \text{ singular}\} = \sigma_{\min}(A) = 1/\|A^{-1}\|_2, so 1κ2(A)\frac{1}{\kappa_2(A)} is the relative distance from AA to the nearest singular matrix. (Proof: if A+δAA + \delta A is singular, Theorem 2's argument shows ∥A−1∥∥δA∥≥1\|A^{-1}\|\|\delta A\| \ge 1; conversely, subtracting σmin⁡unvnT\sigma_{\min}u_nv_n^T from the SVD reaches a singular matrix.) Ill-conditioned problems are those near the "variety of ill-posed problems" — a geometric picture that Demmel (1987) extended to many other problems.

For differential equations, geometric integration asks methods to preserve the structure of the flow. Explicit Euler applied to the harmonic oscillator q˙=p\dot q = p, p˙=−q\dot p = -q multiplies the energy by 1+h21 + h^2 at every step, so the orbit spirals outwards. The symplectic Euler method, pn+1=pn−hqnp_{n+1} = p_n - hq_n, qn+1=qn+hpn+1q_{n+1} = q_n + hp_{n+1}, preserves area in phase space exactly, and its energy error stays bounded for exponentially long times. That is why symplectic methods are used for planetary orbits and molecular dynamics. See dynamical systems.

Analysis— Consistency + stability = convergence

Theorem 6 is a special case of a general principle. The Lax equivalence theorem (Lax and Richtmyer, 1956) says that for a consistent finite-difference approximation of a well-posed linear initial-value problem, stability is necessary and sufficient for convergence. The explicit heat-equation scheme of partial differential equations is the standard example: consistent for all r=kΔt/Δx2r = k\Delta t/\Delta x^2, stable (hence convergent) exactly for r≤12r \le \frac12. In the language of functional analysis, stability is a uniform bound on the powers of the discrete evolution operators, and the theorem is an application of the uniform boundedness principle. Backward error analysis has an analytic counterpart for ODEs too: a numerical integrator follows, to high accuracy, the exact flow of a modified differential equation, and studying that equation explains long-time behaviour.

Algebra— Factorisations are group decompositions

The factorisations of numerical linear algebra are classical decompositions of the group GLn(R)GL_n(\R). Gram–Schmidt proves the Iwasawa (QR) decomposition: every invertible AA is uniquely QRQR with QQ orthogonal and RR upper triangular with positive diagonal, so GLn(R)=O(n)⋅B+GL_n(\R) = O(n) \cdot B^+ as a product of subgroups. LU with pivoting is the Bruhat decomposition GLn=⨆wB−wBGL_n = \bigsqcup_{w} B^-wB over permutation matrices ww (with B±B^\pm the lower and upper triangular groups); the cell of the identity — matrices with an LU factorisation — is open and dense. The QR algorithm has a hidden integrable structure: its iterates are samples of the Toda flow, a Hamiltonian system on symmetric matrices that preserves eigenvalues (Symes, 1982; Deift, Nanda and Tomei, 1983).

Probability— Random matrices and why pivoting works

Worst-case analysis says partial pivoting can lose everything (ρ=2n−1\rho = 2^{n-1}); experience says it never does. Probability bridges the gap. For an n×nn \times n matrix with independent standard Gaussian entries, Edelman (1988) showed that E[log⁡κ2]=log⁡n+1.537…+o(1)\mathbb E[\log\kappa_2] = \log n + 1.537\ldots + o(1), so random matrices are only mildly ill-conditioned. Trefethen and Schreiber's experiments (1990) found typical growth factors of order n2/3n^{2/3}. Smoothed analysis (Spielman and Teng, 2004) studies the worst case over data perturbed by small random noise. Sankar, Spielman and Teng (2006) showed that for Gaussian elimination without pivoting the growth stays polynomial in nn and 1/σ1/\sigma with high probability, where σ\sigma is the noise level. A smoothed analysis that fully explains partial pivoting is still a research question.

Computation— Floating point, complexity and the cost of linear algebra

The IEEE 754 standard (1985), designed largely by William Kahan, fixed the model used here: correctly rounded basic operations, so that fl(x∘y)=(x∘y)(1+δ)\mathrm{fl}(x \circ y) = (x\circ y)(1 + \delta) is a guarantee, not an approximation. It also defined infinities, NaNs and gradual underflow. Its predictability is what makes backward error analysis possible. On the complexity side, Gaussian elimination costs O(n3)O(n^3), but Volker Strassen (1969) showed that matrix multiplication, and therefore solving linear systems and inversion, can be done in O(n2.81)O(n^{2.81}). The exponent ω\omega of matrix multiplication has been below 2.382.38 since Coppersmith and Winograd (1990), with small further improvements since 2010. Whether ω=2\omega = 2 is open, and these fast algorithms raise their own questions of numerical stability. See computational complexity.

History & global context

History and global context

  • Elimination. The Nine Chapters on the Mathematical Art (China, compiled by about the 1st century CE) solves systems of linear equations by the fangcheng method: elimination on a counting board, column by column, with negative numbers, essentially Gaussian elimination. Carl Friedrich Gauss used elimination systematically for least-squares problems in astronomy and geodesy (1809–1810), after Adrien-Marie Legendre published the method of least squares in 1805.
  • ODE methods. Leonhard Euler described his method in 1768. Carl Runge (1895), Karl Heun (1900) and Martin Wilhelm Kutta (1901) developed the methods named after them, and John Butcher's algebraic theory of order conditions (from 1963) made Runge–Kutta methods a subject in their own right.
  • The computer age. John von Neumann and Herman Goldstine's 1947 analysis of matrix inversion, and Alan Turing's 1948 paper "Rounding-off errors in matrix processes" (which introduced the term condition number and the LU language), founded rounding-error analysis. James Wilkinson developed backward error analysis at the National Physical Laboratory (Rounding Errors in Algebraic Processes, 1963; The Algebraic Eigenvalue Problem, 1965). Alston Householder introduced his reflections in 1958.
  • Eigenvalues. The QR algorithm was discovered independently in 1961 by John Francis in England and Vera Kublanovskaya in Leningrad. It is often listed among the most important algorithms of the twentieth century.
  • Stiffness and stability. Charles Curtiss and Joseph Hirschfelder named stiffness in 1952, in chemical kinetics. Germund Dahlquist in Stockholm proved his stability barriers (1956, 1963), and Peter Lax and Robert Richtmyer their equivalence theorem (1956).
  • Floating point. William Kahan led the design of IEEE 754 (1985), for which he received the Turing Award in 1989.
Further reading

Further reading

  • Lloyd N. Trefethen and David Bau III, Numerical Linear Algebra (1997) — forty short lectures on conditioning, stability, QR, least squares and eigenvalue algorithms.
  • Endre Süli and David F. Mayers, An Introduction to Numerical Analysis (2003) — a rigorous general course, including ODE methods and their convergence proofs.
  • Nicholas J. Higham, Accuracy and Stability of Numerical Algorithms (2nd edition, 2002) — the reference for rounding-error analysis, including Wilkinson's results.
  • Ernst Hairer, Syvert P. Nørsett and Gerhard Wanner, Solving Ordinary Differential Equations I (1987), and Hairer and Wanner, II: Stiff and Differential-Algebraic Problems (1991) — order conditions, stability and stiffness in depth.