<p class="hint">Every known record prime has the form $2^p - 1$ (a Mersenne number) for a prime exponent $p$. Pick an exponent and run the Lucas-Lehmer test — the exact same test GIMPS runs on numbers with tens of millions of digits, just small enough here to finish instantly.</p>
<div class="row">
<label for="expSelect">Exponent p:</label>
<select id="expSelect"></select>
<button id="run" type="button">Run Lucas-Lehmer test</button>
</div>
<div class="status" id="status">Pick an exponent and run the test.</div>
<pre class="trace" id="trace"></pre>
<div class="chart-wrap">
<p class="chart-title">Record prime sizes vs. the two unclaimed EFF prizes</p>
<div class="chart" id="chart"></div>
<p class="chart-caption">Bars use a logarithmic scale (each step up is 10x more digits). The striped red bars are the EFF’s remaining $150,000 (100,000,000-digit) and $250,000 (1,000,000,000-digit) thresholds — both still unclaimed.</p>
</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; }
.row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .6rem; }
label { font-size: .9rem; font-weight: 600; color: #1d3557; }
select { font: 600 14px ui-monospace, monospace; padding: .35rem .5rem; border-radius: 6px;
border: 1px solid #adb1b8; background: #fff; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.trace { background: #eef1f4; border: 1px solid #cdd9e3; border-radius: 8px; padding: .55rem .7rem;
font: 12px ui-monospace, monospace; max-height: 110px; overflow: auto; white-space: pre-wrap;
word-break: break-all; margin: 0 0 .8rem; color: #333; }
.chart-wrap { border-top: 1px dashed #cdd9e3; padding-top: .7rem; }
.chart-title { font-size: .85rem; font-weight: 700; color: #1d3557; margin: 0 0 .5rem; }
.chart { display: flex; align-items: flex-end; gap: 10px; height: 130px; padding: 0 .2rem; }
.bar-col { display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
height: 100%; flex: 1; }
.bar { width: 100%; max-width: 34px; background: #457b9d; border-radius: 4px 4px 0 0; position: relative; }
.bar.prize { background: repeating-linear-gradient(45deg, #e63946, #e63946 5px, #f2818b 5px, #f2818b 10px); }
.bar-label { font-size: .62rem; color: #444; margin-top: .3rem; text-align: center; line-height: 1.2; }
.chart-caption { font-size: .78rem; color: #555; margin: .5rem 0 0; line-height: 1.4; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Lucas-Lehmer test: for a Mersenne candidate M = 2^p - 1 with p prime,
// define s_0 = 4 and s_(i+1) = (s_i^2 - 2) mod M; M is prime iff s_(p-2) = 0.
function lucasLehmer(p) {
if (p === 2) return { prime: true, steps: [] }; // p=2 gives M=3, prime by inspection (loop would run 0 times anyway).
const M = (1n << BigInt(p)) - 1n;
let s = 4n;
const steps = [];
for (let i = 0; i < p - 2; i++) {
s = (s * s - 2n) % M;
if (i < 6 || i >= p - 3) steps.push(s.toString()); // only keep the first few and last few residues so the trace stays short.
}
return { prime: s === 0n, steps, M };
}
// Small prime exponents to try (mix of real Mersenne primes and composites).
const EXPONENTS = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31];
function digitsOf(p) {
// digit count of 2^p - 1 is floor(p * log10(2)) + 1.
return Math.floor(p * Math.log10(2)) + 1;
}
function populateSelect() {
const sel = document.getElementById('expSelect');
EXPONENTS.forEach((p) => {
const opt = document.createElement('option');
opt.value = String(p);
opt.textContent = i18nFmt(`p = %p% (M has %d% digit(s))`, { p, d: digitsOf(p) });
sel.appendChild(opt);
});
sel.value = '7';
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + cls;
}
function run() {
const p = Number(document.getElementById('expSelect').value);
const { prime, steps, M } = lucasLehmer(p);
const trace = document.getElementById('trace');
const mStr = M !== undefined ? M.toString() : '3';
const shownSteps = steps.length ? steps.join(', ') : '(' + `no iterations needed for p=2` + ')';
trace.textContent = i18nFmt(`Testing M = 2^%p% - 1 = %m%. Lucas-Lehmer sequence (mod M): %steps%`, { p, m: mStr, steps: shownSteps });
if (prime) setStatus(i18nFmt(`✓ 2^%p% - 1 IS prime (Lucas-Lehmer residue = 0).`, { p }), 'ok');
else setStatus(i18nFmt(`✗ 2^%p% - 1 is NOT prime (Lucas-Lehmer residue ≠ 0).`, { p }), 'bad');
}
// Real GIMPS/Mersenne record-prime digit counts plus the two unclaimed EFF thresholds.
const RECORDS = [
{ label: `1996
(420,921 digits)`, digits: 420921 },
{ label: `2008
(~13M digits, $100k won)`, digits: 12978189 },
{ label: `2018
(~25M digits)`, digits: 24862048 },
{ label: `2024
(~41M digits, current record)`, digits: 41024320 },
{ label: `$150,000 prize
(100,000,000 digits)`, digits: 100000000, prize: true },
{ label: `$250,000 prize
(1,000,000,000 digits)`, digits: 1000000000, prize: true }
];
function renderChart() {
const chart = document.getElementById('chart');
chart.innerHTML = '';
const max = Math.max(...RECORDS.map((r) => r.digits));
RECORDS.forEach((r) => {
const col = document.createElement('div'); col.className = 'bar-col';
const bar = document.createElement('div'); bar.className = 'bar' + (r.prize ? ' prize' : '');
// log scale so a 1000x range of digit counts still fits on one chart.
const pct = Math.max(3, (Math.log10(r.digits) / Math.log10(max)) * 100);
bar.style.height = pct + '%';
bar.title = i18nFmt(`%n% digits`, { n: r.digits.toLocaleString() });
const label = document.createElement('div'); label.className = 'bar-label'; label.textContent = r.label;
col.appendChild(bar); col.appendChild(label);
chart.appendChild(col);
});
}
populateSelect();
renderChart();
document.getElementById('run').addEventListener('click', run);
<p class="hint">Every known record prime has the form $2^p - 1$ (a Mersenne number) for a prime exponent $p$. Pick an exponent and run the Lucas-Lehmer test — the exact same test GIMPS runs on numbers with tens of millions of digits, just small enough here to finish instantly.</p>
<div class="row">
<label for="expSelect">Exponent p:</label>
<select id="expSelect"></select>
<button id="run" type="button">Run Lucas-Lehmer test</button>
</div>
<div class="status" id="status">Pick an exponent and run the test.</div>
<pre class="trace" id="trace"></pre>
<div class="chart-wrap">
<p class="chart-title">Record prime sizes vs. the two unclaimed EFF prizes</p>
<div class="chart" id="chart"></div>
<p class="chart-caption">Bars use a logarithmic scale (each step up is 10x more digits). The striped red bars are the EFF’s remaining $150,000 (100,000,000-digit) and $250,000 (1,000,000,000-digit) thresholds — both still unclaimed.</p>
</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; }
.row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .6rem; }
label { font-size: .9rem; font-weight: 600; color: #1d3557; }
select { font: 600 14px ui-monospace, monospace; padding: .35rem .5rem; border-radius: 6px;
border: 1px solid #adb1b8; background: #fff; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.trace { background: #eef1f4; border: 1px solid #cdd9e3; border-radius: 8px; padding: .55rem .7rem;
font: 12px ui-monospace, monospace; max-height: 110px; overflow: auto; white-space: pre-wrap;
word-break: break-all; margin: 0 0 .8rem; color: #333; }
.chart-wrap { border-top: 1px dashed #cdd9e3; padding-top: .7rem; }
.chart-title { font-size: .85rem; font-weight: 700; color: #1d3557; margin: 0 0 .5rem; }
.chart { display: flex; align-items: flex-end; gap: 10px; height: 130px; padding: 0 .2rem; }
.bar-col { display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
height: 100%; flex: 1; }
.bar { width: 100%; max-width: 34px; background: #457b9d; border-radius: 4px 4px 0 0; position: relative; }
.bar.prize { background: repeating-linear-gradient(45deg, #e63946, #e63946 5px, #f2818b 5px, #f2818b 10px); }
.bar-label { font-size: .62rem; color: #444; margin-top: .3rem; text-align: center; line-height: 1.2; }
.chart-caption { font-size: .78rem; color: #555; margin: .5rem 0 0; line-height: 1.4; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Lucas-Lehmer test: for a Mersenne candidate M = 2^p - 1 with p prime,
// define s_0 = 4 and s_(i+1) = (s_i^2 - 2) mod M; M is prime iff s_(p-2) = 0.
function lucasLehmer(p) {
if (p === 2) return { prime: true, steps: [] }; // p=2 gives M=3, prime by inspection (loop would run 0 times anyway).
const M = (1n << BigInt(p)) - 1n;
let s = 4n;
const steps = [];
for (let i = 0; i < p - 2; i++) {
s = (s * s - 2n) % M;
if (i < 6 || i >= p - 3) steps.push(s.toString()); // only keep the first few and last few residues so the trace stays short.
}
return { prime: s === 0n, steps, M };
}
// Small prime exponents to try (mix of real Mersenne primes and composites).
const EXPONENTS = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31];
function digitsOf(p) {
// digit count of 2^p - 1 is floor(p * log10(2)) + 1.
return Math.floor(p * Math.log10(2)) + 1;
}
function populateSelect() {
const sel = document.getElementById('expSelect');
EXPONENTS.forEach((p) => {
const opt = document.createElement('option');
opt.value = String(p);
opt.textContent = i18nFmt(`p = %p% (M has %d% digit(s))`, { p, d: digitsOf(p) });
sel.appendChild(opt);
});
sel.value = '7';
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + cls;
}
function run() {
const p = Number(document.getElementById('expSelect').value);
const { prime, steps, M } = lucasLehmer(p);
const trace = document.getElementById('trace');
const mStr = M !== undefined ? M.toString() : '3';
const shownSteps = steps.length ? steps.join(', ') : '(' + `no iterations needed for p=2` + ')';
trace.textContent = i18nFmt(`Testing M = 2^%p% - 1 = %m%. Lucas-Lehmer sequence (mod M): %steps%`, { p, m: mStr, steps: shownSteps });
if (prime) setStatus(i18nFmt(`✓ 2^%p% - 1 IS prime (Lucas-Lehmer residue = 0).`, { p }), 'ok');
else setStatus(i18nFmt(`✗ 2^%p% - 1 is NOT prime (Lucas-Lehmer residue ≠ 0).`, { p }), 'bad');
}
// Real GIMPS/Mersenne record-prime digit counts plus the two unclaimed EFF thresholds.
const RECORDS = [
{ label: `1996
(420,921 digits)`, digits: 420921 },
{ label: `2008
(~13M digits, $100k won)`, digits: 12978189 },
{ label: `2018
(~25M digits)`, digits: 24862048 },
{ label: `2024
(~41M digits, current record)`, digits: 41024320 },
{ label: `$150,000 prize
(100,000,000 digits)`, digits: 100000000, prize: true },
{ label: `$250,000 prize
(1,000,000,000 digits)`, digits: 1000000000, prize: true }
];
function renderChart() {
const chart = document.getElementById('chart');
chart.innerHTML = '';
const max = Math.max(...RECORDS.map((r) => r.digits));
RECORDS.forEach((r) => {
const col = document.createElement('div'); col.className = 'bar-col';
const bar = document.createElement('div'); bar.className = 'bar' + (r.prize ? ' prize' : '');
// log scale so a 1000x range of digit counts still fits on one chart.
const pct = Math.max(3, (Math.log10(r.digits) / Math.log10(max)) * 100);
bar.style.height = pct + '%';
bar.title = i18nFmt(`%n% digits`, { n: r.digits.toLocaleString() });
const label = document.createElement('div'); label.className = 'bar-label'; label.textContent = r.label;
col.appendChild(bar); col.appendChild(label);
chart.appendChild(col);
});
}
populateSelect();
renderChart();
document.getElementById('run').addEventListener('click', run);