Numbers that are almost right
Nearly four thousand years ago a scribe in Mesopotamia pressed a small clay tablet — now called YBC 7289 and kept at Yale — with a square, its diagonals, and the number in base . That is , the ratio of a square's diagonal to its side, correct to about six decimal places. The true value is irrational: no finite expansion in any base is exact. Every practical computation with it is an approximation.
Modern computers face the same fact on a vast scale. A weather model, a bridge design or a vaccine-trial analysis performs billions of arithmetic operations on numbers stored with about sixteen significant digits, so almost every operation rounds. Usually that is harmless. Sometimes it is not: in 1982 the Vancouver Stock Exchange started a new index at and recomputed it thousands of times a day, truncating to three decimals each time; within two years it read about while the correctly computed value was above .
Numerical analysis is the mathematics of computing with approximations: designing methods that find roots, derivatives and integrals from finitely many arithmetic operations, and proving how large their errors can be. This lesson treats the three classical sources of error — rounding, truncation and the failure of hypotheses — through floating-point arithmetic, root finding, interpolation and differentiation, and numerical integration.
It assumes calculus of one variable (the mean value theorem and Taylor's theorem with remainder) and some programming.
Three kinds of error, and how fast they shrink
Computing on a laptop involves three different approximations.
- Modelling error: the integral itself may be only a model of something physical. Numerical analysis takes the mathematical problem as given.
- Truncation (discretisation) error: we replace the integral by a finite sum, say the trapezoidal rule with strips. Even with exact arithmetic the sum differs from the integral, typically by about .
- Rounding error: every operation in the sum is done in floating point and is rounded to about significant digits.
Truncation error shrinks as we work harder; rounding error tends to grow with the number of operations. Good methods balance the two, and good analysis predicts the balance. The key measure of a method is how fast its error shrinks: an error that is halved at each step (bisection) gains one correct binary digit per step, while an error that is squared at each step (Newton's method) doubles the number of correct digits per step:
Definitions
Definition— floating-point number
A (binary, normalised) floating-point number with precision is a number where the significand is an integer with and the exponent is an integer in a fixed range. The IEEE 754 binary64 format ("double precision", Python's float) has and exponents giving magnitudes from about to , plus zero, subnormal numbers, and NaN ("not a number").
Definition— machine epsilon and unit roundoff
Machine epsilon is the gap between and the next larger floating-point number: , which is for binary64. The unit roundoff is , the largest relative error made by rounding to nearest.
Definition— absolute and relative error
If approximates , its absolute error is and, for , its relative error is . A relative error of about means about correct significant digits.
Definition— order of convergence
A sequence with errors converges with order if for some constant and all large (with when ). Order is linear, order quadratic. In practice one estimates .
Definition— condition number
For a differentiable function with , the (relative) condition number measures how much a small relative change in is magnified in . A problem with a large condition number is ill-conditioned: no method can solve it accurately from inexact data.
Floating point and rounding error
Theorem— Proposition 1 (rounding)
If is a real number in the normal range of binary64 and is the nearest binary64 number, then with .
Proof
Take and let . In this interval the binary64 numbers are for , spaced apart (and is also one). So the nearest one is within half the spacing: .
∎
IEEE 754 requires , , , and to be correctly rounded: the computed result is of the exact result. This gives the standard model , , on which error analysis is built. So floating-point arithmetic is not random or vague; it is exact arithmetic followed by one well-defined rounding. But it is not ordinary arithmetic either: is stored as , so in floating point, and addition is not associative: but .
Theorem— Theorem 2 (cancellation)
Suppose and with , and . Then
Proof
, and .
∎
The subtraction itself is exact or nearly so; the danger is that it exposes errors already present in and . If and agree in their first digits, the factor is about , and digits are lost. This is catastrophic cancellation, and is exactly the condition number of subtraction.
Example— Two cures for cancellation
- For small , is close to , and computed directly loses digits: at the computer finds rounds to exactly and returns instead of . The identity removes the subtraction, and is accurate to full precision.
- The roots of are about and . The formula for the small root subtracts two nearly equal numbers and is wrong by . Computing the large root first and using the product of the roots, , gives the small root to full precision.
A computed answer is backward stable if it is the exact answer to a slightly perturbed problem (perturbations of relative size about ). Then its accuracy is limited only by the problem's condition number: error condition number backward error. This way of thinking, due largely to James Wilkinson, separates what is the method's fault from what is the problem's.
Finding roots: bisection and Newton's method
Bisection. Given a continuous on with , look at the midpoint . If , replace by ; otherwise replace by . Repeat.
Theorem— Theorem 3 (bisection)
After steps the interval has length , still satisfies , and contains a root of . The midpoint satisfies . So bisection always converges, linearly with rate .
Proof
The length is halved at each step, and the sign condition is preserved by construction: we keep only when , and only when . By the intermediate value theorem has a root , and the midpoint of an interval is within half its length of every point in it.
∎
Bisection is slow — about steps per decimal digit — but it needs only continuity and a sign change, and it cannot fail. Its proof rests on the completeness of : the nested intervals shrink to a point, which is a root.
Newton's method. Replace near the current guess by its tangent line, and take the tangent's zero as the next guess:
For this is — the method Heron of Alexandria described in the first century CE. (It is one plausible way the Babylonian value could have been found, but how the scribes actually computed it is not recorded.)
Theorem— Theorem 4 (local quadratic convergence of Newton's method)
Let be twice continuously differentiable on an open interval containing , with and . Then there is such that for every starting value with , the Newton iterates are defined, stay within of , converge to , and satisfy
Moreover .
Proof
Since is continuous and , there is with on . Let be the quotient computed on (finite, since and are continuous on the closed interval), and put (or if ). Then on the smaller interval.
Suppose . Taylor's theorem with the Lagrange remainder at gives a between and with
Dividing by and rearranging,
Hence . By induction every iterate stays within of , , and the quadratic bound holds at every step. Finally and , so by continuity the ratio tends to .
∎
The hypotheses are all needed, and Newton's method can fail badly when they are not met or when the start is too far away.
Example— When Newton's method fails or slows down
- No convergence from a bad start. For and : and . The iterates cycle forever, although has a real root near . For , starts with (roughly) make the iterates oscillate with growing size.
- Not differentiable at the root. For (real cube root), does not exist and : every start except diverges.
- A double root. For , and : convergence, but only linear. (For a root of multiplicity , the modified step restores quadratic convergence.)
The secant method replaces by the slope through the last two points. It needs no derivative, and under the hypotheses of Theorem 4 it converges with order : its errors satisfy , and the exponents of follow the Fibonacci recurrence (see recurrences). Practical root finders, such as Brent's method, combine bisection's safety with the speed of secant-type steps.
Interpolation and numerical differentiation
Given distinct nodes and values , there is exactly one polynomial of degree at most with for all : the Lagrange form is , and uniqueness holds because the difference of two such polynomials has roots and degree at most .
Theorem— Theorem 5 (interpolation error)
If has continuous derivatives on , which contains the nodes, then for every there is with
Proof
If is a node, both sides are . Otherwise let and choose the constant so that vanishes at . Then has at least zeros in (the nodes and ). By Rolle's theorem has at least zeros between them, at least , …, and at least one, . Since and , we get , and is the claimed formula.
∎
More nodes need not help. For Runge's function on , interpolants at equally spaced nodes oscillate wildly near the ends and diverge as (Runge, 1901), because is huge there and the derivatives of grow fast. Choosing Chebyshev nodes makes as small as possible, namely , and for functions as smooth as this one the interpolants converge.
Numerical differentiation. Taylor's theorem gives
(for the central difference, use the intermediate value theorem to combine the two third-derivative terms). So the truncation error of the forward difference is and of the central difference . But if each value of carries an error up to , the forward difference also carries a rounding error up to , so its total error is at most about
With the best step is , giving only about correct digits; smaller is worse. For the central difference the optimum is , with about digits. The third experiment shows both V-shaped error curves.
Numerical integration
Divide into strips of width with points . The composite trapezoidal rule and, for even , the composite Simpson rule are
The trapezoidal rule integrates the piecewise-linear interpolant of ; Simpson's rule integrates a quadratic interpolant on each pair of strips.
Theorem— Theorem 6 (trapezoidal rule)
If is twice differentiable on with , then .
Proof
Consider one strip, shifted to , and define for : the error of a single trapezoid of width . Then and, by the fundamental theorem of calculus,
So . Integrating from (using , then ): and . Each of the strips therefore contributes at most , and .
∎
Theorem— Theorem 7 (Simpson's rule)
If is four times differentiable on with and is even, then .
Proof
Consider one pair of strips, shifted to , and for let . Differentiating three times (each time some terms cancel):
with . By the mean value theorem for some , so . Integrating three times from : , , . There are pairs, each contributing at most , and .
∎
So for smooth integrands, halving divides the trapezoidal error by about and Simpson's by about . The bounds also explain degrees of exactness: the trapezoidal rule is exact for polynomials of degree (), Simpson's rule for degree () — one degree more than its quadratic interpolants would suggest, by symmetry — but not for . Two warnings, both visible in the third experiment:
- Hypotheses matter. For on , is unbounded near ; both rules then converge only like .
- Bounds are not estimates. For a smooth periodic function integrated over a full period, the trapezoidal rule converges faster than any power of (the error terms cancel; this follows from the Euler–Maclaurin formula), so it beats Simpson's rule there.
Combining and as cancels the term of the error and gives exactly Simpson's rule; repeating the idea (Richardson extrapolation) gives Romberg integration.
Numerical analysis in Lean
Lean has a hardware type Float, but its operations are opaque to proofs: Lean can run 0.1 + 0.2 but cannot reason about it. So we formalise a model: binary64 numbers as real numbers of the form , and rounding as "a relative error of at most ". First, machine epsilon — no binary64 number lies strictly between and :
/-- A positive **binary64 number** (an IEEE 754 "double"), ignoring the limits on the exponent:
`m · 2ᵉ` with an integer *significand* `m` of exactly 53 bits, `2⁵² ≤ m < 2⁵³`, and an integer
exponent `e`. -/
def IsBinary64 (x : ℝ) : Prop :=
∃ m e : ℤ, 2 ^ 52 ≤ m ∧ m < 2 ^ 53 ∧ x = m * (2 : ℝ) ^ e/-- `1` and `1 + 2⁻⁵²` are both binary64 numbers: `1 = 2⁵² · 2⁻⁵²` and
`1 + 2⁻⁵² = (2⁵² + 1) · 2⁻⁵²`. -/
theorem one_add_eps_is_binary64 : IsBinary64 1 ∧ IsBinary64 (1 + 2 ^ (-52 : ℤ)) := by
refine ⟨⟨2 ^ 52, -52, le_refl _, by norm_num, ?_⟩, ⟨2 ^ 52 + 1, -52, by norm_num, by norm_num, ?_⟩⟩
· norm_num
· norm_num/-- **Machine epsilon.** No binary64 number lies strictly between `1` and `1 + 2⁻⁵²`, so the gap
between `1` and the next larger double is `ε = 2⁻⁵² ≈ 2.2 × 10⁻¹⁶`.
Proof: if `1 < m 2ᵉ < 1 + 2⁻⁵² < 2` with `2⁵² ≤ m < 2⁵³`, then `2⁵³⁺ᵉ > 1` and `2⁵²⁺ᵉ < 2`, which
forces `e = −52`; but then `2⁵² < m < 2⁵² + 1`, impossible for an integer `m`. -/
theorem machine_epsilon (x : ℝ) (hx : IsBinary64 x) : ¬ (1 < x ∧ x < 1 + 2 ^ (-52 : ℤ)) := by
rintro ⟨h1, h2⟩
obtain ⟨m, e, hm1, hm2, rfl⟩ := hx
have two_pos : (0 : ℝ) < 2 := by norm_num
have hm1' : (2 : ℝ) ^ (52 : ℤ) ≤ m := by exact_mod_cast hm1
have hm2' : (m : ℝ) < 2 ^ (53 : ℤ) := by exact_mod_cast hm2
have hpow : (0 : ℝ) < 2 ^ e := zpow_pos two_pos e
have heps : (2 : ℝ) ^ (-52 : ℤ) < 1 := zpow_lt_one_of_neg₀ (by norm_num) (by norm_num)
-- Lower bound on `e`: `1 < m 2ᵉ < 2⁵³ · 2ᵉ = 2^(53 + e)`.
have hlo : (1 : ℝ) < 2 ^ (53 + e) := by
rw [zpow_add₀ two_pos.ne']
calc (1 : ℝ) < m * 2 ^ e := h1
_ ≤ 2 ^ (53 : ℤ) * 2 ^ e := by gcongr
have he1 : 0 < 53 + e := by
by_contra hc
rw [not_lt] at hc
have : (2 : ℝ) ^ (53 + e) ≤ 1 := zpow_le_one_of_nonpos₀ (by norm_num) hc
linarith
-- Upper bound on `e`: `2^(52 + e) = 2⁵² · 2ᵉ ≤ m 2ᵉ < 1 + 2⁻⁵² < 2 = 2¹`.
have hhi : (2 : ℝ) ^ (52 + e) < 2 ^ (1 : ℤ) := by
rw [zpow_add₀ two_pos.ne', zpow_one]
calc (2 : ℝ) ^ (52 : ℤ) * 2 ^ e ≤ m * 2 ^ e := by gcongr
_ < 1 + 2 ^ (-52 : ℤ) := h2
_ < 2 := by linarith
have he2 : 52 + e < 1 := (zpow_lt_zpow_iff_right₀ (by norm_num : (1 : ℝ) < 2)).mp hhi
have he : e = -52 := by omega
subst he
-- Now `2⁵² < m < 2⁵² + 1` after multiplying through by `2⁵²`.
have key : (2 : ℝ) ^ (52 : ℤ) * 2 ^ (-52 : ℤ) = 1 := by
rw [← zpow_add₀ two_pos.ne']; norm_num
have hmlo : (2 : ℝ) ^ (52 : ℤ) < m := by
by_contra hc
rw [not_lt] at hc
have : (m : ℝ) * 2 ^ (-52 : ℤ) ≤ 2 ^ (52 : ℤ) * 2 ^ (-52 : ℤ) := by gcongr
linarith
have hmhi : (m : ℝ) < 2 ^ (52 : ℤ) + 1 := by
by_contra hc
rw [not_lt] at hc
have : (2 ^ (52 : ℤ) + 1) * (2 : ℝ) ^ (-52 : ℤ) ≤ m * 2 ^ (-52 : ℤ) := by gcongr
rw [add_mul, key, one_mul] at this
linarith
have hz1 : (2 : ℤ) ^ 52 < m := by exact_mod_cast hmlo
have hz2 : m < (2 : ℤ) ^ 52 + 1 := by exact_mod_cast hmhi
omegaThe cancellation bound (Theorem 2), with the standard model as a definition:
/-- The **standard model of floating-point arithmetic**: `x̂` is `x` with a relative error of at
most `u`, i.e. `x̂ = x (1 + δ)` for some `|δ| ≤ u`. For binary64 with rounding to nearest,
`u = 2⁻⁵³`, half of machine epsilon. -/
def RoundsTo (u x xhat : ℝ) : Prop :=
∃ δ : ℝ, |δ| ≤ u ∧ xhat = x * (1 + δ)/-- **Cancellation amplifies relative error.** If `x̂` and `ŷ` approximate `x` and `y` with
relative errors at most `u`, then the exact difference `x̂ − ŷ` is within `u (|x| + |y|)` of
`x − y`: `(x̂ − ŷ) − (x − y) = x δ₁ − y δ₂`. Dividing by `|x − y|`, the relative error of the
difference can be as large as `u (|x| + |y|)/|x − y|`, which is huge when `x ≈ y`. -/
theorem cancellation_bound {u x y xhat yhat : ℝ} (hx : RoundsTo u x xhat)
(hy : RoundsTo u y yhat) : |(xhat - yhat) - (x - y)| ≤ u * (|x| + |y|) := by
obtain ⟨d₁, hd₁, rfl⟩ := hx
obtain ⟨d₂, hd₂, rfl⟩ := hy
calc |(x * (1 + d₁) - y * (1 + d₂)) - (x - y)| = |x * d₁ + -(y * d₂)| := by ring_nf
_ ≤ |x * d₁| + |-(y * d₂)| := abs_add_le _ _
_ = |x| * |d₁| + |y| * |d₂| := by rw [abs_neg, abs_mul, abs_mul]
_ ≤ |x| * u + |y| * u := by gcongr
_ = u * (|x| + |y|) := by ringBisection (Theorem 3). The method is a definition; its loop invariant and its halving property are proved by induction, and Mathlib's intermediate value theorem supplies the root:
/-- One step of **bisection** on an interval `[a, b]` (stored as a pair): look at the midpoint
`m`; keep `[m, b]` if `f m ≤ 0` and `[a, m]` otherwise, so a sign change is kept. -/
noncomputable def bisectStep (f : ℝ → ℝ) (p : ℝ × ℝ) : ℝ × ℝ :=
if f ((p.1 + p.2) / 2) ≤ 0 then ((p.1 + p.2) / 2, p.2) else (p.1, (p.1 + p.2) / 2)/-- The interval after `n` steps of bisection, starting from `[a, b]`. -/
noncomputable def bisect (f : ℝ → ℝ) (a b : ℝ) : ℕ → ℝ × ℝ
| 0 => (a, b)
| n + 1 => bisectStep f (bisect f a b n)/-- **Bisection halves the interval**: after `n` steps its width is `(b − a)/2ⁿ`. -/
theorem bisect_width (f : ℝ → ℝ) (a b : ℝ) (n : ℕ) :
(bisect f a b n).2 - (bisect f a b n).1 = (b - a) / 2 ^ n := by
induction n with
| zero => simp [bisect]
| succ k ih =>
simp only [bisect, bisectStep]
split_ifs <;> simp only [pow_succ] <;> linear_combination ih / 2/-- **The loop invariant of bisection**: if `f a ≤ 0 ≤ f b`, then after every step the left end
still has `f ≤ 0` and the right end `f ≥ 0`. -/
theorem bisect_sign (f : ℝ → ℝ) (a b : ℝ) (ha : f a ≤ 0) (hb : 0 ≤ f b) (n : ℕ) :
f (bisect f a b n).1 ≤ 0 ∧ 0 ≤ f (bisect f a b n).2 := by
induction n with
| zero => exact ⟨ha, hb⟩
| succ k ih =>
simp only [bisect, bisectStep]
split_ifs with h
· exact ⟨h, ih.2⟩
· exact ⟨ih.1, (lt_of_not_ge h).le⟩/-- **Bisection always works for a continuous function with a sign change.** If `f` is
continuous, `a ≤ b` and `f a ≤ 0 ≤ f b`, then for every `n` there is a root `c` of `f` within
`(b − a)/2ⁿ⁺¹` of the `n`-th midpoint. The root comes from the intermediate value theorem
(Mathlib's `intermediate_value_Icc`) applied to the current interval. -/
theorem bisection_converges (f : ℝ → ℝ) (hf : Continuous f) (a b : ℝ) (hab : a ≤ b)
(ha : f a ≤ 0) (hb : 0 ≤ f b) (n : ℕ) :
∃ c, f c = 0 ∧
|((bisect f a b n).1 + (bisect f a b n).2) / 2 - c| ≤ (b - a) / 2 ^ (n + 1) := by
set p := bisect f a b n with hp
have hw : p.2 - p.1 = (b - a) / 2 ^ n := bisect_width f a b n
have hs := bisect_sign f a b ha hb n
have hle : p.1 ≤ p.2 := by
have : 0 ≤ (b - a) / 2 ^ n := div_nonneg (by linarith) (by positivity)
linarith
obtain ⟨c, ⟨hc1, hc2⟩, hfc⟩ :=
intermediate_value_Icc hle hf.continuousOn ⟨hs.1, hs.2⟩
refine ⟨c, hfc, ?_⟩
rw [abs_le, pow_succ]
constructor <;>
· rw [div_mul_eq_div_div] at *
linarithNewton's method. For square roots the error identity is exact algebra, which gives quadratic convergence directly. For a general we formalise the algebraic step of Theorem 4's proof, and cite Mathlib's Taylor theorem, which supplies the remainder term; we have not assembled the full local convergence theorem (choosing and running the induction) in Lean.
/-- **Heron's method is Newton's method for `x² = a`**: `x' = (x + a/x)/2`. Its error satisfies
`x' − √a = (x − √a)² / (2x)` exactly: the new error is proportional to the *square* of the old
one. -/
theorem heron_error {a x : ℝ} (ha : 0 ≤ a) (hx : 0 < x) :
(x + a / x) / 2 - √a = (x - √a) ^ 2 / (2 * x) := by
have hs : √a ^ 2 = a := sq_sqrt ha
field_simp
linear_combination (-1 : ℝ) * hs/-- From any positive start, every Heron iterate is at least `√a` (an instance of the AM–GM
inequality, read off from `heron_error`: the right-hand side is a square over a positive
number). -/
theorem heron_ge_sqrt {a x : ℝ} (ha : 0 ≤ a) (hx : 0 < x) : √a ≤ (x + a / x) / 2 := by
have h := heron_error ha hx
have : 0 ≤ (x - √a) ^ 2 / (2 * x) := by positivity
linarith/-- **Quadratic convergence of Heron's method.** If `x ≥ √a > 0`, the new error is at most the
old error squared, divided by `2√a`. So once the error is below `√a`, the number of correct
digits roughly doubles at every step. -/
theorem heron_quadratic {a x : ℝ} (ha : 0 < a) (hx : √a ≤ x) :
|(x + a / x) / 2 - √a| ≤ (x - √a) ^ 2 / (2 * √a) := by
have hs : 0 < √a := sqrt_pos.mpr ha
have hx0 : 0 < x := lt_of_lt_of_le hs hx
rw [heron_error ha.le hx0, abs_of_nonneg (by positivity)]
apply div_le_div_of_nonneg_left (by positivity) (by positivity)
linarith/-- **The algebraic heart of Newton's convergence proof.** Suppose `f(r) = 0` and Taylor's
theorem at `x` gives `0 = f(r) = f(x) + f'(x)(r − x) + c (r − x)²` (with `c = f''(ξ)/2`). If
`f'(x) ≠ 0`, the Newton step `x' = x − f(x)/f'(x)` satisfies `x' − r = (c / f'(x)) (x − r)²`. -/
theorem newton_error_step {x r fx dfx c : ℝ} (hd : dfx ≠ 0)
(htaylor : fx + dfx * (r - x) + c * (r - x) ^ 2 = 0) :
(x - fx / dfx) - r = c / dfx * (x - r) ^ 2 := by
have hfx : fx = -(dfx * (r - x) + c * (r - x) ^ 2) := by linarith
rw [hfx]
field_simp
ring/-- **Taylor's theorem with the Lagrange remainder**, as Mathlib states it
(`taylor_mean_remainder_lagrange`): if `f` is `n` times continuously differentiable on the
interval between `x₀` and `x`, and its `n`-th derivative is differentiable inside, then
`f(x) − Tₙ(x) = f⁽ⁿ⁺¹⁾(ξ)(x − x₀)ⁿ⁺¹/(n + 1)!` for some `ξ` strictly between. With `n = 1` this
supplies the constant `c = f''(ξ)/2` in `newton_error_step`. -/
theorem taylor_lagrange {f : ℝ → ℝ} {x x₀ : ℝ} {n : ℕ} (hx : x₀ ≠ x)
(hf : ContDiffOn ℝ n f (uIcc x₀ x))
(hf' : DifferentiableOn ℝ (iteratedDerivWithin n f (uIcc x₀ x)) (uIoo x₀ x)) :
∃ x' ∈ uIoo x₀ x, f x - taylorWithinEval f n (uIcc x₀ x) x₀ x =
iteratedDerivWithin (n + 1) f (uIcc x₀ x) x' * (x - x₀) ^ (n + 1) / (n + 1).factorial :=
taylor_mean_remainder_lagrange hx hf hf'Mathlib also has Newton's method for polynomials over any commutative ring, with an algebraic form of quadratic convergence (see the algebraist's viewpoint below):
/-- **Quadratic convergence, algebraically.** For a polynomial `P` over a commutative ring, if
`P(x)` is nilpotent and `P'(x)` is invertible, then after `n` Newton steps `P(x)^(2ⁿ)` divides
the new value of `P`: the "error" is raised to the power `2ⁿ`. This is Mathlib's
`Polynomial.aeval_pow_two_pow_dvd_aeval_iterate_newtonMap`, the engine of Hensel's lemma. -/
theorem newton_algebraic {R S : Type*} [CommRing R] [CommRing S] [Algebra R S] {P : R[X]}
{x : S} (h : IsNilpotent (aeval x P)) (h' : IsUnit (aeval x (derivative P))) (n : ℕ) :
(aeval x P) ^ (2 ^ n) ∣ aeval (P.newtonMap^[n] x) P :=
aeval_pow_two_pow_dvd_aeval_iterate_newtonMap h h' nIntegration. Simpson's rule is exact for cubics but not for , proved with the fundamental theorem of calculus; and Mathlib contains the composite trapezoidal rule with the error bound of Theorem 6 — proved, as it happens, by the same "differentiate the error twice" argument as the lesson. Simpson's error bound (Theorem 7) is not formalised here.
/-- **Simpson's rule integrates cubics exactly**:
`∫ₐᵇ p = (b − a)/6 · (p(a) + 4 p((a + b)/2) + p(b))` for `p(x) = c₃x³ + c₂x² + c₁x + c₀`.
Proof: an antiderivative `P` gives `∫ₐᵇ p = P(b) − P(a)` (the fundamental theorem of calculus,
Mathlib's `integral_eq_sub_of_hasDerivAt`), and the two sides agree as polynomials in `a, b`. -/
theorem simpson_exact_cubic (c₃ c₂ c₁ c₀ a b : ℝ) :
∫ x in a..b, (c₃ * x ^ 3 + c₂ * x ^ 2 + c₁ * x + c₀) =
(b - a) / 6 * ((c₃ * a ^ 3 + c₂ * a ^ 2 + c₁ * a + c₀) +
4 * (c₃ * ((a + b) / 2) ^ 3 + c₂ * ((a + b) / 2) ^ 2 + c₁ * ((a + b) / 2) + c₀) +
(c₃ * b ^ 3 + c₂ * b ^ 2 + c₁ * b + c₀)) := by
have hderiv : ∀ x ∈ uIcc a b, HasDerivAt
(fun x => c₃ * x ^ 4 / 4 + c₂ * x ^ 3 / 3 + c₁ * x ^ 2 / 2 + c₀ * x)
(c₃ * x ^ 3 + c₂ * x ^ 2 + c₁ * x + c₀) x := by
intro x _
have h := ((((hasDerivAt_pow 4 x).const_mul c₃).div_const 4).add
(((hasDerivAt_pow 3 x).const_mul c₂).div_const 3)).add
(((hasDerivAt_pow 2 x).const_mul c₁).div_const 2) |>.add ((hasDerivAt_id x).const_mul c₀)
convert h using 1
· ext y; simp
· push_cast; ring
rw [intervalIntegral.integral_eq_sub_of_hasDerivAt hderiv
(by apply Continuous.intervalIntegrable; fun_prop)]
ring/-- …but not quartics: on `[0, 1]`, `∫ x⁴ = 1/5`, while Simpson's rule gives
`(1/6)(0 + 4 · (1/2)⁴ + 1) = 5/24`. So the degree of exactness of Simpson's rule is exactly `3`,
which is why its error involves the fourth derivative. -/
theorem simpson_not_exact_quartic :
∫ x in (0 : ℝ)..1, x ^ 4 = 1 / 5 ∧
(1 - 0) / 6 * ((0 : ℝ) ^ 4 + 4 * ((0 + 1) / 2) ^ 4 + 1 ^ 4) = 5 / 24 := by
constructor
· rw [integral_pow]; norm_num
· norm_num/-- **The error of the composite trapezoidal rule** with `N` strips is at most
`|b − a|³ ζ / (12 N²)` when `|f''| ≤ ζ` on the interval — that is, `(b − a) h² ζ / 12` with
`h = (b − a)/N`: the `O(h²)` of the lesson. This applies Mathlib's
`trapezoidal_error_le_of_c2`, whose proof is the lesson's proof (differentiate the error of one
strip twice). -/
theorem trapezoid_error {f : ℝ → ℝ} {a b : ℝ} (hf : ContDiffOn ℝ 2 f (uIcc a b)) {ζ : ℝ}
(hζ : ∀ x, |iteratedDerivWithin 2 f (uIcc a b) x| ≤ ζ) {N : ℕ} (hN : 0 < N) :
|trapezoidal_error f N a b| ≤ |b - a| ^ 3 * ζ / (12 * N ^ 2) :=
trapezoidal_error_le_of_c2 hf hζ hNExperiments: rounding, convergence and balance
The first experiment finds machine epsilon, shows the exact value Python stores for , and watches cancellation destroy and the quadratic formula — and the rearranged formulas repair them. Note how the naive value at is printed with twelve digits but has only four correct.
"""Floating point: machine epsilon, and cancellation that destroys accuracy.
Python floats are IEEE 754 binary64 numbers. We find machine epsilon by
halving until 1 + eps is no longer bigger than 1, check that 0.1 + 0.2 is not
the float nearest to 0.3, and then watch catastrophic cancellation: computing
(1 - cos x)/x^2 for small x loses all its digits, although every operation is
correctly rounded, while the algebraically equal 2 sin^2(x/2)/x^2 stays
accurate. The same happens to the small root of x^2 - 10^8 x + 1 in the
schoolbook quadratic formula, and a rearranged formula fixes it.
"""
import math
from fractions import Fraction
def machine_epsilon():
"""Smallest power of two eps with 1 + eps > 1 in floating point."""
eps = 1.0
while 1.0 + eps / 2 > 1.0:
eps /= 2
return eps
def naive_one_minus_cos(x):
"""(1 - cos x) / x^2, computed directly (suffers cancellation)."""
return (1 - math.cos(x)) / x ** 2
def stable_one_minus_cos(x):
"""The same quantity as 2 sin^2(x/2) / x^2: no subtraction of close numbers."""
s = math.sin(x / 2)
return 2 * s * s / x ** 2
def one_minus_cos_series(x, terms=8):
"""(1 - cos x)/x^2 from its Taylor series sum (-1)^k x^(2k) / (2k + 2)!."""
return sum((-1) ** k * x ** (2 * k) / math.factorial(2 * k + 2)
for k in range(terms))
def relative_error(approx, exact):
"""|approx - exact| / |exact|."""
return abs(approx - exact) / abs(exact)
def quadratic_roots_naive(b, c):
"""Roots of x^2 + b x + c by the schoolbook formula (b < 0 assumed)."""
d = math.sqrt(b * b - 4 * c)
return (-b + d) / 2, (-b - d) / 2
def quadratic_roots_stable(b, c):
"""Large root by the formula without cancellation, small root as c / large."""
d = math.sqrt(b * b - 4 * c)
big = (-b + d) / 2 if b < 0 else (-b - d) / 2
return big, c / big
def main():
eps = machine_epsilon()
print(f"machine epsilon = {eps!r} = 2^{round(math.log2(eps))}")
print(f"1 + eps/2 == 1: {1 + eps / 2 == 1}")
print(f"0.1 is stored as {Fraction(0.1)}")
print(f"0.1 + 0.2 = {0.1 + 0.2!r}; equal to 0.3: {0.1 + 0.2 == 0.3}")
print()
print("(1 - cos x)/x^2 (true value from its Taylor series, about 1/2):")
print(" x naive rel. error stable rel. error")
for k in range(1, 9):
x = 10.0 ** (-k)
exact = one_minus_cos_series(x)
naive = naive_one_minus_cos(x)
print(f" 1e-{k:d} {naive:.12f} {relative_error(naive, exact):10.1e}"
f" {relative_error(stable_one_minus_cos(x), exact):10.1e}")
print()
b, c = -1e8, 1.0
small_true = 1e-8 + 1e-24 # small root of x^2 - 1e8 x + 1, to 32 digits
naive_small = quadratic_roots_naive(b, c)[1]
stable_small = quadratic_roots_stable(b, c)[1]
print("Small root of x^2 - 10^8 x + 1 (true value 1.0000...e-08):")
print(f" schoolbook formula: {naive_small:.10e} rel. error "
f"{relative_error(naive_small, small_true):.1e}")
print(f" c / (large root): {stable_small:.10e} rel. error "
f"{relative_error(stable_small, small_true):.1e}")
print()
print("Epistemic status: exact facts about IEEE binary64 arithmetic as")
print("implemented by this Python (all mainstream platforms agree). The")
print("errors are measured against series and exact expansions; they")
print("illustrate, not prove, the cancellation bound of the lesson.")
if __name__ == "__main__":
main()The second solves with -digit decimal arithmetic, so rounding does not mask the convergence, and estimates the orders of bisection, Newton and the secant method; then it shows Newton's method at a double root and in a -cycle.
"""Bisection, Newton and secant: measuring orders of convergence.
We solve x^2 = 2 with 50-digit decimal arithmetic, so that rounding does not
hide the behaviour, and print the errors e_n = |x_n - sqrt 2|. Bisection
halves its error bound each step (order 1, rate 1/2); Newton's method roughly
doubles the number of correct digits (order 2); the secant method is in
between (order (1 + sqrt 5)/2 = 1.618...). The estimated order is
log(e_{n+1}/e_n) / log(e_n/e_{n-1}). Then two warnings: at the double root of
(x - 1)^2 Newton converges only linearly, and for x^3 - 2x + 2 started at 0
it never converges, cycling 0, 1, 0, 1, ...
"""
import math
from decimal import Decimal, getcontext
getcontext().prec = 50
def bisection(f, a, b, steps):
"""Midpoints of the bisection intervals, assuming f(a) < 0 < f(b)."""
mids = []
for _ in range(steps):
m = (a + b) / 2
mids.append(m)
if f(m) <= 0:
a = m
else:
b = m
return mids
def newton(f, df, x, steps):
"""Newton iterates x_{n+1} = x_n - f(x_n)/f'(x_n), starting value included."""
xs = [x]
for _ in range(steps):
x = x - f(x) / df(x)
xs.append(x)
return xs
def secant(f, x0, x1, steps):
"""Secant iterates, starting values included; stops if f values coincide."""
xs = [x0, x1]
for _ in range(steps):
f0, f1 = f(xs[-2]), f(xs[-1])
if f1 == f0:
break
xs.append(xs[-1] - f1 * (xs[-1] - xs[-2]) / (f1 - f0))
return xs
def orders(errors):
"""Estimated orders log(e2/e1)/log(e1/e0) for consecutive triples."""
out = []
for e0, e1, e2 in zip(errors, errors[1:], errors[2:]):
if min(e0, e1, e2) > 0 and e1 != e0:
out.append(math.log(e2 / e1) / math.log(e1 / e0))
return out
def main():
root = Decimal(2).sqrt()
f = lambda x: x * x - 2
df = lambda x: 2 * x
mids = bisection(f, Decimal(1), Decimal(2), 8)
errs = [float(abs(x - root)) for x in mids]
print("bisection errors: " + " ".join(f"{e:.0e}" for e in errs))
print(" bounds: " + " ".join(f"{0.5 ** (n + 1):.0e}"
for n in range(len(mids))))
runs = [
("Newton", newton(f, df, Decimal(2), 6)[1:]),
("secant", secant(f, Decimal(2), Decimal(1), 7)[2:]),
]
for name, xs in runs:
errs = [float(abs(x - root)) for x in xs]
shown = " ".join(f"{e:.0e}" for e in errs)
est = orders(errs)
print(f"{name:9s} errors: {shown}")
print(f"{'':9s} estimated orders: "
+ ", ".join(f"{p:.2f}" for p in est[:5]))
print()
g = lambda x: (x - 1) ** 2
dg = lambda x: 2 * (x - 1)
xs = newton(g, dg, Decimal(2), 6)
print("Newton at the double root of (x - 1)^2, errors:")
print(" " + " ".join(f"{float(x - 1):g}" for x in xs))
h = lambda x: x ** 3 - 2 * x + 2
dh = lambda x: 3 * x * x - 2
cyc = newton(h, dh, Decimal(0), 6)
print("Newton for x^3 - 2x + 2 from 0:", [int(x) for x in cyc])
print()
print("Epistemic status: numerical evidence for one equation; the orders")
print("1, 2 and 1.618 are theorems for simple roots of smooth functions")
print("(proved for bisection and Newton in the lesson). The last two runs")
print("are exact counterexamples showing that the hypotheses matter.")
if __name__ == "__main__":
main()The third shows the V-shaped total error of difference quotients, and how the smoothness hypotheses govern the convergence of the trapezoidal and Simpson rules.
"""Truncation error against rounding error, and when error bounds fail.
Part 1: the forward difference (f(x + h) - f(x))/h approximates f'(x) with a
truncation error about h|f''|/2, but rounding errors in f contribute about
2u|f|/h. As h shrinks the total error first falls and then rises again, with
its minimum near h = sqrt(u) = 1e-8; the central difference does better,
with its minimum near u^(1/3) = 1e-5.
Part 2: the composite trapezoid and Simpson rules on two integrands. For the
periodic function e^(cos x) on [0, 2 pi] the trapezoid rule converges far
faster than its O(h^2) bound suggests. For sqrt(x) on [0, 1], whose
derivatives blow up at 0, both rules converge only like h^1.5: the smoothness
hypotheses of the error theorems are not met.
"""
import math
def forward_difference(f, x, h):
"""(f(x + h) - f(x)) / h."""
return (f(x + h) - f(x)) / h
def central_difference(f, x, h):
"""(f(x + h) - f(x - h)) / (2h)."""
return (f(x + h) - f(x - h)) / (2 * h)
def trapezoid(f, a, b, n):
"""Composite trapezoid rule with n strips."""
h = (b - a) / n
return h * (0.5 * f(a) + sum(f(a + i * h) for i in range(1, n)) + 0.5 * f(b))
def simpson(f, a, b, n):
"""Composite Simpson rule with n strips (n even)."""
if n <= 0 or n % 2:
raise ValueError("Simpson's rule needs a positive even number of strips")
h = (b - a) / n
odd = sum(f(a + i * h) for i in range(1, n, 2))
even = sum(f(a + i * h) for i in range(2, n, 2))
return h / 3 * (f(a) + 4 * odd + 2 * even + f(b))
def bessel_i0(x, terms=30):
"""Modified Bessel function I0(x) = sum (x/2)^(2k) / (k!)^2."""
return sum((x / 2) ** (2 * k) / math.factorial(k) ** 2 for k in range(terms))
def main():
exact = math.cos(1.0)
print("Derivative of sin at 1: errors of difference quotients")
print(" h forward central")
for k in range(1, 14, 2):
h = 10.0 ** (-k)
fe = abs(forward_difference(math.sin, 1.0, h) - exact)
ce = abs(central_difference(math.sin, 1.0, h) - exact)
print(f" 1e-{k:02d} {fe:9.1e} {ce:9.1e}")
print()
per_exact = 2 * math.pi * bessel_i0(1.0)
print("Trapezoid errors for e^cos(x) on [0, 2 pi] (periodic):")
for n in (2, 4, 8, 16):
err = abs(trapezoid(lambda t: math.exp(math.cos(t)), 0, 2 * math.pi, n)
- per_exact)
print(f" n = {n:2d}: {err:.1e}")
print("sqrt(x) on [0, 1] (exact 2/3): errors and observed orders")
print(" n trapezoid Simpson")
prev = None
for n in (16, 64, 256, 1024):
te = abs(trapezoid(math.sqrt, 0, 1, n) - 2 / 3)
se = abs(simpson(math.sqrt, 0, 1, n) - 2 / 3)
rate = "" if prev is None else (
f" orders {math.log(prev[0] / te, 4):.2f},"
f" {math.log(prev[1] / se, 4):.2f}")
print(f" {n:4d} {te:9.2e} {se:9.2e}{rate}")
prev = (te, se)
print()
print("Epistemic status: floating-point measurements for a few functions.")
print("They agree with the error analysis (truncation ~ h or h^2, rounding")
print("~ u/h) and show that the O(h^2) and O(h^4) quadrature bounds need")
print("bounded derivatives; they do not prove any rate.")
if __name__ == "__main__":
main()Explore: Newton's method, Runge's phenomenon, the trapezoidal rule
Newton's method is the iteration of the map , and a root of is a fixed point of with — which is why the cobweb hits the fixed point so fast. For , start at and count the steps until the cobweb is indistinguishable from the fixed point.
Newton's method for x² = 2
The map x ↦ (x + 2/x)/2 has a superattracting fixed point at √2.
Iterates xₖ against k
- y = f(x)
- y = x
- cobweb path
Last iterates: x6 = 1.41421, x7 = 1.41421, x8 = 1.41421
The orbit appears to settle at a fixed point x* ≈ 1.41421.
Fixed points in view (numerical): x* = 1.4142 (attracting: |f′(x*)| < 1)
Based on 3,000 iterations in floating-point arithmetic: numerical evidence, not proof.
Now the cubic , started at . The cobweb settles into a square: a cycle of period . Try other starting values — those below about find the root near .
Newton's method can cycle
For x³ − 2x + 2 the iterates from 0 alternate 0, 1, 0, 1, …
Iterates xₖ against k
- y = f(x)
- y = x
- cobweb path
Last iterates: x18 = 0, x19 = 1, x20 = 0
The orbit appears to settle into a cycle of period 2: 0, 1.
Fixed points in view (numerical): x* = −1.7693 (attracting: |f′(x*)| < 1)
Based on 3,000 iterations in floating-point arithmetic: numerical evidence, not proof.
Runge's phenomenon: Runge's function, its degree- interpolant at eleven equally spaced nodes (marked), and its degree- interpolant at Chebyshev nodes (coefficients rounded). The first overshoots to almost near ; the second stays within about everywhere.
Runge's phenomenon
Move the sliders and trace along the curves to read off values.
- 1/(1 + 25x²)
- equally spaced nodes
- Chebyshev nodes
- 1/(1 + 25x²): y = 0.0385
- equally spaced nodes: y = 0.0385
- Chebyshev nodes: y = −0.0054
Finally, the trapezoidal rule on the periodic function over : increase the number of strips from to and watch the error collapse far faster than . Then switch to the midpoint or left rule — for periodic functions over a full period they are just as good.
The trapezoidal rule on a periodic function
Approximate the area under a curve with thin strips, and watch the error shrink as the strips get thinner.
- f(x) = exp(cos(x))
Trapezoid sum with 4 strips
T4 = 7.989323
Exact value of the integral
∫02π f(x) dx = 7.954927
Error (sum − integral)
0.034397
All four rules with n = 4
| Rule | Sum | Error |
|---|---|---|
| Left (L4) | 7.989323 | 0.034397 |
| Right (R4) | 7.989323 | 0.034397 |
| Midpoint (M4) | 7.920532 | −0.034394 |
| Trapezoid (T4) | 7.989323 | 0.034397 |
Double n and watch the error: left and right sums roughly halve it, midpoint and trapezoid sums cut it to about a quarter.
Exercises
1. Numerical methods
Warm-upHumanBisection is started on to find as a root of . After halvings we report the midpoint of the current interval. What is the smallest for which Theorem 3 guarantees an error of at most ?
2. Numerical methods
Warm-upHumanNewton's method for is . Starting from , compute exactly , and in lowest terms. Enter the numerator .
3. Numerical methods
CoreHumanFor large (say ), which formula computes most accurately in floating point?
4. Numerical methods
CoreHumanNewton's method for (the real cube root) gives and diverges from every , although . Which hypothesis of Theorem 4 fails?
5. Numerical methods
CoreHumanApply Simpson's rule with strips () to . What value does it give?
6. Numerical methods
CoreHumanWhich statements are true? (Choose all that apply.)
7. Numerical methods
CoreHumanIn binary64 arithmetic, approximately which step minimises the total error of the forward difference for a typical smooth function with , of moderate size?
8. Numerical methods
CoreFormalWith IsBinary64 x meaning " is a positive binary64 number", which Lean statement says that is the gap between and the next larger binary64 number?
9. Numerical methods
StretchComputationalWrite newton(f, df, x0, tol=1e-12, max_iter=50) that runs Newton's method and returns (x, n): the first iterate with abs(f(x)) <= tol and the number of steps taken. If a derivative is exactly 0, or max_iter steps pass without success, return (None, n).
Common misconceptions
Misconception
"Printing more digits means more accuracy." The number of digits printed is a formatting choice; the number of correct digits is decided by the error analysis. Python prints the naive value of at as , of which only four digits are right; and printed to twenty digits is , which says something about binary storage, not about your problem.
Misconception
"Newton's method converges from any starting point." Theorem 4 is local: it guarantees convergence only from starts close enough to a simple root. From , Newton's method for cycles forever; for it diverges from every start except ; at a double root it converges only linearly. Robust software safeguards Newton steps with bisection.
Misconception
"A smaller step size is always more accurate." For difference quotients, rounding error grows like ; below the forward difference gets worse, and at it is typically useless.
Misconception
"Floating-point arithmetic is random or unreliable." It is deterministic and precisely specified: each basic operation returns the correctly rounded exact result. What fails are expectations borrowed from real arithmetic — associativity, exact decimals, and tests like 0.1 + 0.2 == 0.3. Compare floats with a tolerance chosen by error analysis.
Misconception
"A higher-order rule is always better." The bound for Simpson's rule needs a bounded fourth derivative. For on both rules converge like , and for periodic functions over a period the trapezoidal rule beats Simpson's rule by far.
Many ways of seeing
Analysis— Iteration, contraction and completeness
Newton's method is a fixed-point iteration . If a map sends a closed interval into itself and on , the mean value theorem gives : is a contraction, and the Banach fixed-point theorem says it has exactly one fixed point in , reached from every start, with linear rate . The order of convergence is governed by derivatives at the fixed point: if the convergence is linear; if it is quadratic. For Newton, vanishes at a simple root, which is the analytic reason for quadratic convergence. The Newton–Kantorovich theorem (1948) extends Theorem 4 to systems of equations and to Banach spaces, with a computable test on the starting point. See real analysis and numerical analysis.
Algebra— Newton's method without limits: Hensel lifting
The identity behind quadratic convergence is algebraic. Over any commutative ring, if and is invertible modulo , then satisfies , by Taylor expansion of the polynomial . For example, has the solution ; one Newton step lifts it to , and indeed . Repeating gives solutions modulo — Hensel's lemma, the -adic Newton method that builds in the -adic numbers. Mathlib's newton_algebraic above is a version with "error" meaning a nilpotent element: after steps divides at the new point. The same idea, with power series in place of numbers, computes reciprocals and square roots of power series in computer algebra. See algebraic number theory and congruences.
Geometry— Tangent lines and basins in the complex plane
Geometrically, each Newton step follows the tangent line of the graph down to the axis; convexity explains much of its behaviour (if is increasing and convex on , every start to the right of converges monotonically). In the complex plane the same formula solves polynomial equations, and Arthur Cayley (1879) asked which starting points converge to which root. For the answer is clean: the half-planes and . For the three basins of attraction have a common, fractal boundary — the Julia set of the Newton map — and every point of that boundary is a boundary point of all three basins. See complex analysis and dynamical systems.
Topology— Why bisection cannot fail
Bisection is the intermediate value theorem made into an algorithm, and the intermediate value theorem is a topological statement: a continuous image of the connected interval is connected, so it contains every value between and . The proof that the nested intervals close in on a root uses the completeness of ; over , the function changes sign on but has no root. In higher dimensions the analogue of a sign change is a non-zero degree (winding number) on a boundary: if a map winds non-trivially around on a loop, it has a zero inside (Brouwer), and bisection-like subdivision methods can locate it. See point-set topology.
Probability— Monte Carlo: an error that ignores dimension
To integrate over the unit cube , average at independent uniformly random points. By the central limit theorem the error is about , where is the variance of — whatever the dimension. A tensor-product trapezoidal rule with points has only points per axis, so its error behaves like , which beats Monte Carlo only when . This is why high-dimensional integrals in statistics, finance and physics are computed by random sampling, and the error statement is probabilistic: a confidence interval, not a bound. (Rounding errors themselves are sometimes modelled as random, which explains why accumulated error often grows like rather than the worst case — a heuristic, not a theorem.) See probability theory.
Computation— Cost, stability and standards
Speed of convergence is not the whole story: one Newton step costs an evaluation of and of , one secant step only of , so per function evaluation the secant method (order ) is often faster than Newton (order per two evaluations, about per evaluation). For linear systems, Gaussian elimination with partial pivoting is backward stable in practice, while computing the inverse matrix explicitly is slower and less accurate; for least squares, the QR factorisation is preferred to the normal equations because squaring the matrix squares its condition number. Behind all of this stands the IEEE 754 standard (1985, revised 2008 and 2019), which made floating-point results reproducible across machines. See algorithm design.
History and global context
- Ancient approximation. The Old Babylonian tablet YBC 7289 (c. 1800–1600 BCE) gives to about six decimal places. Heron of Alexandria (1st century CE) described the iterative square-root method now seen as Newton's method for . The Chinese Nine Chapters on the Mathematical Art solved problems by the "excess and deficit" method, a form of linear interpolation (double false position). Liu Hui (263 CE) approximated with polygons of up to 3072 sides, and Zu Chongzhi (5th century) showed . In Samarkand, Jamshīd al-Kāshī computed to the equivalent of sixteen decimal places (1424) and found by a fixed-point iteration on a cubic equation.
- Newton, Raphson and Simpson. Isaac Newton described his method for polynomial equations in De analysi (written 1669, published 1711), with the famous example ; Joseph Raphson published a streamlined version (1690), and Thomas Simpson (1740) gave the general form for non-polynomial equations using derivatives. Johannes Kepler's barrel rule (1615) is Simpson's rule for one panel, which Bonaventura Cavalieri and James Gregory also knew; Thomas Simpson popularised it (1743).
- Interpolation. Newton developed divided-difference interpolation; the "Lagrange" form was published by Edward Waring (1779) and Leonhard Euler (1783) before Joseph-Louis Lagrange (1795). Carl Runge exhibited the divergence of equally spaced interpolation in 1901, and Pafnuty Chebyshev's polynomials (1850s) underlie the cure.
- The computer age. John von Neumann and Herman Goldstine analysed rounding errors in matrix inversion (1947); James H. Wilkinson developed backward error analysis (Rounding Errors in Algebraic Processes, 1963). William Kahan led the design of the IEEE 754 floating-point standard (1985), for which he received the Turing Award (1989).
Further reading
- Richard L. Burden and J. Douglas Faires, Numerical Analysis (1978; many later editions, now with Annette Burden) — a standard first course with many worked examples.
- Endre Süli and David F. Mayers, An Introduction to Numerical Analysis (2003) — rigorous and concise, with complete proofs of the error bounds.
- Nicholas J. Higham, Accuracy and Stability of Numerical Algorithms (1996; 2nd edition 2002) — the reference on floating-point error analysis.
- David Goldberg, "What every computer scientist should know about floating-point arithmetic", ACM Computing Surveys 23 (1991) — a classic tutorial on IEEE arithmetic.
- Lloyd N. Trefethen, Approximation Theory and Approximation Practice (2013) — interpolation, Chebyshev points and Runge's phenomenon done right.
- Lloyd N. Trefethen and David Bau III, Numerical Linear Algebra (1997) — conditioning and stability in the setting where they matter most.
Next in the Lumenary: numerical analysis (linear systems, eigenvalues and differential equations), ordinary differential equations (Euler and Runge–Kutta methods) and optimisation (Newton's method for minimisation).