Numerical Integration (Quadrature)

You need \int_0^1 e^{-x^2}\,dx. Reach for the fundamental theorem of calculus and you hit a wall: e^{-x^2} has no antiderivative expressible in elementary functions. Neither does \frac{\sin x}{x}, nor \sqrt{1 + \cos^2 x} (the arc length of a sine wave), nor most integrands a scientist actually meets. The clean symbolic answer simply does not exist. And yet the area is a perfectly definite number — the bell curve encloses a real amount of ground. We just have to compute it.

This is quadrature: approximating a definite integral \int_a^b f(x)\,dx by sampling the integrand at a handful of points and combining those samples with clever weights. The name is ancient — "quadrature" meant constructing a square of equal area to a curved region, the very problem that stumped Greek geometers ("squaring the circle"). Today it is the engine inside every physics simulation, every financial model, every renderer that computes how much light reaches a pixel. This page teaches the one central idea — fit a shape you can integrate exactly, then add up the pieces — and shows why one particular choice of shape, the humble parabola, is astonishingly good.

From rectangles to better shapes

You already know the crudest quadrature rule. In Riemann sums we chopped [a, b] into n thin strips of width h = \tfrac{b-a}{n} and stood a rectangle on each. A rectangle approximates f on a strip by a constant — the flattest possible fit. It works, but it is wasteful: on a strip where the curve is visibly sloping, the flat top leaves an obvious triangular sliver of error.

So raise the ambition. Instead of fitting a constant on each panel, fit a straight line through the two endpoints — that is the trapezoid rule. Better still, fit a parabola through three points — that is Simpson's rule. Each step up matches more of the curve's shape, so each panel's error shrinks dramatically. This is the whole ladder of Newton–Cotes rules: sample f at equally spaced points, pass a polynomial of degree k through them (exactly the interpolating polynomial), and integrate that polynomial — which we can do exactly, since polynomials always have clean antiderivatives.

The trapezoid rule: fit a line on each panel

Take a single panel from x_0 to x_1 = x_0 + h. Join the two endpoints (x_0, f_0) and (x_1, f_1) with a straight line. The region under that line is a trapezoid — a rectangle of height f_0 with a triangle perched on top — and school geometry gives its area as width times the average of the two heights:

\int_{x_0}^{x_1} f(x)\,dx \;\approx\; h\cdot\frac{f_0 + f_1}{2}.

Now lay n panels side by side, with nodes x_0, x_1, \dots, x_n and f_i = f(x_i). Add the panels. Every interior node is shared by two neighbouring trapezoids, so it gets counted twice (weight 1), while the two endpoints appear only once (weight \tfrac12). Collecting terms gives the composite trapezoid rule:

T_n = h\left[\frac{f_0 + f_n}{2} + f_1 + f_2 + \dots + f_{n-1}\right] = h\left[\frac{f_0 + f_n}{2} + \sum_{i=1}^{n-1} f_i\right].

It is the midpoint's cousin from the Riemann page, but with the ends counted at half weight — and that small correction turns a first-order rule into a second-order one. The error of the composite trapezoid rule behaves like

E_T = -\frac{(b-a)h^2}{12}\, f''(\xi) = O(h^2),

for some point \xi in [a,b]. Two consequences fall straight out of that h^2. First, if f'' = 0 — that is, f is a straight line — the error is exactly zero: the trapezoid rule integrates linear functions perfectly, which makes sense because a line fits a line with nothing left over. Second, halving the step h multiplies h^2 by \tfrac14, so each doubling of n cuts the error by about a factor of four. Let us watch that happen.

// Composite trapezoid rule for the integral of f from a to b with n panels. function trapezoid(f: (x: number) => number, a: number, b: number, n: number): number { const h: number = (b - a) / n; let sum: number = (f(a) + f(b)) / 2; // endpoints at half weight for (let i = 1; i < n; i++) { sum += f(a + i * h); // interior nodes at full weight } return sum * h; } // Test on integral of sin(x) from 0 to pi, whose exact value is 2. const exact: number = 2; for (const n of [1, 2, 4, 8, 16, 32]) { const approx: number = trapezoid(Math.sin, 0, Math.PI, n); const err: number = Math.abs(approx - exact); console.log(`n=${n.toString().padStart(2)} T=${approx.toFixed(8)} error=${err.toExponential(3)}`); }

Read the error column top to bottom: each time n doubles the error falls to roughly a quarter of the line above (2^{-2} per doubling). That factor of four is the O(h^2) promise made visible.

Simpson's rule: fit a parabola over pairs of panels

A line matches a curve's height and slope but ignores its bend. Fit a parabola instead and you capture the curvature too. A parabola needs three points, so Simpson's rule works over a pair of panels at a time: pass the unique parabola through (x_0, f_0), (x_1, f_1), (x_2, f_2) and integrate it. A short calculation (integrate the interpolating parabola over [x_0, x_2]) collapses to a beautifully simple weighting — 1, 4, 1:

\int_{x_0}^{x_2} f(x)\,dx \;\approx\; \frac{h}{3}\big(f_0 + 4f_1 + f_2\big).

Chain these two-panel blocks along the whole interval (so n must be even). Interior nodes at odd index are always the middle of a block and pick up weight 4; interior nodes at even index sit where two blocks meet and collect 2 = 1 + 1; the two ends keep weight 1. That is the composite Simpson's rule:

S_n = \frac{h}{3}\Big[f_0 + f_n + 4\!\!\sum_{i\ \text{odd}}\!\! f_i + 2\!\!\sum_{i\ \text{even}}\!\! f_i\Big].

Its error is a whole two orders better than the trapezoid rule:

E_S = -\frac{(b-a)h^4}{180}\, f^{(4)}(\xi) = O(h^4).

The h^4 is the headline. Halving h now multiplies the error by \left(\tfrac12\right)^4 = \tfrac{1}{16}: each doubling of n cuts the error by about a factor of sixteen, not four. And here is the pleasant surprise buried in that fourth derivative.

Simpson's rule builds its estimate from parabolas, so you would expect it to nail quadratics and be wrong on anything with an x^3. Yet its error term carries f^{(4)} — the fourth derivative — which vanishes for any cubic. So Simpson integrates every cubic exactly, one degree beyond the parabola it actually draws. Why the free extra degree? Symmetry. Over the symmetric block [x_0, x_2] centred at x_1, the leftover cubic term is an odd function about the centre, so the bit it overshoots on the right is exactly the bit it undershoots on the left, and the two cancel in the integral. This lucky cancellation is why Simpson punches above its weight and why it, not the trapezoid rule, is the workhorse of hand-computation.

Worked example: Simpson on one block, by hand

Estimate \int_0^1 e^{x}\,dx (true value e - 1 \approx 1.718282) with a single Simpson block, n = 2. The nodes are x_0 = 0, x_1 = \tfrac12, x_2 = 1, so h = \tfrac12 and the heights are

f_0 = e^0 = 1, \quad f_1 = e^{1/2} \approx 1.648721, \quad f_2 = e^1 \approx 2.718282.

Apply the 1, 4, 1 weighting:

S_2 = \frac{h}{3}\big(f_0 + 4f_1 + f_2\big) = \frac{1/2}{3}\big(1 + 4(1.648721) + 2.718282\big) = \frac{1}{6}(10.313166) \approx 1.718861.

With just three function evaluations the error is only about 6\times10^{-4} — the trapezoid rule needs dozens of panels to match that. Now the same integral in code, both rules side by side so you can watch Simpson pull ahead.

// Composite Simpson's rule. n MUST be even (parabolas span pairs of panels). function simpson(f: (x: number) => number, a: number, b: number, n: number): number { if (n % 2 !== 0) throw new Error("Simpson needs an even number of panels"); const h: number = (b - a) / n; let sum: number = f(a) + f(b); // the two ends, weight 1 for (let i = 1; i < n; i++) { const w: number = i % 2 === 1 ? 4 : 2; // odd node -> 4, even node -> 2 sum += w * f(a + i * h); } return (sum * h) / 3; } // Integral of e^x from 0 to 1; exact answer is e - 1. const exact: number = Math.E - 1; for (const n of [2, 4, 8, 16]) { const approx: number = simpson(Math.exp, 0, 1, n); console.log(`n=${n.toString().padStart(2)} S=${approx.toFixed(10)} error=${Math.abs(approx - exact).toExponential(3)}`); }

Each doubling of n slices the error to about a sixteenth of the previous row — the O(h^4) rate, plain as day.

Order of accuracy: the race, side by side

"Order" is the exponent p in E \approx C h^p. It decides how fast you buy accuracy by spending function evaluations. Trapezoid has p = 2; Simpson has p = 4. That gap sounds modest until you compound it: to gain one more decimal digit (\times\tfrac{1}{10} error), trapezoid must shrink h by \sqrt{10}\approx 3.2, while Simpson needs only 10^{1/4}\approx 1.8. Over many refinements the two curves diverge wildly. Put them in one table for \int_0^\pi \sin x\,dx = 2 and count the digits.

function trapezoid(f: (x: number) => number, a: number, b: number, n: number): number { const h: number = (b - a) / n; let s: number = (f(a) + f(b)) / 2; for (let i = 1; i < n; i++) s += f(a + i * h); return s * h; } function simpson(f: (x: number) => number, a: number, b: number, n: number): number { const h: number = (b - a) / n; let s: number = f(a) + f(b); for (let i = 1; i < n; i++) s += (i % 2 === 1 ? 4 : 2) * f(a + i * h); return (s * h) / 3; } const exact: number = 2; console.log(" n | trap error | ratio | simpson error | ratio"); let prevT: number = NaN; let prevS: number = NaN; for (const n of [2, 4, 8, 16, 32, 64]) { const eT: number = Math.abs(trapezoid(Math.sin, 0, Math.PI, n) - exact); const eS: number = Math.abs(simpson(Math.sin, 0, Math.PI, n) - exact); const rT: string = isNaN(prevT) ? " - " : (prevT / eT).toFixed(1); const rS: string = isNaN(prevS) ? " - " : (prevS / eS).toFixed(1); console.log(`${n.toString().padStart(2)} | ${eT.toExponential(3)} | ${rT.padStart(5)} | ${eS.toExponential(3)} | ${rS}`); prevT = eT; prevS = eS; }

The ratio columns are the punchline: trapezoid's error shrinks by about 4 per row, Simpson's by about 16. By n = 64 Simpson is already near the floor of double-precision arithmetic while trapezoid still limps along with a handful of correct digits. The chart below plots exactly this race on a log scale.

Beyond Newton–Cotes: smarter points, smarter panels

Newton–Cotes rules are handed their sample points — equally spaced, take it or leave it. But where you sample is a free choice, and spending it wisely buys a huge return. That is the idea of Gaussian quadrature: choose both the nodes and the weights to make the rule exact for the highest-degree polynomial possible. With m cleverly placed points — bunched toward the ends of the interval, at the roots of certain special (Legendre) polynomials — a Gauss rule integrates every polynomial up to degree 2m - 1 exactly, roughly double the degree a Newton–Cotes rule reaches with the same number of samples. For smooth integrands it is spectacularly efficient: a handful of well-chosen points can beat thousands of trapezoids.

The other big idea is adaptive quadrature. Not all of an integrand is equally hard: a function might be nearly flat across most of [a,b] and then spike sharply in one narrow region. Spreading panels uniformly wastes effort on the flat part and starves the spike. Adaptive schemes instead estimate the local error on each subinterval (typically by comparing a coarse and a fine Simpson estimate) and recursively subdivide only where the error is too large, packing panels densely where the action is and leaving them sparse where the curve is tame. This is what a library routine like scipy.integrate.quad actually does under the hood — a Gauss rule wrapped in adaptive error control.

Simpson's glorious O(h^4) comes with fine print: the error term is -\tfrac{(b-a)h^4}{180} f^{(4)}(\xi), which is only meaningful if f has a well-behaved fourth derivative across the whole interval. Feed Simpson a function with a kink (like |x| at the origin), a jump, or a singularity (like \tfrac{1}{\sqrt{x}} near 0), and f^{(4)} blows up right where you needed it small. The O(h^4) rate collapses — often all the way down to O(h) or worse — and Simpson loses its advantage over the trapezoid rule entirely. Two habits protect you: split the interval at any kink or discontinuity so each piece is smooth, and reach for adaptive methods (or a change of variables) near singularities. High order is a reward for smoothness, never a free lunch.

Simpson's rule glues together two-panel parabolic blocks, so it is only defined when n is even — an odd n leaves a lone panel with no partner and no parabola. A careless loop that runs Simpson with n = 7 will still return a number, but it will be the wrong number: the weights 1,4,2,4,\dots fall out of step and the estimate quietly loses its O(h^4) accuracy. Always assert n is even (as the code above does) — or use a rule designed for odd counts, such as Simpson's \tfrac38 rule, which works over blocks of three panels.