Enter two numbers below and press Multiply. The demo walks through Karatsuba's divide-and-conquer split step by step, showing the three recursive sub-multiplications and how they are combined to get the final product.
<div class="controls">
<label>A: <input id="numA" type="number" value="1234" min="0" max="99999999" step="1"></label>
<label>B: <input id="numB" type="number" value="5678" min="0" max="99999999" step="1"></label>
<button id="btnMultiply" type="button">Multiply</button>
<button id="btnRandom" type="button" class="ghost">Random</button>
</div>
<div id="result" class="result hidden"></div>
<div id="steps" class="steps"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; margin-bottom: .8rem; }
label { display: flex; align-items: center; gap: .35rem; font-size: .95rem; font-weight: 600; }
input[type=number] { width: 110px; padding: .35rem .5rem; font: 600 .95rem ui-monospace,monospace;
border: 1.5px solid #c0c8d4; border-radius: 6px; }
button { font: 600 14px system-ui,sans-serif; padding: .4rem .9rem; border: 1.5px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.result { background: #e8f4ea; border: 1.5px solid #4caf50; border-radius: 8px;
padding: .55rem .9rem; font: 700 1.05rem ui-monospace,monospace; color: #1b5e20;
margin-bottom: .7rem; }
.result.hidden { display: none; }
.steps { display: flex; flex-direction: column; gap: .5rem; }
.step { background: #f0f4f8; border-left: 3px solid #1d3557; border-radius: 0 6px 6px 0;
padding: .45rem .75rem; font-size: .88rem; line-height: 1.55; }
.step.base { border-color: #4caf50; background: #f0faf1; }
.step .label { font-weight: 700; color: #1d3557; margin-bottom: .15rem; }
.step .label.base { color: #2e7d32; }
.step code { font-family: ui-monospace,monospace; background: #dde5ef; border-radius: 3px;
padding: 0 3px; font-size: .87em; }
.step .sub { color: #555; margin-top: .1rem; }
.highlight { color: #c0392b; font-weight: 700; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Karatsuba multiplication with step tracing
// Formula: x*y where x = a*10^m + b, y = c*10^m + d
// ac = a*c, bd = b*d, z = (a+b)*(c+d)
// x*y = ac * 10^(2m) + (z - ac - bd) * 10^m + bd
// Only 3 multiplications (ac, bd, z) instead of 4.
const log = [];
function karatsuba(x, y, depth) {
const indent = depth;
// Base case: single-digit (or small enough)
if (x < 10 || y < 10) {
const product = x * y;
log.push({ depth, base: true,
label: `Base case`,
text: `${x} × ${y} = ${product}`
});
return product;
}
// Split: use half the number of digits of the larger operand
const m = Math.floor(Math.max(String(x).length, String(y).length) / 2);
const p = Math.pow(10, m);
const a = Math.floor(x / p), b = x % p;
const c = Math.floor(y / p), d = y % p;
log.push({ depth, base: false,
label: fmt(`Split (depth %depth%)`, { depth: depth }),
text: `${x} = ${a}·10^${m} + ${b}`,
text2: `${y} = ${c}·10^${m} + ${d}`
});
// Three recursive multiplications
const ac = karatsuba(a, c, depth + 1); // mul 1
const bd = karatsuba(b, d, depth + 1); // mul 2
const mid = karatsuba(a + b, c + d, depth + 1); // mul 3: (a+b)(c+d)
const middle = mid - ac - bd; // a·d + b·c recovered by subtraction
const result = ac * p * p + middle * p + bd;
log.push({ depth, base: false,
label: fmt(`Combine (depth %depth%)`, { depth: depth }),
text: `ac=${ac}, bd=${bd}, (a+b)(c+d)=${mid}`,
text2: `middle = ${mid} − ${ac} − ${bd} = ${middle}`,
text3: `${x}×${y} = ${ac}·10^${m*2} + ${middle}·10^${m} + ${bd} = ${result}`,
highlight: true
});
return result;
}
function render(a, b, product) {
const res = document.getElementById('result');
res.textContent = `${a} × ${b} = ${product}`;
res.classList.remove('hidden');
const container = document.getElementById('steps');
container.innerHTML = '';
// Verify with native multiply
const expected = a * b;
const ok = product === expected;
log.forEach(entry => {
const div = document.createElement('div');
div.className = 'step' + (entry.base ? ' base' : '');
const lbl = document.createElement('div');
lbl.className = 'label' + (entry.base ? ' base' : '');
lbl.textContent = ' '.repeat(entry.depth) + entry.label;
div.appendChild(lbl);
const lines = [entry.text, entry.text2, entry.text3].filter(Boolean);
lines.forEach((line, i) => {
const p2 = document.createElement('div');
p2.className = 'sub';
if (entry.highlight && i === lines.length - 1) {
p2.innerHTML = line.replace(/(=\s*\d+\s*$)/, '<span class="highlight">$1</span>');
} else {
p2.textContent = line;
}
div.appendChild(p2);
});
container.appendChild(div);
});
// Verification note
const note = document.createElement('div');
note.className = 'step' + (ok ? ' base' : '');
note.innerHTML = fmt(`<div class="label %v%">Verification</div>
<div class="sub">%a% × %b% = <span class="highlight">%product%</span> (native: %expected%) — %v2%</div>`, { v: ok ? 'base' : '', a: a, b: b, product: product, expected: expected, v2: ok ? `✓ correct` : `✗ mismatch!` });
container.appendChild(note);
}
function multiply() {
const a = parseInt(document.getElementById('numA').value, 10) || 0;
const b = parseInt(document.getElementById('numB').value, 10) || 0;
log.length = 0;
const result = karatsuba(a, b, 0);
render(a, b, result);
}
function randomPair() {
const digits = 4 + Math.floor(Math.random() * 5); // 4–8 digit numbers
const max = Math.pow(10, digits);
document.getElementById('numA').value = Math.floor(Math.random() * (max - 1000) + 1000);
document.getElementById('numB').value = Math.floor(Math.random() * (max - 1000) + 1000);
multiply();
}
document.getElementById('btnMultiply').addEventListener('click', multiply);
document.getElementById('btnRandom').addEventListener('click', randomPair);
multiply();
<div class="controls">
<label>A: <input id="numA" type="number" value="1234" min="0" max="99999999" step="1"></label>
<label>B: <input id="numB" type="number" value="5678" min="0" max="99999999" step="1"></label>
<button id="btnMultiply" type="button">Multiply</button>
<button id="btnRandom" type="button" class="ghost">Random</button>
</div>
<div id="result" class="result hidden"></div>
<div id="steps" class="steps"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; margin-bottom: .8rem; }
label { display: flex; align-items: center; gap: .35rem; font-size: .95rem; font-weight: 600; }
input[type=number] { width: 110px; padding: .35rem .5rem; font: 600 .95rem ui-monospace,monospace;
border: 1.5px solid #c0c8d4; border-radius: 6px; }
button { font: 600 14px system-ui,sans-serif; padding: .4rem .9rem; border: 1.5px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.result { background: #e8f4ea; border: 1.5px solid #4caf50; border-radius: 8px;
padding: .55rem .9rem; font: 700 1.05rem ui-monospace,monospace; color: #1b5e20;
margin-bottom: .7rem; }
.result.hidden { display: none; }
.steps { display: flex; flex-direction: column; gap: .5rem; }
.step { background: #f0f4f8; border-left: 3px solid #1d3557; border-radius: 0 6px 6px 0;
padding: .45rem .75rem; font-size: .88rem; line-height: 1.55; }
.step.base { border-color: #4caf50; background: #f0faf1; }
.step .label { font-weight: 700; color: #1d3557; margin-bottom: .15rem; }
.step .label.base { color: #2e7d32; }
.step code { font-family: ui-monospace,monospace; background: #dde5ef; border-radius: 3px;
padding: 0 3px; font-size: .87em; }
.step .sub { color: #555; margin-top: .1rem; }
.highlight { color: #c0392b; font-weight: 700; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Karatsuba multiplication with step tracing
// Formula: x*y where x = a*10^m + b, y = c*10^m + d
// ac = a*c, bd = b*d, z = (a+b)*(c+d)
// x*y = ac * 10^(2m) + (z - ac - bd) * 10^m + bd
// Only 3 multiplications (ac, bd, z) instead of 4.
const log = [];
function karatsuba(x, y, depth) {
const indent = depth;
// Base case: single-digit (or small enough)
if (x < 10 || y < 10) {
const product = x * y;
log.push({ depth, base: true,
label: `Base case`,
text: `${x} × ${y} = ${product}`
});
return product;
}
// Split: use half the number of digits of the larger operand
const m = Math.floor(Math.max(String(x).length, String(y).length) / 2);
const p = Math.pow(10, m);
const a = Math.floor(x / p), b = x % p;
const c = Math.floor(y / p), d = y % p;
log.push({ depth, base: false,
label: fmt(`Split (depth %depth%)`, { depth: depth }),
text: `${x} = ${a}·10^${m} + ${b}`,
text2: `${y} = ${c}·10^${m} + ${d}`
});
// Three recursive multiplications
const ac = karatsuba(a, c, depth + 1); // mul 1
const bd = karatsuba(b, d, depth + 1); // mul 2
const mid = karatsuba(a + b, c + d, depth + 1); // mul 3: (a+b)(c+d)
const middle = mid - ac - bd; // a·d + b·c recovered by subtraction
const result = ac * p * p + middle * p + bd;
log.push({ depth, base: false,
label: fmt(`Combine (depth %depth%)`, { depth: depth }),
text: `ac=${ac}, bd=${bd}, (a+b)(c+d)=${mid}`,
text2: `middle = ${mid} − ${ac} − ${bd} = ${middle}`,
text3: `${x}×${y} = ${ac}·10^${m*2} + ${middle}·10^${m} + ${bd} = ${result}`,
highlight: true
});
return result;
}
function render(a, b, product) {
const res = document.getElementById('result');
res.textContent = `${a} × ${b} = ${product}`;
res.classList.remove('hidden');
const container = document.getElementById('steps');
container.innerHTML = '';
// Verify with native multiply
const expected = a * b;
const ok = product === expected;
log.forEach(entry => {
const div = document.createElement('div');
div.className = 'step' + (entry.base ? ' base' : '');
const lbl = document.createElement('div');
lbl.className = 'label' + (entry.base ? ' base' : '');
lbl.textContent = ' '.repeat(entry.depth) + entry.label;
div.appendChild(lbl);
const lines = [entry.text, entry.text2, entry.text3].filter(Boolean);
lines.forEach((line, i) => {
const p2 = document.createElement('div');
p2.className = 'sub';
if (entry.highlight && i === lines.length - 1) {
p2.innerHTML = line.replace(/(=\s*\d+\s*$)/, '<span class="highlight">$1</span>');
} else {
p2.textContent = line;
}
div.appendChild(p2);
});
container.appendChild(div);
});
// Verification note
const note = document.createElement('div');
note.className = 'step' + (ok ? ' base' : '');
note.innerHTML = fmt(`<div class="label %v%">Verification</div>
<div class="sub">%a% × %b% = <span class="highlight">%product%</span> (native: %expected%) — %v2%</div>`, { v: ok ? 'base' : '', a: a, b: b, product: product, expected: expected, v2: ok ? `✓ correct` : `✗ mismatch!` });
container.appendChild(note);
}
function multiply() {
const a = parseInt(document.getElementById('numA').value, 10) || 0;
const b = parseInt(document.getElementById('numB').value, 10) || 0;
log.length = 0;
const result = karatsuba(a, b, 0);
render(a, b, result);
}
function randomPair() {
const digits = 4 + Math.floor(Math.random() * 5); // 4–8 digit numbers
const max = Math.pow(10, digits);
document.getElementById('numA').value = Math.floor(Math.random() * (max - 1000) + 1000);
document.getElementById('numB').value = Math.floor(Math.random() * (max - 1000) + 1000);
multiply();
}
document.getElementById('btnMultiply').addEventListener('click', multiply);
document.getElementById('btnRandom').addEventListener('click', randomPair);
multiply();
Notice that each level splits the work into three smaller multiplications instead of four. Schoolbook would need four multiplications at each split: a·c, a·d, b·c, b·d. Karatsuba computes a·c, b·d, and one extra product (a+b)·(c+d), then recovers the middle term a·d + b·c by subtraction — saving one multiplication per level in exchange for a handful of additions.
Comments
Loading comments...