Pick any two positive numbers and run the algorithm one step at a time. Each step replaces the larger number by the remainder of dividing the larger by the smaller. Watch the pair collapse toward the answer.
<p class="hint">Enter two positive whole numbers. Each <b>step</b> replaces the larger number with the remainder of dividing it by the smaller. When the remainder hits zero, the other number is the greatest common divisor (GCD).</p>
<div class="inputs">
<label>a <input id="a" type="number" min="1" value="48"></label>
<label>b <input id="b" type="number" min="1" value="18"></label>
</div>
<div class="pair" id="pair"></div>
<div class="status" id="status">Press Step or Run all.</div>
<table class="log"><tbody id="log"></tbody></table>
<div class="btns">
<button id="step" type="button">Step</button>
<button id="run" type="button">Run all</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; }
.inputs { display: flex; gap: 1rem; margin: .3rem 0 .6rem; }
.inputs label { font: 600 14px system-ui, sans-serif; color: #1d3557; }
.inputs input { width: 90px; font: 600 15px ui-monospace, monospace; padding: .3rem .4rem;
border: 1px solid #cdd9e3; border-radius: 6px; }
.pair { display: flex; gap: .6rem; align-items: center; font: 700 26px ui-monospace, monospace;
color: #1d3557; min-height: 1.4em; margin: .3rem 0; }
.pair .num { background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 8px; padding: .15rem .6rem; }
.pair .gcd { background: #0a7d33; border-color: #0a7d33; color: #fff; }
.status { font-size: 1rem; font-weight: 600; margin: .4rem 0; min-height: 1.3em; }
.status.ok { color: #0a7d33; }
.log { border-collapse: collapse; font: 13px ui-monospace, monospace; margin: .3rem 0; }
.log td { border: 1px solid #e0e6ec; padding: .2rem .55rem; }
.log td:first-child { color: #888; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .4rem; }
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; }
button:disabled { opacity: .5; cursor: default; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Euclid's algorithm (c. 300 BC): gcd(a,b) = gcd(b, a mod b), repeated until the remainder is 0. Each step strictly shrinks the pair; the number of steps is logarithmic in the inputs (polynomial time). Worst case = consecutive Fibonacci numbers (Lamé, 1844).
let a, b, steps, done;
const $ = id => document.getElementById(id);
function init() {
a = Math.max(1, Math.floor(Math.abs(+$('a').value || 1)));
b = Math.max(1, Math.floor(Math.abs(+$('b').value || 1)));
if (a < b) { const t = a; a = b; b = t; } // keep a >= b
steps = 0; done = false;
$('log').innerHTML = '';
logRow('start', a, b, '');
render(); setStatus('Press Step or Run all.', '');
$('step').disabled = false; $('run').disabled = false;
}
function render() {
const p = $('pair');
if (done) { p.innerHTML = fmt('gcd = <span class="num gcd">%a%</span>', { a: a }); }
else { p.innerHTML = '<span class="num">' + a + '</span><span>,</span><span class="num">' + b + '</span>'; }
}
function logRow(label, x, y, note) {
const tr = document.createElement('tr');
tr.innerHTML = '<td>' + label + '</td><td>a=' + x + '</td><td>b=' + y + '</td><td>' + note + '</td>';
$('log').appendChild(tr);
}
function setStatus(t, cls) { const s = $('status'); s.textContent = t; s.className = 'status ' + cls; }
// one Euclidean step: a, b -> b, a mod b
function step() {
if (done) return;
if (b === 0) { finish(); return; }
const r = a % b;
logRow(fmt('step %steps%%v%', { steps: steps, v: 1 }), b, r, a + ' mod ' + b + ' = ' + r);
a = b; b = r; steps++;
if (b === 0) { finish(); } else { render(); setStatus(fmt('Step %steps%: remainder %b%.', { steps: steps, b: b }), ''); }
}
function finish() {
done = true; render();
setStatus(fmt('Done in %steps% %v% — gcd = %a%.', { steps: steps, v: steps === 1 ? 'step' : 'steps', a: a }), 'ok');
$('step').disabled = true; $('run').disabled = true;
console.log('gcd =', a, '| steps =', steps);
}
function runAll() { let guard = 0; while (!done && guard++ < 10000) step(); }
$('step').addEventListener('click', step);
$('run').addEventListener('click', runAll);
$('reset').addEventListener('click', init);
$('a').addEventListener('change', init);
$('b').addEventListener('change', init);
init();
<p class="hint">Enter two positive whole numbers. Each <b>step</b> replaces the larger number with the remainder of dividing it by the smaller. When the remainder hits zero, the other number is the greatest common divisor (GCD).</p>
<div class="inputs">
<label>a <input id="a" type="number" min="1" value="48"></label>
<label>b <input id="b" type="number" min="1" value="18"></label>
</div>
<div class="pair" id="pair"></div>
<div class="status" id="status">Press Step or Run all.</div>
<table class="log"><tbody id="log"></tbody></table>
<div class="btns">
<button id="step" type="button">Step</button>
<button id="run" type="button">Run all</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; }
.inputs { display: flex; gap: 1rem; margin: .3rem 0 .6rem; }
.inputs label { font: 600 14px system-ui, sans-serif; color: #1d3557; }
.inputs input { width: 90px; font: 600 15px ui-monospace, monospace; padding: .3rem .4rem;
border: 1px solid #cdd9e3; border-radius: 6px; }
.pair { display: flex; gap: .6rem; align-items: center; font: 700 26px ui-monospace, monospace;
color: #1d3557; min-height: 1.4em; margin: .3rem 0; }
.pair .num { background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 8px; padding: .15rem .6rem; }
.pair .gcd { background: #0a7d33; border-color: #0a7d33; color: #fff; }
.status { font-size: 1rem; font-weight: 600; margin: .4rem 0; min-height: 1.3em; }
.status.ok { color: #0a7d33; }
.log { border-collapse: collapse; font: 13px ui-monospace, monospace; margin: .3rem 0; }
.log td { border: 1px solid #e0e6ec; padding: .2rem .55rem; }
.log td:first-child { color: #888; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .4rem; }
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; }
button:disabled { opacity: .5; cursor: default; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Euclid's algorithm (c. 300 BC): gcd(a,b) = gcd(b, a mod b), repeated until the remainder is 0. Each step strictly shrinks the pair; the number of steps is logarithmic in the inputs (polynomial time). Worst case = consecutive Fibonacci numbers (Lamé, 1844).
let a, b, steps, done;
const $ = id => document.getElementById(id);
function init() {
a = Math.max(1, Math.floor(Math.abs(+$('a').value || 1)));
b = Math.max(1, Math.floor(Math.abs(+$('b').value || 1)));
if (a < b) { const t = a; a = b; b = t; } // keep a >= b
steps = 0; done = false;
$('log').innerHTML = '';
logRow('start', a, b, '');
render(); setStatus('Press Step or Run all.', '');
$('step').disabled = false; $('run').disabled = false;
}
function render() {
const p = $('pair');
if (done) { p.innerHTML = fmt('gcd = <span class="num gcd">%a%</span>', { a: a }); }
else { p.innerHTML = '<span class="num">' + a + '</span><span>,</span><span class="num">' + b + '</span>'; }
}
function logRow(label, x, y, note) {
const tr = document.createElement('tr');
tr.innerHTML = '<td>' + label + '</td><td>a=' + x + '</td><td>b=' + y + '</td><td>' + note + '</td>';
$('log').appendChild(tr);
}
function setStatus(t, cls) { const s = $('status'); s.textContent = t; s.className = 'status ' + cls; }
// one Euclidean step: a, b -> b, a mod b
function step() {
if (done) return;
if (b === 0) { finish(); return; }
const r = a % b;
logRow(fmt('step %steps%%v%', { steps: steps, v: 1 }), b, r, a + ' mod ' + b + ' = ' + r);
a = b; b = r; steps++;
if (b === 0) { finish(); } else { render(); setStatus(fmt('Step %steps%: remainder %b%.', { steps: steps, b: b }), ''); }
}
function finish() {
done = true; render();
setStatus(fmt('Done in %steps% %v% — gcd = %a%.', { steps: steps, v: steps === 1 ? 'step' : 'steps', a: a }), 'ok');
$('step').disabled = true; $('run').disabled = true;
console.log('gcd =', a, '| steps =', steps);
}
function runAll() { let guard = 0; while (!done && guard++ < 10000) step(); }
$('step').addEventListener('click', step);
$('run').addEventListener('click', runAll);
$('reset').addEventListener('click', init);
$('a').addEventListener('change', init);
$('b').addEventListener('change', init);
init();
Notice how few steps it takes. Even starting from large, unrelated numbers, the pair shrinks by a lot every couple of steps — never by just one. Try 48 and 18, then try the consecutive Fibonacci numbers 89 and 55: those are the worst case, the inputs that force the most steps, and even they finish in a handful. That is the magic: brute-force divisor hunting would take thousands of checks where Euclid takes single digits.
Comments
Loading comments...