Here are two classic infinite series for π. The Leibniz series is famous and dead simple — but painfully slow: it needs hundreds of terms just to pin down two decimals. The Nilakantha series (15th-century India) converges far faster from the same idea. Drag the slider and watch each estimate crawl, then sprint, toward 3.14159…
<p class="hint">Two infinite series for <b>π</b>. <b>Leibniz</b> is simple but slow; <b>Nilakantha</b> converges far faster. Drag the slider to add terms and watch each estimate close in on the true value, digit by digit.</p>
<label class="slabel">Terms: <b id="nval">50</b></label>
<input id="terms" type="range" min="1" max="2000" value="50" class="slider" />
<div class="rows">
<div class="row">
<span class="name">Leibniz</span>
<span class="val" id="leib">—</span>
<span class="acc" id="leibAcc"></span>
</div>
<div class="row">
<span class="name">Nilakantha</span>
<span class="val" id="nila">—</span>
<span class="acc" id="nilaAcc"></span>
</div>
<div class="row truth">
<span class="name">π (true)</span>
<span class="val">3.14159265358979</span>
<span class="acc"></span>
</div>
</div>
/* Layout for the two series rows and the slider. */
* { box-sizing: border-box; }
.hint { font-size: 14px; color: #556; line-height: 1.5; }
.slabel { display: block; font-size: 14px; margin: 12px 0 4px; color: #334; }
.slider { width: 100%; }
.rows { margin-top: 18px; font-family: 'JetBrains Mono', ui-monospace, monospace; }
.row {
display: grid; grid-template-columns: 110px 1fr auto; align-items: baseline;
gap: 10px; padding: 8px 10px; border-radius: 8px; background: #f3f5f8; margin-bottom: 6px;
}
.row.truth { background: #e1f5ee; }
.name { font-weight: 700; color: #1f6feb; }
.truth .name { color: #0f6e56; }
.val { font-size: 18px; letter-spacing: .3px; color: #223; white-space: nowrap; overflow: hidden; }
.acc { font-size: 12px; color: #0f6e56; font-weight: 600; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Two classic series for pi. Same idea (an alternating sum), wildly different speed.
const PI = Math.PI;
// Leibniz: pi = 4 * (1 - 1/3 + 1/5 - 1/7 + ...)
function leibniz(n) {
let s = 0;
for (let k = 0; k < n; k++) s += (k % 2 ? -1 : 1) / (2 * k + 1);
return 4 * s;
}
// Nilakantha: pi = 3 + 4/(2·3·4) - 4/(4·5·6) + 4/(6·7·8) - ...
function nilakantha(n) {
let s = 3;
for (let k = 1; k <= n; k++) {
const term = 4 / ((2 * k) * (2 * k + 1) * (2 * k + 2));
s += (k % 2 ? term : -term);
}
return s;
}
// How many leading decimal digits match the true value of pi.
function correctDigits(x) {
const err = Math.abs(x - PI);
if (err === 0) return 14;
return Math.max(0, Math.min(14, Math.floor(-Math.log10(err))));
}
const slider = document.getElementById('terms');
const nval = document.getElementById('nval');
const leibEl = document.getElementById('leib');
const nilaEl = document.getElementById('nila');
const leibAcc = document.getElementById('leibAcc');
const nilaAcc = document.getElementById('nilaAcc');
function update() {
const n = +slider.value;
nval.textContent = n;
const l = leibniz(n), m = nilakantha(n);
leibEl.textContent = l.toFixed(14);
nilaEl.textContent = m.toFixed(14);
leibAcc.textContent = fmt('%correctDigits% digits', { correctDigits: correctDigits(l) });
nilaAcc.textContent = fmt('%correctDigits% digits', { correctDigits: correctDigits(m) });
}
slider.addEventListener('input', update);
update();
<p class="hint">Two infinite series for <b>π</b>. <b>Leibniz</b> is simple but slow; <b>Nilakantha</b> converges far faster. Drag the slider to add terms and watch each estimate close in on the true value, digit by digit.</p>
<label class="slabel">Terms: <b id="nval">50</b></label>
<input id="terms" type="range" min="1" max="2000" value="50" class="slider" />
<div class="rows">
<div class="row">
<span class="name">Leibniz</span>
<span class="val" id="leib">—</span>
<span class="acc" id="leibAcc"></span>
</div>
<div class="row">
<span class="name">Nilakantha</span>
<span class="val" id="nila">—</span>
<span class="acc" id="nilaAcc"></span>
</div>
<div class="row truth">
<span class="name">π (true)</span>
<span class="val">3.14159265358979</span>
<span class="acc"></span>
</div>
</div>
/* Layout for the two series rows and the slider. */
* { box-sizing: border-box; }
.hint { font-size: 14px; color: #556; line-height: 1.5; }
.slabel { display: block; font-size: 14px; margin: 12px 0 4px; color: #334; }
.slider { width: 100%; }
.rows { margin-top: 18px; font-family: 'JetBrains Mono', ui-monospace, monospace; }
.row {
display: grid; grid-template-columns: 110px 1fr auto; align-items: baseline;
gap: 10px; padding: 8px 10px; border-radius: 8px; background: #f3f5f8; margin-bottom: 6px;
}
.row.truth { background: #e1f5ee; }
.name { font-weight: 700; color: #1f6feb; }
.truth .name { color: #0f6e56; }
.val { font-size: 18px; letter-spacing: .3px; color: #223; white-space: nowrap; overflow: hidden; }
.acc { font-size: 12px; color: #0f6e56; font-weight: 600; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Two classic series for pi. Same idea (an alternating sum), wildly different speed.
const PI = Math.PI;
// Leibniz: pi = 4 * (1 - 1/3 + 1/5 - 1/7 + ...)
function leibniz(n) {
let s = 0;
for (let k = 0; k < n; k++) s += (k % 2 ? -1 : 1) / (2 * k + 1);
return 4 * s;
}
// Nilakantha: pi = 3 + 4/(2·3·4) - 4/(4·5·6) + 4/(6·7·8) - ...
function nilakantha(n) {
let s = 3;
for (let k = 1; k <= n; k++) {
const term = 4 / ((2 * k) * (2 * k + 1) * (2 * k + 2));
s += (k % 2 ? term : -term);
}
return s;
}
// How many leading decimal digits match the true value of pi.
function correctDigits(x) {
const err = Math.abs(x - PI);
if (err === 0) return 14;
return Math.max(0, Math.min(14, Math.floor(-Math.log10(err))));
}
const slider = document.getElementById('terms');
const nval = document.getElementById('nval');
const leibEl = document.getElementById('leib');
const nilaEl = document.getElementById('nila');
const leibAcc = document.getElementById('leibAcc');
const nilaAcc = document.getElementById('nilaAcc');
function update() {
const n = +slider.value;
nval.textContent = n;
const l = leibniz(n), m = nilakantha(n);
leibEl.textContent = l.toFixed(14);
nilaEl.textContent = m.toFixed(14);
leibAcc.textContent = fmt('%correctDigits% digits', { correctDigits: correctDigits(l) });
nilaAcc.textContent = fmt('%correctDigits% digits', { correctDigits: correctDigits(m) });
}
slider.addEventListener('input', update);
update();
This is the whole lesson in one widget: both algorithms are correct, but one is wildly more efficient. Choosing the right series is the difference between two digits and two billion.
Comments
Loading comments...