Pick a composite number from the list (or type your own), then press Run Pollard's Rho to watch the algorithm work step by step.
The demo shows two "runners" — slow (tortoise) and fast (hare) — advancing through the sequence xn+1=xn2+1 (mod n). When they land on the same value, Floyd's cycle detection fires. The GCD of their difference with n reveals the hidden prime factor.
<p class="hint">Pick a composite number and watch Pollard's rho find a prime factor. The <b>tortoise</b> moves one step at a time; the <b>hare</b> moves two. When they meet, Floyd's cycle detection fires and gcd reveals the factor.</p>
<div class="controls">
<label>n = <select id="nsel">
<option value="8051">8051 = 83 × 97</option>
<option value="1387">1387 = 19 × 73</option>
<option value="10403">10403 = 101 × 103</option>
<option value="77">77 = 7 × 11</option>
<option value="1649">1649 = 17 × 97</option>
<option value="custom">Custom…</option>
</select></label>
<input id="ncustom" type="number" min="4" max="9999999" placeholder="enter n" style="display:none;width:110px">
<button id="runBtn" type="button">Run Pollard's Rho</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div id="seqBox" class="seq-box"></div>
<div id="status" class="status"></div>
<table id="stepsTable" class="steps-table" style="display:none">
<thead><tr><th>Step</th><th>Tortoise xᵢ</th><th>Hare x₂ᵢ</th><th>|xᵢ − x₂ᵢ|</th><th>gcd with n</th></tr></thead>
<tbody id="stepsBody"></tbody>
</table>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: .8rem; }
label { font-size: .9rem; }
select, input[type=number] { font-size: .9rem; padding: .3rem .4rem; border: 1px solid #bbb; border-radius: 6px; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.seq-box { display: flex; flex-wrap: wrap; gap: 5px; min-height: 2.4rem; margin-bottom: .6rem; }
.node { width: 46px; height: 36px; display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace,monospace; border-radius: 7px; border: 1.5px solid #cdd9e3;
background: #e8eef3; color: #1d3557; transition: background .2s; }
.node.tortoise { background: #457b9d; color: #fff; border-color: #1d3557; }
.node.hare { background: #e63946; color: #fff; border-color: #c92f3c; }
.node.both { background: #8338ec; color: #fff; border-color: #5a10c7; }
.node.factor { background: #2dc653; color: #fff; border-color: #1a8a38; }
.status { font-size: 1rem; font-weight: 600; min-height: 1.5em; margin-bottom: .5rem; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.steps-table { width: 100%; border-collapse: collapse; font-size: .85rem; margin-top: .4rem; }
.steps-table th { background: #1d3557; color: #fff; padding: .35rem .5rem; text-align: left; }
.steps-table td { padding: .3rem .5rem; border-bottom: 1px solid #dde3e8; font-family: ui-monospace,monospace; }
.steps-table tr.hit td { background: #d4edda; font-weight: 700; }
@media (max-width: 480px) { .steps-table { font-size: .75rem; } }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Pollard's rho algorithm (Brent variant of Floyd cycle detection)
// f(x) = (x*x + 1) mod n
// tortoise: one step per iteration
// hare: two steps per iteration
// When tortoise == hare: gcd(|tortoise - hare|, n) may reveal a factor.
// (We record the step BEFORE they become equal, then compute gcd of difference.)
function gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { [a, b] = [b, a % b]; } return a; }
function f(x, n) { return (x * x + 1) % n; }
const nsel = document.getElementById('nsel');
const ncustom = document.getElementById('ncustom');
const runBtn = document.getElementById('runBtn');
const resetBtn = document.getElementById('resetBtn');
const seqBox = document.getElementById('seqBox');
const status = document.getElementById('status');
const stepsTable = document.getElementById('stepsTable');
const stepsBody = document.getElementById('stepsBody');
nsel.addEventListener('change', () => {
ncustom.style.display = nsel.value === 'custom' ? 'inline-block' : 'none';
reset();
});
function getN() {
const raw = nsel.value === 'custom' ? parseInt(ncustom.value, 10) : parseInt(nsel.value, 10);
return isNaN(raw) ? 8051 : raw;
}
function reset() {
seqBox.innerHTML = '';
status.textContent = '';
status.className = 'status';
stepsTable.style.display = 'none';
stepsBody.innerHTML = '';
}
function run() {
reset();
const n = getN();
if (n < 4) { status.textContent = 'n must be ≥ 4'; status.className = 'status bad'; return; }
if (n % 2 === 0) { showResult([2], n, 0); return; }
let tort = 2, hare = 2;
const MAX_STEPS = 200;
const stepLog = []; // {step, tort, hare, diff, g}
let factor = null;
for (let step = 1; step <= MAX_STEPS; step++) {
tort = f(tort, n);
hare = f(f(hare, n), n);
const diff = Math.abs(tort - hare);
const g = gcd(diff, n);
stepLog.push({ step, tort, hare, diff, g });
if (g !== 1 && g !== n) { factor = g; break; }
if (diff === 0) { status.textContent = 'Cycle found but gcd = n. Try a different starting point or n.'; status.className = 'status bad'; return; }
}
if (!factor) { status.textContent = fmt('Could not find a factor in %MAX_STEPS% steps.', { MAX_STEPS: MAX_STEPS }); status.className = 'status bad'; return; }
// Build sequence visualization (tortoise positions)
const seqVals = [2];
let xv = 2;
for (let i = 0; i < stepLog.length; i++) { xv = stepLog[i].tort; seqVals.push(xv); }
seqBox.innerHTML = '';
const lastTort = stepLog[stepLog.length - 1].tort;
const lastHare = stepLog[stepLog.length - 1].hare;
seqVals.forEach((v, i) => {
const el = document.createElement('div');
const isTort = v === lastTort && i === seqVals.length - 1;
const isHare = v === lastHare && i === seqVals.length - 1;
el.className = 'node' + (isTort && isHare ? ' both' : isTort ? ' tortoise' : isHare ? ' hare' : '');
el.textContent = v;
el.title = i === 0 ? 'start x₀=2' : fmt('x%i% (tortoise step %i%)', { i: i });
seqBox.appendChild(el);
});
// Render steps table
stepsTable.style.display = '';
stepsBody.innerHTML = '';
stepLog.forEach(({ step, tort, hare, diff, g }) => {
const tr = document.createElement('tr');
if (g !== 1 && g !== n) tr.className = 'hit';
tr.innerHTML = `<td>${step}</td><td>${tort}</td><td>${hare}</td><td>${diff}</td><td>${g}</td>`;
stepsBody.appendChild(tr);
});
// Show result
const other = n / factor;
status.textContent = fmt('✓ Found factor %factor% — so %n% = %factor% × %other% (after %length% steps)', { factor: factor, n: n, other: other, length: stepLog.length });
status.className = 'status ok';
console.log(fmt('Pollard rho: n=%n% => %factor% × %other% in %length% steps', { n: n, factor: factor, other: other, length: stepLog.length }));
}
runBtn.addEventListener('click', run);
resetBtn.addEventListener('click', reset);
<p class="hint">Pick a composite number and watch Pollard's rho find a prime factor. The <b>tortoise</b> moves one step at a time; the <b>hare</b> moves two. When they meet, Floyd's cycle detection fires and gcd reveals the factor.</p>
<div class="controls">
<label>n = <select id="nsel">
<option value="8051">8051 = 83 × 97</option>
<option value="1387">1387 = 19 × 73</option>
<option value="10403">10403 = 101 × 103</option>
<option value="77">77 = 7 × 11</option>
<option value="1649">1649 = 17 × 97</option>
<option value="custom">Custom…</option>
</select></label>
<input id="ncustom" type="number" min="4" max="9999999" placeholder="enter n" style="display:none;width:110px">
<button id="runBtn" type="button">Run Pollard's Rho</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div id="seqBox" class="seq-box"></div>
<div id="status" class="status"></div>
<table id="stepsTable" class="steps-table" style="display:none">
<thead><tr><th>Step</th><th>Tortoise xᵢ</th><th>Hare x₂ᵢ</th><th>|xᵢ − x₂ᵢ|</th><th>gcd with n</th></tr></thead>
<tbody id="stepsBody"></tbody>
</table>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: .8rem; }
label { font-size: .9rem; }
select, input[type=number] { font-size: .9rem; padding: .3rem .4rem; border: 1px solid #bbb; border-radius: 6px; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.seq-box { display: flex; flex-wrap: wrap; gap: 5px; min-height: 2.4rem; margin-bottom: .6rem; }
.node { width: 46px; height: 36px; display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace,monospace; border-radius: 7px; border: 1.5px solid #cdd9e3;
background: #e8eef3; color: #1d3557; transition: background .2s; }
.node.tortoise { background: #457b9d; color: #fff; border-color: #1d3557; }
.node.hare { background: #e63946; color: #fff; border-color: #c92f3c; }
.node.both { background: #8338ec; color: #fff; border-color: #5a10c7; }
.node.factor { background: #2dc653; color: #fff; border-color: #1a8a38; }
.status { font-size: 1rem; font-weight: 600; min-height: 1.5em; margin-bottom: .5rem; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.steps-table { width: 100%; border-collapse: collapse; font-size: .85rem; margin-top: .4rem; }
.steps-table th { background: #1d3557; color: #fff; padding: .35rem .5rem; text-align: left; }
.steps-table td { padding: .3rem .5rem; border-bottom: 1px solid #dde3e8; font-family: ui-monospace,monospace; }
.steps-table tr.hit td { background: #d4edda; font-weight: 700; }
@media (max-width: 480px) { .steps-table { font-size: .75rem; } }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Pollard's rho algorithm (Brent variant of Floyd cycle detection)
// f(x) = (x*x + 1) mod n
// tortoise: one step per iteration
// hare: two steps per iteration
// When tortoise == hare: gcd(|tortoise - hare|, n) may reveal a factor.
// (We record the step BEFORE they become equal, then compute gcd of difference.)
function gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { [a, b] = [b, a % b]; } return a; }
function f(x, n) { return (x * x + 1) % n; }
const nsel = document.getElementById('nsel');
const ncustom = document.getElementById('ncustom');
const runBtn = document.getElementById('runBtn');
const resetBtn = document.getElementById('resetBtn');
const seqBox = document.getElementById('seqBox');
const status = document.getElementById('status');
const stepsTable = document.getElementById('stepsTable');
const stepsBody = document.getElementById('stepsBody');
nsel.addEventListener('change', () => {
ncustom.style.display = nsel.value === 'custom' ? 'inline-block' : 'none';
reset();
});
function getN() {
const raw = nsel.value === 'custom' ? parseInt(ncustom.value, 10) : parseInt(nsel.value, 10);
return isNaN(raw) ? 8051 : raw;
}
function reset() {
seqBox.innerHTML = '';
status.textContent = '';
status.className = 'status';
stepsTable.style.display = 'none';
stepsBody.innerHTML = '';
}
function run() {
reset();
const n = getN();
if (n < 4) { status.textContent = 'n must be ≥ 4'; status.className = 'status bad'; return; }
if (n % 2 === 0) { showResult([2], n, 0); return; }
let tort = 2, hare = 2;
const MAX_STEPS = 200;
const stepLog = []; // {step, tort, hare, diff, g}
let factor = null;
for (let step = 1; step <= MAX_STEPS; step++) {
tort = f(tort, n);
hare = f(f(hare, n), n);
const diff = Math.abs(tort - hare);
const g = gcd(diff, n);
stepLog.push({ step, tort, hare, diff, g });
if (g !== 1 && g !== n) { factor = g; break; }
if (diff === 0) { status.textContent = 'Cycle found but gcd = n. Try a different starting point or n.'; status.className = 'status bad'; return; }
}
if (!factor) { status.textContent = fmt('Could not find a factor in %MAX_STEPS% steps.', { MAX_STEPS: MAX_STEPS }); status.className = 'status bad'; return; }
// Build sequence visualization (tortoise positions)
const seqVals = [2];
let xv = 2;
for (let i = 0; i < stepLog.length; i++) { xv = stepLog[i].tort; seqVals.push(xv); }
seqBox.innerHTML = '';
const lastTort = stepLog[stepLog.length - 1].tort;
const lastHare = stepLog[stepLog.length - 1].hare;
seqVals.forEach((v, i) => {
const el = document.createElement('div');
const isTort = v === lastTort && i === seqVals.length - 1;
const isHare = v === lastHare && i === seqVals.length - 1;
el.className = 'node' + (isTort && isHare ? ' both' : isTort ? ' tortoise' : isHare ? ' hare' : '');
el.textContent = v;
el.title = i === 0 ? 'start x₀=2' : fmt('x%i% (tortoise step %i%)', { i: i });
seqBox.appendChild(el);
});
// Render steps table
stepsTable.style.display = '';
stepsBody.innerHTML = '';
stepLog.forEach(({ step, tort, hare, diff, g }) => {
const tr = document.createElement('tr');
if (g !== 1 && g !== n) tr.className = 'hit';
tr.innerHTML = `<td>${step}</td><td>${tort}</td><td>${hare}</td><td>${diff}</td><td>${g}</td>`;
stepsBody.appendChild(tr);
});
// Show result
const other = n / factor;
status.textContent = fmt('✓ Found factor %factor% — so %n% = %factor% × %other% (after %length% steps)', { factor: factor, n: n, other: other, length: stepLog.length });
status.className = 'status ok';
console.log(fmt('Pollard rho: n=%n% => %factor% × %other% in %length% steps', { n: n, factor: factor, other: other, length: stepLog.length }));
}
runBtn.addEventListener('click', run);
resetBtn.addEventListener('click', reset);
Notice how few steps it takes. For n = 8051 the algorithm finds factor 97 in a handful of iterations — far fewer than the 89 trial divisions needed to reach it the slow way.
Comments
Loading comments...