This site is version 0.0.1. Most of the code on this site is drafted by an agent and needs review. I apologize
for the AI-speak, we are working on rewriting it!
The Runge phenomenon, where more points give a worse fit
Adam Zettel · September 21, 2026 · written with an agent
Interpolate 1/(1+25x2) by a polynomial through equally spaced points and the error grows as you add points. Move the same number of points to cos(jπ/n) and it falls geometrically, at a rate you can work out from where the function's poles are.
f(x)=1+25x21,∥f−pn∥∞→∞
The function has poles at x=±i/5. Polynomial interpolation in Chebyshev points converges like
ρ−n, where ρ is the size of the largest ellipse with foci ±1 that the function is
analytic inside. The ellipse through ±i/5 has ρ=51+1+251=1.2198.
Both interpolants use the barycentric formula, which is stable in Chebyshev points. With equally
spaced points the problem is ill-conditioned (its Lebesgue constant grows exponentially with n),
and this formula adds rounding error of its own.
The program’s map: every function and which calls which.
There’s no recorded run of this program yet.
Shift+Enter in a cell runs it and moves to the next. Running a cell runs every cell above it too, as one program.
[1]lines 20–121
pi =3.141592653589793
M =400
XS =linspace(-1.0, 1.0, M)
#| Runge's function, on an array of points.defrunge(x) {
return1.0/ (1.0+25.0* x * x)
}
#| The degree-n interpolant through the given nodes, by the barycentric formula, on XS.definterpolate(nodes, weights) {
n =len(nodes)
values =runge(nodes)
out =zeros(M)
for i to M {
top =0.0
bottom =0.0
hit =-1.0for j to n {
d = XS[i] - nodes[j]
ifabs(d) <1.0e-14 {
hit = j
}
else {
top = top + weights[j] * values[j] / d
bottom = bottom + weights[j] / d
}
}
if hit >=0.0 {
out[i] = values[hit]
}
else {
out[i] = top / bottom
}
}
return out
}
#| n + 1 equally spaced points, with barycentric weights (-1)^j C(n, j).defequispaced(n) {
nodes =linspace(-1.0, 1.0, n +1.0)
w =zeros(n +1.0)
w[0] =1.0for j to n {
w[j +1.0] =-w[j] * (n - j) / (j +1.0)
}
returninterpolate(nodes, w)
}
#| n + 1 Chebyshev points cos(j pi / n), with weights (-1)^j, halved at the two ends.defchebyshev(n) {
nodes =zeros(n +1.0)
w =zeros(n +1.0)
sign =1.0for j to n +1.0 {
nodes[j] =cos(j * pi / n)
w[j] = sign
sign =-sign
}
w[0] =0.5* w[0]
w[n] =0.5* w[n]
returninterpolate(nodes, w)
}
# a degree-20 interpolant goes through 21 points
DEGREE =20.0
POINTS = DEGREE +1.0# the function's poles are at x = +-i POLE
POLE =1.0/5.0
EXACT =runge(XS)
equi_10 =take the largest difference between equispaced(10.0) and EXACT
equi_20 =take the largest difference between equispaced(DEGREE) and EXACT
cheb_20 =take the largest difference between chebyshev(DEGREE) and EXACT
cheb_40 =take the largest difference between chebyshev(40.0) and EXACT
rate =pow(cheb_20 / cheb_40, 1.0/20.0)
predicted_rate = POLE +sqrt(1.0+ POLE * POLE)
show equi_10
show equi_20
show cheb_20
show cheb_40
show rate
show predicted_rate
figure("Interpolating $1/(1+25x^2)$ at 21 points", "x", "interpolant", -1.0, 1.0, -0.4, 1.4)
plot(XS, EXACT, "$1/(1 + 25x^2)$")
plot(XS, equispaced(20.0), "equally spaced")
plot(XS, chebyshev(20.0), "Chebyshev")
style("$1/(1 + 25x^2)$", "dash thick")
test"with equally spaced points, doubling them makes the error more than thirty times larger" {
assert_gt(equi_20, 30.0* equi_10)
}
test"with Chebyshev points the same 21 are good to two percent" {
assert_lt(cheb_20, 0.02)
}
test"and the error falls like rho^-n, with rho set by the poles at +-i/5" {
assert_near(rate, predicted_rate, 0.02)
}
# SPDX-License-Identifier: MIT OR Apache-2.0# SPDX-FileCopyrightText: 2026 The Viv authors