The Riemann Hypothesis says every nontrivial zero of ζ(s) lives on the critical line Re(s)=21. Below, we walk up that line — fixing the real part at 21 and increasing the height t — and plot how big the zeta function is at each point. Whenever the curve dives all the way to the floor, you have found a zero.
<p class="hint">We walk up the <b>critical line</b> Re(s) = ½, raising the height
t, and plot how big the zeta function is. Every time the curve dives to the floor,
that height is a <b>zero</b> — and the Riemann Hypothesis says they all live here.</p>
<canvas id="plot" width="600" height="230"></canvas>
<div class="status" id="status">Press “Scan the line” to walk from t = 0 upward.</div>
<div class="btns">
<button id="scan" type="button">Scan the line</button>
<button id="step" type="button" class="ghost">Step +1</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
canvas { width: 100%; max-width: 600px; height: auto; background: #f4f7fa;
border: 1px solid #cdd9e3; border-radius: 8px; display: block; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// The Riemann zeta function on the critical line s = 1/2 + i t.
// We evaluate it with the Dirichlet eta (alternating) series, which converges
// for Re(s) > 0: eta(s) = sum_{n>=1} (-1)^(n-1) / n^s ,
// zeta(s) = eta(s) / (1 - 2^(1-s)).
// The Riemann Hypothesis (Riemann, 1859 — open Clay Millennium Prize problem)
// says every nontrivial zero has real part exactly 1/2, i.e. it sits on THIS line.
// |zeta(1/2 + it)| crashes to ~0 at t = 14.13, 21.02, 25.01, 30.42, ... — the zeros.
const SIGMA = 0.5, TERMS = 4000, TMAX = 35;
const canvas = document.getElementById('plot'), ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height, PAD = 26;
function zetaAbs(t){
// eta = sum sign/n^s ; 1/n^s = n^-sigma * (cos(-t ln n) + i sin(-t ln n))
let er = 0, ei = 0;
for (let n = 1; n <= TERMS; n++){
const lnn = Math.log(n), mag = Math.pow(n, -SIGMA), ang = -t * lnn;
const s = (n % 2 === 1) ? 1 : -1;
er += s * mag * Math.cos(ang);
ei += s * mag * Math.sin(ang);
}
// 2^(1-s) = 2^(1-sigma) * (cos(-t ln2) + i sin(-t ln2))
const ln2 = Math.log(2), m = Math.pow(2, 1 - SIGMA), a = -t * ln2;
const dr = 1 - m * Math.cos(a), di = -m * Math.sin(a);
const d = dr*dr + di*di;
const zr = (er*dr + ei*di) / d, zi = (ei*dr - er*di) / d;
return Math.hypot(zr, zi);
}
let t = 0, timer = null, found = [];
const x = tt => PAD + (tt / TMAX) * (W - 2*PAD);
const y = v => H - PAD - Math.min(v, 4) / 4 * (H - 2*PAD);
function axes(){
ctx.clearRect(0,0,W,H);
ctx.strokeStyle = '#cdd9e3'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(PAD,H-PAD); ctx.lineTo(W-PAD,H-PAD); ctx.stroke();
ctx.fillStyle = '#7a8aa0'; ctx.font = '11px system-ui';
for (let tt = 0; tt <= TMAX; tt += 5){
ctx.fillText('t='+tt, x(tt)-8, H-8);
ctx.strokeStyle = '#e3eaf1'; ctx.beginPath(); ctx.moveTo(x(tt),PAD); ctx.lineTo(x(tt),H-PAD); ctx.stroke();
}
}
function draw(){
axes();
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2; ctx.beginPath();
let first = true;
for (let tt = 0; tt <= t + 1e-9; tt += 0.15){
const v = zetaAbs(tt); const px = x(tt), py = y(v);
if (first){ ctx.moveTo(px,py); first = false; } else ctx.lineTo(px,py);
}
ctx.stroke();
// mark detected zeros
for (const z of found){
ctx.fillStyle = '#e63946';
ctx.beginPath(); ctx.arc(x(z), y(0), 5, 0, 7); ctx.fill();
ctx.fillStyle = '#c92f3c'; ctx.font = 'bold 11px system-ui';
ctx.fillText(z.toFixed(2), x(z)-12, y(0)-9);
}
}
function detect(){
// a zero ~ a local minimum below a small threshold
found = [];
const step = 0.05; let prev = zetaAbs(0), prev2 = prev;
for (let tt = step; tt <= TMAX; tt += step){
const cur = zetaAbs(tt);
if (tt - step > 1e-9 && prev < prev2 && prev < cur && prev < 0.25)
found.push(+(tt - step).toFixed(2));
prev2 = prev; prev = cur;
}
}
function status(){
const s = document.getElementById('status');
if (found.length) s.textContent = fmt('Zeros found on the line so far: t ≈ %found% (all on Re(s)=½)', { found: found.join(', ') });
else s.textContent = fmt('Walking up to t = %t% …', { t: t.toFixed(1) });
}
function refresh(){ detect(); found = found.filter(z => z <= t + 0.3); draw(); status(); }
function reset(){ if (timer){clearInterval(timer); timer=null;} t = 0; found = []; axes();
document.getElementById('status').textContent = 'Press “Scan the line” to walk from t = 0 upward.'; }
document.getElementById('scan').addEventListener('click', () => {
if (timer) return; t = 0; found = [];
timer = setInterval(() => {
t += 1; if (t >= TMAX){ t = TMAX; clearInterval(timer); timer = null; }
refresh();
}, 180);
});
document.getElementById('step').addEventListener('click', () => {
if (timer) return; t = Math.min(TMAX, t + 1); refresh();
});
document.getElementById('reset').addEventListener('click', reset);
axes();
<p class="hint">We walk up the <b>critical line</b> Re(s) = ½, raising the height
t, and plot how big the zeta function is. Every time the curve dives to the floor,
that height is a <b>zero</b> — and the Riemann Hypothesis says they all live here.</p>
<canvas id="plot" width="600" height="230"></canvas>
<div class="status" id="status">Press “Scan the line” to walk from t = 0 upward.</div>
<div class="btns">
<button id="scan" type="button">Scan the line</button>
<button id="step" type="button" class="ghost">Step +1</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
canvas { width: 100%; max-width: 600px; height: auto; background: #f4f7fa;
border: 1px solid #cdd9e3; border-radius: 8px; display: block; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// The Riemann zeta function on the critical line s = 1/2 + i t.
// We evaluate it with the Dirichlet eta (alternating) series, which converges
// for Re(s) > 0: eta(s) = sum_{n>=1} (-1)^(n-1) / n^s ,
// zeta(s) = eta(s) / (1 - 2^(1-s)).
// The Riemann Hypothesis (Riemann, 1859 — open Clay Millennium Prize problem)
// says every nontrivial zero has real part exactly 1/2, i.e. it sits on THIS line.
// |zeta(1/2 + it)| crashes to ~0 at t = 14.13, 21.02, 25.01, 30.42, ... — the zeros.
const SIGMA = 0.5, TERMS = 4000, TMAX = 35;
const canvas = document.getElementById('plot'), ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height, PAD = 26;
function zetaAbs(t){
// eta = sum sign/n^s ; 1/n^s = n^-sigma * (cos(-t ln n) + i sin(-t ln n))
let er = 0, ei = 0;
for (let n = 1; n <= TERMS; n++){
const lnn = Math.log(n), mag = Math.pow(n, -SIGMA), ang = -t * lnn;
const s = (n % 2 === 1) ? 1 : -1;
er += s * mag * Math.cos(ang);
ei += s * mag * Math.sin(ang);
}
// 2^(1-s) = 2^(1-sigma) * (cos(-t ln2) + i sin(-t ln2))
const ln2 = Math.log(2), m = Math.pow(2, 1 - SIGMA), a = -t * ln2;
const dr = 1 - m * Math.cos(a), di = -m * Math.sin(a);
const d = dr*dr + di*di;
const zr = (er*dr + ei*di) / d, zi = (ei*dr - er*di) / d;
return Math.hypot(zr, zi);
}
let t = 0, timer = null, found = [];
const x = tt => PAD + (tt / TMAX) * (W - 2*PAD);
const y = v => H - PAD - Math.min(v, 4) / 4 * (H - 2*PAD);
function axes(){
ctx.clearRect(0,0,W,H);
ctx.strokeStyle = '#cdd9e3'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(PAD,H-PAD); ctx.lineTo(W-PAD,H-PAD); ctx.stroke();
ctx.fillStyle = '#7a8aa0'; ctx.font = '11px system-ui';
for (let tt = 0; tt <= TMAX; tt += 5){
ctx.fillText('t='+tt, x(tt)-8, H-8);
ctx.strokeStyle = '#e3eaf1'; ctx.beginPath(); ctx.moveTo(x(tt),PAD); ctx.lineTo(x(tt),H-PAD); ctx.stroke();
}
}
function draw(){
axes();
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2; ctx.beginPath();
let first = true;
for (let tt = 0; tt <= t + 1e-9; tt += 0.15){
const v = zetaAbs(tt); const px = x(tt), py = y(v);
if (first){ ctx.moveTo(px,py); first = false; } else ctx.lineTo(px,py);
}
ctx.stroke();
// mark detected zeros
for (const z of found){
ctx.fillStyle = '#e63946';
ctx.beginPath(); ctx.arc(x(z), y(0), 5, 0, 7); ctx.fill();
ctx.fillStyle = '#c92f3c'; ctx.font = 'bold 11px system-ui';
ctx.fillText(z.toFixed(2), x(z)-12, y(0)-9);
}
}
function detect(){
// a zero ~ a local minimum below a small threshold
found = [];
const step = 0.05; let prev = zetaAbs(0), prev2 = prev;
for (let tt = step; tt <= TMAX; tt += step){
const cur = zetaAbs(tt);
if (tt - step > 1e-9 && prev < prev2 && prev < cur && prev < 0.25)
found.push(+(tt - step).toFixed(2));
prev2 = prev; prev = cur;
}
}
function status(){
const s = document.getElementById('status');
if (found.length) s.textContent = fmt('Zeros found on the line so far: t ≈ %found% (all on Re(s)=½)', { found: found.join(', ') });
else s.textContent = fmt('Walking up to t = %t% …', { t: t.toFixed(1) });
}
function refresh(){ detect(); found = found.filter(z => z <= t + 0.3); draw(); status(); }
function reset(){ if (timer){clearInterval(timer); timer=null;} t = 0; found = []; axes();
document.getElementById('status').textContent = 'Press “Scan the line” to walk from t = 0 upward.'; }
document.getElementById('scan').addEventListener('click', () => {
if (timer) return; t = 0; found = [];
timer = setInterval(() => {
t += 1; if (t >= TMAX){ t = TMAX; clearInterval(timer); timer = null; }
refresh();
}, 180);
});
document.getElementById('step').addEventListener('click', () => {
if (timer) return; t = Math.min(TMAX, t + 1); refresh();
});
document.getElementById('reset').addEventListener('click', reset);
axes();
Press Scan the line and watch the dips appear at t≈14.13,21.02,25.01,30.42 — the first nontrivial zeros, exactly where Riemann's conjecture predicts. Every zero anyone has ever computed, over ten trillion of them, has landed precisely on this line. None has ever strayed off it. That mountain of evidence is not a proof: a single rogue zero anywhere else would topple the whole conjecture.
Comments
Loading comments...