Below is a real randomized algorithm: a primality test in the spirit of Miller and Rabin. Pick a number, then press Flip a coin to draw one random witness. Each witness either exposes the number as composite for certain, or comes back saying "looks prime" — and a composite can fool any single witness with probability under one half.
<p class="hint">A randomized <b>primality test</b>. Each "Flip a coin" draws one random
witness. A prime always passes; a composite is exposed with probability over ½
per witness — so the chance of being fooled is below <b>1 / 2ᵏ</b> after k flips.</p>
<div class="pick">
<label>Test the number:</label>
<select id="num">
<option value="561">561 (composite — a sneaky Carmichael number)</option>
<option value="1105">1105 (composite)</option>
<option value="104729">104729 (prime)</option>
<option value="97">97 (prime)</option>
</select>
</div>
<div class="verdict" id="verdict">No witnesses yet.</div>
<div class="bar"><div class="fill" id="fill"></div></div>
<div class="err" id="err">Error bound: 100%</div>
<div class="log" id="log"></div>
<div class="btns">
<button id="flip" type="button">Flip a coin</button>
<button id="ten" type="button">Flip 10 coins</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; }
.pick { margin: .4rem 0 .7rem; font-size: .92rem; }
.pick label { font-weight: 600; margin-right: .4rem; }
select { font: 500 14px system-ui, sans-serif; padding: .3rem .4rem; border: 1px solid #adb1b8; border-radius: 6px; }
.verdict { font-size: 1.05rem; font-weight: 700; margin: .5rem 0; min-height: 1.4em; color: #1d3557; }
.verdict.composite { color: #c92f3c; }
.verdict.prime { color: #0a7d33; }
.bar { height: 14px; background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 7px; overflow: hidden; }
.fill { height: 100%; width: 100%; background: #e63946; transition: width .25s; }
.err { font: 600 .95rem ui-monospace, monospace; color: #1d3557; margin: .35rem 0 .6rem; }
.log { font: 500 .82rem ui-monospace, monospace; color: #444; min-height: 3.2em; line-height: 1.4;
background: #f5f7f9; border: 1px solid #e1e7ec; border-radius: 8px; padding: .5rem .6rem; margin-bottom: .6rem; white-space: pre-wrap; }
.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] ?? ''));
// A randomized primality test (Miller-Rabin). For an odd n, write n-1 = d * 2^s.
// A random base a in [2, n-2] is a "witness" if it proves n composite. If the
// base fails to expose n, the test says "probably prime". A prime passes for
// EVERY base; a composite is exposed by at least 3/4 of all bases, so each
// independent coin flip that returns "probably prime" multiplies the chance of
// a wrong answer by at most 1/2. Checking is fast; the error shrinks like 1/2^k.
// This is the spirit of BPP: bounded error, amplified toward zero by repetition.
function modpow(base, exp, mod){
let r = 1n; base %= mod;
while (exp > 0n){ if (exp & 1n) r = (r * base) % mod; exp >>= 1n; base = (base * base) % mod; }
return r;
}
// Returns true if base `a` is a STRONG WITNESS that n is composite (exposes it).
function isWitness(a, n){
if (n % 2n === 0n) return n !== 2n;
let d = n - 1n, s = 0n;
while (d % 2n === 0n){ d /= 2n; s += 1n; }
let x = modpow(a, d, n);
if (x === 1n || x === n - 1n) return false; // looks prime for this base
for (let i = 0n; i < s - 1n; i++){
x = (x * x) % n;
if (x === n - 1n) return false; // looks prime for this base
}
return true; // a proves n is composite
}
let n = 561n, flips = 0, exposed = false;
function $(id){ return document.getElementById(id); }
function pct(p){ return p >= 1 ? '100%' : (p*100 < 0.01 ? '< 0.01%' : (p*100).toFixed(p*100<1?3:1) + '%'); }
function render(){
const bound = exposed ? 0 : Math.pow(0.5, flips); // upper bound on error
$('fill').style.width = (exposed ? 0 : bound*100) + '%';
if (exposed){
$('verdict').textContent = '✗ Definitely COMPOSITE (a witness proved it).';
$('verdict').className = 'verdict composite';
$('err').textContent = 'Error bound: 0% (a witness is a certificate)';
} else if (flips === 0){
$('verdict').textContent = 'No witnesses yet.';
$('verdict').className = 'verdict';
$('err').textContent = 'Error bound: 100%';
} else {
$('verdict').textContent = fmt('✓ Probably PRIME after %flips% coin flip%v%.', { flips: flips, v: flips>1?'s':'' });
$('verdict').className = 'verdict prime';
$('err').textContent = fmt('Error bound: ≤ 1/2^%flips% = %pct%', { flips: flips, pct: pct(bound) });
}
}
function flip(){
if (exposed) return;
// random base in [2, n-2]
const a = 2n + BigInt(Math.floor(Math.random() * Number(n - 3n)));
flips++;
if (isWitness(a, n)){
exposed = true;
$('log').textContent += fmt('flip %flips%: base %a% → WITNESS, n is composite.', { flips: flips, a: a }) + "\n";
} else {
$('log').textContent += fmt('flip %flips%: base %a% → looks prime.', { flips: flips, a: a }) + "\n";
}
$('log').scrollTop = $('log').scrollHeight;
render();
}
function reset(){
n = BigInt($('num').value); flips = 0; exposed = false;
$('log').textContent = ''; render();
}
$('flip').addEventListener('click', flip);
$('ten').addEventListener('click', function(){ for (let i=0;i<10 && !exposed;i++) flip(); });
$('reset').addEventListener('click', reset);
$('num').addEventListener('change', reset);
reset();
<p class="hint">A randomized <b>primality test</b>. Each "Flip a coin" draws one random
witness. A prime always passes; a composite is exposed with probability over ½
per witness — so the chance of being fooled is below <b>1 / 2ᵏ</b> after k flips.</p>
<div class="pick">
<label>Test the number:</label>
<select id="num">
<option value="561">561 (composite — a sneaky Carmichael number)</option>
<option value="1105">1105 (composite)</option>
<option value="104729">104729 (prime)</option>
<option value="97">97 (prime)</option>
</select>
</div>
<div class="verdict" id="verdict">No witnesses yet.</div>
<div class="bar"><div class="fill" id="fill"></div></div>
<div class="err" id="err">Error bound: 100%</div>
<div class="log" id="log"></div>
<div class="btns">
<button id="flip" type="button">Flip a coin</button>
<button id="ten" type="button">Flip 10 coins</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; }
.pick { margin: .4rem 0 .7rem; font-size: .92rem; }
.pick label { font-weight: 600; margin-right: .4rem; }
select { font: 500 14px system-ui, sans-serif; padding: .3rem .4rem; border: 1px solid #adb1b8; border-radius: 6px; }
.verdict { font-size: 1.05rem; font-weight: 700; margin: .5rem 0; min-height: 1.4em; color: #1d3557; }
.verdict.composite { color: #c92f3c; }
.verdict.prime { color: #0a7d33; }
.bar { height: 14px; background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 7px; overflow: hidden; }
.fill { height: 100%; width: 100%; background: #e63946; transition: width .25s; }
.err { font: 600 .95rem ui-monospace, monospace; color: #1d3557; margin: .35rem 0 .6rem; }
.log { font: 500 .82rem ui-monospace, monospace; color: #444; min-height: 3.2em; line-height: 1.4;
background: #f5f7f9; border: 1px solid #e1e7ec; border-radius: 8px; padding: .5rem .6rem; margin-bottom: .6rem; white-space: pre-wrap; }
.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] ?? ''));
// A randomized primality test (Miller-Rabin). For an odd n, write n-1 = d * 2^s.
// A random base a in [2, n-2] is a "witness" if it proves n composite. If the
// base fails to expose n, the test says "probably prime". A prime passes for
// EVERY base; a composite is exposed by at least 3/4 of all bases, so each
// independent coin flip that returns "probably prime" multiplies the chance of
// a wrong answer by at most 1/2. Checking is fast; the error shrinks like 1/2^k.
// This is the spirit of BPP: bounded error, amplified toward zero by repetition.
function modpow(base, exp, mod){
let r = 1n; base %= mod;
while (exp > 0n){ if (exp & 1n) r = (r * base) % mod; exp >>= 1n; base = (base * base) % mod; }
return r;
}
// Returns true if base `a` is a STRONG WITNESS that n is composite (exposes it).
function isWitness(a, n){
if (n % 2n === 0n) return n !== 2n;
let d = n - 1n, s = 0n;
while (d % 2n === 0n){ d /= 2n; s += 1n; }
let x = modpow(a, d, n);
if (x === 1n || x === n - 1n) return false; // looks prime for this base
for (let i = 0n; i < s - 1n; i++){
x = (x * x) % n;
if (x === n - 1n) return false; // looks prime for this base
}
return true; // a proves n is composite
}
let n = 561n, flips = 0, exposed = false;
function $(id){ return document.getElementById(id); }
function pct(p){ return p >= 1 ? '100%' : (p*100 < 0.01 ? '< 0.01%' : (p*100).toFixed(p*100<1?3:1) + '%'); }
function render(){
const bound = exposed ? 0 : Math.pow(0.5, flips); // upper bound on error
$('fill').style.width = (exposed ? 0 : bound*100) + '%';
if (exposed){
$('verdict').textContent = '✗ Definitely COMPOSITE (a witness proved it).';
$('verdict').className = 'verdict composite';
$('err').textContent = 'Error bound: 0% (a witness is a certificate)';
} else if (flips === 0){
$('verdict').textContent = 'No witnesses yet.';
$('verdict').className = 'verdict';
$('err').textContent = 'Error bound: 100%';
} else {
$('verdict').textContent = fmt('✓ Probably PRIME after %flips% coin flip%v%.', { flips: flips, v: flips>1?'s':'' });
$('verdict').className = 'verdict prime';
$('err').textContent = fmt('Error bound: ≤ 1/2^%flips% = %pct%', { flips: flips, pct: pct(bound) });
}
}
function flip(){
if (exposed) return;
// random base in [2, n-2]
const a = 2n + BigInt(Math.floor(Math.random() * Number(n - 3n)));
flips++;
if (isWitness(a, n)){
exposed = true;
$('log').textContent += fmt('flip %flips%: base %a% → WITNESS, n is composite.', { flips: flips, a: a }) + "\n";
} else {
$('log').textContent += fmt('flip %flips%: base %a% → looks prime.', { flips: flips, a: a }) + "\n";
}
$('log').scrollTop = $('log').scrollHeight;
render();
}
function reset(){
n = BigInt($('num').value); flips = 0; exposed = false;
$('log').textContent = ''; render();
}
$('flip').addEventListener('click', flip);
$('ten').addEventListener('click', function(){ for (let i=0;i<10 && !exposed;i++) flip(); });
$('reset').addEventListener('click', reset);
$('num').addEventListener('change', reset);
reset();
The point is what happens as you keep flipping. A prime always passes, so the verdict only sharpens for composites: every independent witness that says "looks prime" is another coin that had to land the wrong way. After k flips, the chance of being fooled is below 1 / 2ᵏ — ten flips already push it under one in a thousand. The error never reaches zero, but it shrinks so fast that you can make it smaller than the chance of a cosmic ray flipping a bit in your RAM. That is the whole spirit of BPP.
Comments
Loading comments...