The number 561 passes the Fermat test for every witness — yet it factors as 3×11×17. Enter any number below and pick a witness a to run both tests side-by-side.
<p class="hint">Enter a number and a witness <b>a</b> to run both the Fermat test and Miller-Rabin side-by-side. Try <b>n = 561</b> (a Carmichael number) with <b>a = 2</b> — Fermat says "prime", Miller-Rabin says "composite".</p>
<div class="controls">
<label>n = <input id="n-input" type="number" value="561" min="3" step="2"></label>
<label>a = <input id="a-input" type="number" value="2" min="2"></label>
<button id="run-btn" type="button">Run both tests</button>
<button id="try-561-btn" type="button" class="ghost">Try n = 561</button>
<button id="try-577-btn" type="button" class="ghost">Try n = 577 (prime)</button>
</div>
<div id="results" class="results hidden">
<div class="test-block">
<div class="test-label fermat-label">Fermat Test</div>
<div id="fermat-chain" class="chain"></div>
<div id="fermat-verdict" class="verdict"></div>
</div>
<div class="test-block">
<div class="test-label mr-label">Miller-Rabin</div>
<div id="mr-decompose" class="chain"></div>
<div id="mr-chain" class="chain"></div>
<div id="mr-verdict" class="verdict"></div>
</div>
</div>
<div id="explanation" class="explanation"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 15px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: 1rem; }
.controls label { display: flex; align-items: center; gap: .3rem; font-weight: 600; }
.controls input { width: 90px; font: 600 15px ui-monospace, monospace; padding: .3rem .4rem;
border: 1px solid #adb1b8; border-radius: 6px; text-align: right; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.results { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; margin-bottom: .8rem; }
.results.hidden { display: none; }
.test-block { background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 10px; padding: .7rem; }
.test-label { font-weight: 700; font-size: .8rem; text-transform: uppercase; letter-spacing: .06em;
margin-bottom: .5rem; }
.fermat-label { color: #c92f3c; }
.mr-label { color: #0a7d33; }
.chain { font: 14px ui-monospace, monospace; color: #334; margin-bottom: .4rem; line-height: 1.6;
word-break: break-all; }
.verdict { font-weight: 700; font-size: .95rem; padding: .3rem .5rem; border-radius: 6px; margin-top: .3rem; }
.verdict.pass { background: #d4f0dc; color: #0a7d33; }
.verdict.fail { background: #fce4e6; color: #c92f3c; }
.verdict.prime { background: #e8eef3; color: #1d3557; }
.explanation { background: #fffbe6; border-left: 3px solid #e6b800; border-radius: 0 8px 8px 0;
padding: .6rem .8rem; font-size: .88rem; line-height: 1.5; }
@media (max-width: 520px) { .results { grid-template-columns: 1fr; } }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Miller-Rabin demo: compare Fermat test vs Miller-Rabin on the same (n, a).
// Uses BigInt for exact modular arithmetic even for large n.
function modpow(base, exp, mod) {
base = BigInt(base); exp = BigInt(exp); mod = BigInt(mod);
if (mod === 1n) return 0n;
let result = 1n;
base = base % mod;
while (exp > 0n) {
if (exp % 2n === 1n) result = result * base % mod;
exp = exp / 2n;
base = base * base % mod;
}
return result;
}
// Write n-1 as 2^s * d where d is odd. Returns {s, d}.
function factorOut2(n) {
n = BigInt(n);
let s = 0n, d = n - 1n;
while (d % 2n === 0n) { d /= 2n; s++; }
return { s, d };
}
// Fermat test: returns true if a^(n-1) ≡ 1 (mod n).
function fermat(n, a) {
return modpow(a, BigInt(n) - 1n, n) === 1n;
}
// Miller-Rabin witness check.
// Returns {isProbablePrime, steps} where steps is the chain of squarings.
function millerRabinWitness(n, a) {
const bn = BigInt(n);
const ba = BigInt(a);
const { s, d } = factorOut2(bn);
const steps = [];
let x = modpow(ba, d, bn);
steps.push({ power: `a^d`, value: x });
if (x === 1n || x === bn - 1n) {
return { isProbablePrime: true, steps, reason: x === 1n ? 'a^d ≡ 1' : 'a^d ≡ −1' };
}
for (let r = 1n; r < s; r++) {
x = x * x % bn;
steps.push({ power: `a^(2^${r}·d)`, value: x });
if (x === bn - 1n) {
return { isProbablePrime: true, steps, reason: `a^(2^${r}·d) ≡ −1` };
}
if (x === 1n) {
return { isProbablePrime: false, steps, reason: `Non-trivial square root of 1 found` };
}
}
// Never reached -1 before 1 (or at end)
return { isProbablePrime: false, steps, reason: `a^(n−1) did not reach 1 through −1` };
}
// Simple trial division to know truth (only for small n in demo)
function isTrulyPrime(n) {
n = Number(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 run() {
const nVal = parseInt(document.getElementById('n-input').value, 10);
const aVal = parseInt(document.getElementById('a-input').value, 10);
const results = document.getElementById('results');
const explanation = document.getElementById('explanation');
if (isNaN(nVal) || nVal < 3 || isNaN(aVal) || aVal < 2 || aVal >= nVal) {
explanation.textContent = 'Please enter n ≥ 3 and 2 ≤ a < n.';
results.classList.add('hidden');
return;
}
// --- Fermat test ---
const fermatOk = fermat(nVal, aVal);
const fermatExp = modpow(aVal, BigInt(nVal) - 1n, BigInt(nVal));
document.getElementById('fermat-chain').textContent =
`${aVal}^${nVal - 1} mod ${nVal} = ${fermatExp}`;
const fermatVerdict = document.getElementById('fermat-verdict');
if (fermatOk) {
fermatVerdict.textContent = '✓ Passes — "probably prime"';
fermatVerdict.className = 'verdict pass';
} else {
fermatVerdict.textContent = '✗ Fails — definitely composite';
fermatVerdict.className = 'verdict fail';
}
// --- Miller-Rabin ---
const { s, d } = factorOut2(BigInt(nVal));
document.getElementById('mr-decompose').textContent =
`n-1 = ${nVal - 1} = 2^${s} x ${d}`;
const { isProbablePrime, steps, reason } = millerRabinWitness(nVal, aVal);
const chainEl = document.getElementById('mr-chain');
chainEl.innerHTML = steps.map(st =>
`<div>${st.power} mod n = ${st.value}</div>`
).join('');
const mrVerdict = document.getElementById('mr-verdict');
if (isProbablePrime) {
mrVerdict.textContent = fmt('✓ Passes (%reason%)', { reason: reason });
mrVerdict.className = 'verdict pass';
} else {
mrVerdict.textContent = fmt('✗ Fails — composite! (%reason%)', { reason: reason });
mrVerdict.className = 'verdict fail';
}
results.classList.remove('hidden');
// --- Explanation ---
const truly = isTrulyPrime(nVal);
let msg = '';
if (truly) {
msg = fmt('%nVal% is a genuine prime. Both tests pass for every witness — no contradiction possible.', { nVal: nVal });
} else if (fermatOk && !isProbablePrime) {
msg = fmt('%nVal% is composite but fools the Fermat test with a = %aVal%. Miller-Rabin catches it: the chain of squarings reveals a non-trivial square root of 1 (impossible in a prime field).', { nVal: nVal, aVal: aVal });
} else if (fermatOk && isProbablePrime) {
msg = fmt('%nVal% is composite, and both tests pass with a = %aVal%. Try another witness — for Carmichael numbers like 561, Miller-Rabin still eventually catches them with the right a.', { nVal: nVal, aVal: aVal });
} else {
msg = fmt('%nVal% is composite. The Fermat test also fails here with a = %aVal%, so both tests agree.', { nVal: nVal, aVal: aVal });
}
explanation.textContent = msg;
}
document.getElementById('run-btn').addEventListener('click', run);
document.getElementById('try-561-btn').addEventListener('click', () => {
document.getElementById('n-input').value = '561';
document.getElementById('a-input').value = '2';
run();
});
document.getElementById('try-577-btn').addEventListener('click', () => {
document.getElementById('n-input').value = '577';
document.getElementById('a-input').value = '2';
run();
});
// Run on load with defaults
run();
<p class="hint">Enter a number and a witness <b>a</b> to run both the Fermat test and Miller-Rabin side-by-side. Try <b>n = 561</b> (a Carmichael number) with <b>a = 2</b> — Fermat says "prime", Miller-Rabin says "composite".</p>
<div class="controls">
<label>n = <input id="n-input" type="number" value="561" min="3" step="2"></label>
<label>a = <input id="a-input" type="number" value="2" min="2"></label>
<button id="run-btn" type="button">Run both tests</button>
<button id="try-561-btn" type="button" class="ghost">Try n = 561</button>
<button id="try-577-btn" type="button" class="ghost">Try n = 577 (prime)</button>
</div>
<div id="results" class="results hidden">
<div class="test-block">
<div class="test-label fermat-label">Fermat Test</div>
<div id="fermat-chain" class="chain"></div>
<div id="fermat-verdict" class="verdict"></div>
</div>
<div class="test-block">
<div class="test-label mr-label">Miller-Rabin</div>
<div id="mr-decompose" class="chain"></div>
<div id="mr-chain" class="chain"></div>
<div id="mr-verdict" class="verdict"></div>
</div>
</div>
<div id="explanation" class="explanation"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 15px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: 1rem; }
.controls label { display: flex; align-items: center; gap: .3rem; font-weight: 600; }
.controls input { width: 90px; font: 600 15px ui-monospace, monospace; padding: .3rem .4rem;
border: 1px solid #adb1b8; border-radius: 6px; text-align: right; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.results { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; margin-bottom: .8rem; }
.results.hidden { display: none; }
.test-block { background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 10px; padding: .7rem; }
.test-label { font-weight: 700; font-size: .8rem; text-transform: uppercase; letter-spacing: .06em;
margin-bottom: .5rem; }
.fermat-label { color: #c92f3c; }
.mr-label { color: #0a7d33; }
.chain { font: 14px ui-monospace, monospace; color: #334; margin-bottom: .4rem; line-height: 1.6;
word-break: break-all; }
.verdict { font-weight: 700; font-size: .95rem; padding: .3rem .5rem; border-radius: 6px; margin-top: .3rem; }
.verdict.pass { background: #d4f0dc; color: #0a7d33; }
.verdict.fail { background: #fce4e6; color: #c92f3c; }
.verdict.prime { background: #e8eef3; color: #1d3557; }
.explanation { background: #fffbe6; border-left: 3px solid #e6b800; border-radius: 0 8px 8px 0;
padding: .6rem .8rem; font-size: .88rem; line-height: 1.5; }
@media (max-width: 520px) { .results { grid-template-columns: 1fr; } }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Miller-Rabin demo: compare Fermat test vs Miller-Rabin on the same (n, a).
// Uses BigInt for exact modular arithmetic even for large n.
function modpow(base, exp, mod) {
base = BigInt(base); exp = BigInt(exp); mod = BigInt(mod);
if (mod === 1n) return 0n;
let result = 1n;
base = base % mod;
while (exp > 0n) {
if (exp % 2n === 1n) result = result * base % mod;
exp = exp / 2n;
base = base * base % mod;
}
return result;
}
// Write n-1 as 2^s * d where d is odd. Returns {s, d}.
function factorOut2(n) {
n = BigInt(n);
let s = 0n, d = n - 1n;
while (d % 2n === 0n) { d /= 2n; s++; }
return { s, d };
}
// Fermat test: returns true if a^(n-1) ≡ 1 (mod n).
function fermat(n, a) {
return modpow(a, BigInt(n) - 1n, n) === 1n;
}
// Miller-Rabin witness check.
// Returns {isProbablePrime, steps} where steps is the chain of squarings.
function millerRabinWitness(n, a) {
const bn = BigInt(n);
const ba = BigInt(a);
const { s, d } = factorOut2(bn);
const steps = [];
let x = modpow(ba, d, bn);
steps.push({ power: `a^d`, value: x });
if (x === 1n || x === bn - 1n) {
return { isProbablePrime: true, steps, reason: x === 1n ? 'a^d ≡ 1' : 'a^d ≡ −1' };
}
for (let r = 1n; r < s; r++) {
x = x * x % bn;
steps.push({ power: `a^(2^${r}·d)`, value: x });
if (x === bn - 1n) {
return { isProbablePrime: true, steps, reason: `a^(2^${r}·d) ≡ −1` };
}
if (x === 1n) {
return { isProbablePrime: false, steps, reason: `Non-trivial square root of 1 found` };
}
}
// Never reached -1 before 1 (or at end)
return { isProbablePrime: false, steps, reason: `a^(n−1) did not reach 1 through −1` };
}
// Simple trial division to know truth (only for small n in demo)
function isTrulyPrime(n) {
n = Number(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 run() {
const nVal = parseInt(document.getElementById('n-input').value, 10);
const aVal = parseInt(document.getElementById('a-input').value, 10);
const results = document.getElementById('results');
const explanation = document.getElementById('explanation');
if (isNaN(nVal) || nVal < 3 || isNaN(aVal) || aVal < 2 || aVal >= nVal) {
explanation.textContent = 'Please enter n ≥ 3 and 2 ≤ a < n.';
results.classList.add('hidden');
return;
}
// --- Fermat test ---
const fermatOk = fermat(nVal, aVal);
const fermatExp = modpow(aVal, BigInt(nVal) - 1n, BigInt(nVal));
document.getElementById('fermat-chain').textContent =
`${aVal}^${nVal - 1} mod ${nVal} = ${fermatExp}`;
const fermatVerdict = document.getElementById('fermat-verdict');
if (fermatOk) {
fermatVerdict.textContent = '✓ Passes — "probably prime"';
fermatVerdict.className = 'verdict pass';
} else {
fermatVerdict.textContent = '✗ Fails — definitely composite';
fermatVerdict.className = 'verdict fail';
}
// --- Miller-Rabin ---
const { s, d } = factorOut2(BigInt(nVal));
document.getElementById('mr-decompose').textContent =
`n-1 = ${nVal - 1} = 2^${s} x ${d}`;
const { isProbablePrime, steps, reason } = millerRabinWitness(nVal, aVal);
const chainEl = document.getElementById('mr-chain');
chainEl.innerHTML = steps.map(st =>
`<div>${st.power} mod n = ${st.value}</div>`
).join('');
const mrVerdict = document.getElementById('mr-verdict');
if (isProbablePrime) {
mrVerdict.textContent = fmt('✓ Passes (%reason%)', { reason: reason });
mrVerdict.className = 'verdict pass';
} else {
mrVerdict.textContent = fmt('✗ Fails — composite! (%reason%)', { reason: reason });
mrVerdict.className = 'verdict fail';
}
results.classList.remove('hidden');
// --- Explanation ---
const truly = isTrulyPrime(nVal);
let msg = '';
if (truly) {
msg = fmt('%nVal% is a genuine prime. Both tests pass for every witness — no contradiction possible.', { nVal: nVal });
} else if (fermatOk && !isProbablePrime) {
msg = fmt('%nVal% is composite but fools the Fermat test with a = %aVal%. Miller-Rabin catches it: the chain of squarings reveals a non-trivial square root of 1 (impossible in a prime field).', { nVal: nVal, aVal: aVal });
} else if (fermatOk && isProbablePrime) {
msg = fmt('%nVal% is composite, and both tests pass with a = %aVal%. Try another witness — for Carmichael numbers like 561, Miller-Rabin still eventually catches them with the right a.', { nVal: nVal, aVal: aVal });
} else {
msg = fmt('%nVal% is composite. The Fermat test also fails here with a = %aVal%, so both tests agree.', { nVal: nVal, aVal: aVal });
}
explanation.textContent = msg;
}
document.getElementById('run-btn').addEventListener('click', run);
document.getElementById('try-561-btn').addEventListener('click', () => {
document.getElementById('n-input').value = '561';
document.getElementById('a-input').value = '2';
run();
});
document.getElementById('try-577-btn').addEventListener('click', () => {
document.getElementById('n-input').value = '577';
document.getElementById('a-input').value = '2';
run();
});
// Run on load with defaults
run();
Notice the asymmetry. The Fermat test sees a560≡1(mod561) and declares 561 prime — wrong. Miller-Rabin writes 560=24×35, then checks the chain of square roots: it finds a value that is neither 1 nor −1 but whose square is 1, which is impossible for a true prime. Busted.
For a genuine prime like 577, every witness a satisfies both tests. For 561, Miller-Rabin catches it with witness a=2 on the very first try.
Comments
Loading comments...