Enter a prime exponent p below and run the Lucas–Lehmer sequence. The tester computes s0=4, sk+1=sk2−2(mod2p−1) for p−2 steps and checks whether the final value is 0.
<p class="hint">Enter a prime exponent <b>p</b> and run the Lucas–Lehmer test on 2<sup>p</sup>−1. The sequence starts at s<sub>0</sub>=4 and iterates s<sub>k+1</sub> = s<sub>k</sub><sup>2</sup>−2 (mod 2<sup>p</sup>−1) for p−2 steps. If the final value is 0 the number is prime — guaranteed.</p>
<div class="controls">
<label for="pInput">Exponent <i>p</i>:</label>
<input id="pInput" type="number" min="2" max="127" value="7" step="1" />
<button id="runBtn" type="button">Run test</button>
</div>
<div id="verdict" class="verdict"></div>
<div id="info" class="info"></div>
<div id="steps" class="steps"></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 .8rem; line-height: 1.5; }
.controls { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .8rem; }
label { font-size: .95rem; font-weight: 600; }
input[type=number] { width: 80px; font: 600 16px ui-monospace, monospace; padding: .35rem .5rem;
border: 1px solid #aaa; border-radius: 7px; text-align: center; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem 1rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button:hover { background: #16294a; }
.verdict { font: 700 1.05rem/1.4 system-ui, sans-serif; min-height: 1.6em; margin-bottom: .4rem; }
.verdict.prime { color: #0a7d33; }
.verdict.composite { color: #c92f3c; }
.verdict.error { color: #b45309; }
.info { font-size: .85rem; color: #555; margin-bottom: .6rem; }
.steps { max-height: 200px; overflow-y: auto; border: 1px solid #ddd; border-radius: 8px;
padding: .5rem .7rem; font: 13px/1.6 ui-monospace, monospace; background: #f8fafc; }
.steps:empty { display: none; }
.step-row { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.step-row.final { color: #0a7d33; font-weight: 700; }
.step-row.fail { color: #c92f3c; font-weight: 700; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Lucas–Lehmer primality test for Mersenne numbers M_p = 2^p − 1.
// Algorithm: s_0 = 4; s_{k+1} = s_k^2 − 2 (mod M_p); p−2 iterations.
// M_p is prime iff s_{p-2} ≡ 0 (mod M_p).
//
// We use BigInt arithmetic so the test is exact for all p up to 127 in this demo.
function isPrimeSmall(n) {
if (n < 2) return false;
if (n === 2) return true;
if (n % 2 === 0) return false;
for (let i = 3; i * i <= n; i += 2) if (n % i === 0) return false;
return true;
}
function lucasLehmer(p) {
// p=2: M_2=3 is prime but the standard recurrence runs 0 iterations and
// returns 4 mod 3 ≠ 0. Handle it as a known special case.
if (p === 2) return { prime: true, mp: 3n, steps: [{ k: 0, val: 4n }] };
const pBig = BigInt(p);
const mp = (1n << pBig) - 1n; // 2^p - 1
let s = 4n;
const steps = [{ k: 0, val: s }];
const limit = p > 20 ? 6 : p; // show only first & last few steps for large p
for (let k = 1; k <= p - 2; k++) {
s = ((s * s) - 2n + mp) % mp;
if (k < limit || k > p - 4) steps.push({ k, val: s });
else if (k === limit) steps.push({ k: -1, val: null }); // ellipsis marker
}
return { prime: s === 0n, mp, steps };
}
function truncate(bigStr, maxLen) {
if (bigStr.length <= maxLen) return bigStr;
const half = Math.floor(maxLen / 2) - 2;
return bigStr.slice(0, half) + '…' + bigStr.slice(-half);
}
function run() {
const raw = parseInt(document.getElementById('pInput').value, 10);
const verdictEl = document.getElementById('verdict');
const infoEl = document.getElementById('info');
const stepsEl = document.getElementById('steps');
verdictEl.className = 'verdict'; verdictEl.textContent = '';
infoEl.textContent = ''; stepsEl.innerHTML = '';
if (!Number.isInteger(raw) || raw < 2 || raw > 127) {
verdictEl.className = 'verdict error';
verdictEl.textContent = 'Please enter a prime p between 2 and 127.';
return;
}
if (!isPrimeSmall(raw)) {
verdictEl.className = 'verdict error';
verdictEl.textContent = fmt('p = %raw% is not prime — 2^p−1 is automatically composite when p is composite.', { raw: raw });
return;
}
const { prime, mp, steps } = lucasLehmer(raw);
const mpStr = mp.toString();
const digits = mpStr.length;
if (prime) {
verdictEl.className = 'verdict prime';
verdictEl.textContent = fmt('✓ 2^%raw%−1 is PRIME (a Mersenne prime)!', { raw: raw });
} else {
verdictEl.className = 'verdict composite';
verdictEl.textContent = fmt('✗ 2^%raw%−1 is COMPOSITE.', { raw: raw });
}
infoEl.textContent = fmt('2^%raw%−1 has %digits% %v%. The test ran %v2% %v3%.', { raw: raw, digits: digits, v: digits > 1 ? 'digits' : 'digit', v2: raw - 2, v3: raw - 2 !== 1 ? 'iterations' : 'iteration' });
for (const { k, val } of steps) {
const div = document.createElement('div');
div.className = 'step-row';
if (k === -1) {
div.textContent = ' ' + '… (intermediate steps omitted) …';
div.style.color = '#888';
} else {
const valStr = val !== null ? truncate(val.toString(), 60) : '?';
const isFinal = k === raw - 2;
if (isFinal) div.className += (val === 0n ? ' final' : ' fail');
div.textContent = `s[${k}] = ${valStr}`;
}
stepsEl.appendChild(div);
}
}
document.getElementById('runBtn').addEventListener('click', run);
document.getElementById('pInput').addEventListener('keydown', e => { if (e.key === 'Enter') run(); });
run();
<p class="hint">Enter a prime exponent <b>p</b> and run the Lucas–Lehmer test on 2<sup>p</sup>−1. The sequence starts at s<sub>0</sub>=4 and iterates s<sub>k+1</sub> = s<sub>k</sub><sup>2</sup>−2 (mod 2<sup>p</sup>−1) for p−2 steps. If the final value is 0 the number is prime — guaranteed.</p>
<div class="controls">
<label for="pInput">Exponent <i>p</i>:</label>
<input id="pInput" type="number" min="2" max="127" value="7" step="1" />
<button id="runBtn" type="button">Run test</button>
</div>
<div id="verdict" class="verdict"></div>
<div id="info" class="info"></div>
<div id="steps" class="steps"></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 .8rem; line-height: 1.5; }
.controls { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .8rem; }
label { font-size: .95rem; font-weight: 600; }
input[type=number] { width: 80px; font: 600 16px ui-monospace, monospace; padding: .35rem .5rem;
border: 1px solid #aaa; border-radius: 7px; text-align: center; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem 1rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button:hover { background: #16294a; }
.verdict { font: 700 1.05rem/1.4 system-ui, sans-serif; min-height: 1.6em; margin-bottom: .4rem; }
.verdict.prime { color: #0a7d33; }
.verdict.composite { color: #c92f3c; }
.verdict.error { color: #b45309; }
.info { font-size: .85rem; color: #555; margin-bottom: .6rem; }
.steps { max-height: 200px; overflow-y: auto; border: 1px solid #ddd; border-radius: 8px;
padding: .5rem .7rem; font: 13px/1.6 ui-monospace, monospace; background: #f8fafc; }
.steps:empty { display: none; }
.step-row { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.step-row.final { color: #0a7d33; font-weight: 700; }
.step-row.fail { color: #c92f3c; font-weight: 700; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Lucas–Lehmer primality test for Mersenne numbers M_p = 2^p − 1.
// Algorithm: s_0 = 4; s_{k+1} = s_k^2 − 2 (mod M_p); p−2 iterations.
// M_p is prime iff s_{p-2} ≡ 0 (mod M_p).
//
// We use BigInt arithmetic so the test is exact for all p up to 127 in this demo.
function isPrimeSmall(n) {
if (n < 2) return false;
if (n === 2) return true;
if (n % 2 === 0) return false;
for (let i = 3; i * i <= n; i += 2) if (n % i === 0) return false;
return true;
}
function lucasLehmer(p) {
// p=2: M_2=3 is prime but the standard recurrence runs 0 iterations and
// returns 4 mod 3 ≠ 0. Handle it as a known special case.
if (p === 2) return { prime: true, mp: 3n, steps: [{ k: 0, val: 4n }] };
const pBig = BigInt(p);
const mp = (1n << pBig) - 1n; // 2^p - 1
let s = 4n;
const steps = [{ k: 0, val: s }];
const limit = p > 20 ? 6 : p; // show only first & last few steps for large p
for (let k = 1; k <= p - 2; k++) {
s = ((s * s) - 2n + mp) % mp;
if (k < limit || k > p - 4) steps.push({ k, val: s });
else if (k === limit) steps.push({ k: -1, val: null }); // ellipsis marker
}
return { prime: s === 0n, mp, steps };
}
function truncate(bigStr, maxLen) {
if (bigStr.length <= maxLen) return bigStr;
const half = Math.floor(maxLen / 2) - 2;
return bigStr.slice(0, half) + '…' + bigStr.slice(-half);
}
function run() {
const raw = parseInt(document.getElementById('pInput').value, 10);
const verdictEl = document.getElementById('verdict');
const infoEl = document.getElementById('info');
const stepsEl = document.getElementById('steps');
verdictEl.className = 'verdict'; verdictEl.textContent = '';
infoEl.textContent = ''; stepsEl.innerHTML = '';
if (!Number.isInteger(raw) || raw < 2 || raw > 127) {
verdictEl.className = 'verdict error';
verdictEl.textContent = 'Please enter a prime p between 2 and 127.';
return;
}
if (!isPrimeSmall(raw)) {
verdictEl.className = 'verdict error';
verdictEl.textContent = fmt('p = %raw% is not prime — 2^p−1 is automatically composite when p is composite.', { raw: raw });
return;
}
const { prime, mp, steps } = lucasLehmer(raw);
const mpStr = mp.toString();
const digits = mpStr.length;
if (prime) {
verdictEl.className = 'verdict prime';
verdictEl.textContent = fmt('✓ 2^%raw%−1 is PRIME (a Mersenne prime)!', { raw: raw });
} else {
verdictEl.className = 'verdict composite';
verdictEl.textContent = fmt('✗ 2^%raw%−1 is COMPOSITE.', { raw: raw });
}
infoEl.textContent = fmt('2^%raw%−1 has %digits% %v%. The test ran %v2% %v3%.', { raw: raw, digits: digits, v: digits > 1 ? 'digits' : 'digit', v2: raw - 2, v3: raw - 2 !== 1 ? 'iterations' : 'iteration' });
for (const { k, val } of steps) {
const div = document.createElement('div');
div.className = 'step-row';
if (k === -1) {
div.textContent = ' ' + '… (intermediate steps omitted) …';
div.style.color = '#888';
} else {
const valStr = val !== null ? truncate(val.toString(), 60) : '?';
const isFinal = k === raw - 2;
if (isFinal) div.className += (val === 0n ? ' final' : ' fail');
div.textContent = `s[${k}] = ${valStr}`;
}
stepsEl.appendChild(div);
}
}
document.getElementById('runBtn').addEventListener('click', run);
document.getElementById('pInput').addEventListener('keydown', e => { if (e.key === 'Enter') run(); });
run();
Notice how the intermediate values can be enormous yet the test always reaches a clean verdict. Small values of p finish instantly; try p=31 or p=61 (both prime exponents that yield Mersenne primes) and compare with p=11 or p=23 (composite Mersenne numbers). The test is deterministic — there are no false positives and no false negatives.
Comments
Loading comments...