Below is a fixed polynomial and a value of x. Press Evaluate to run both methods side by side: the naive approach that computes each power of x from scratch, and Horner's nested sweep.
<p class="hint">This fixed degree-5 polynomial gets evaluated two ways at the same x: the naive method recomputes each power of x from scratch, while Horner's method nests the multiplications so no power is ever computed on its own.</p>
<div class="poly" id="poly"></div>
<div class="row">
<label for="xval">x =</label>
<input id="xval" type="number" value="3" step="1" />
<button id="run" type="button">Evaluate</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="cols">
<div class="col">
<h4>Naive (powers from scratch)</h4>
<div class="log" id="naiveLog"></div>
<div class="tally" id="naiveTally"></div>
</div>
<div class="col">
<h4>Horner (nested)</h4>
<div class="log" id="hornerLog"></div>
<div class="tally" id="hornerTally"></div>
</div>
</div>
<div class="status" id="status">Pick a value of x and press Evaluate.</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 .6rem; line-height: 1.45; }
.poly { font: 700 16px ui-monospace, monospace; background: #eef2f6; border: 1px solid #d3dce4;
border-radius: 8px; padding: .5rem .7rem; margin-bottom: .6rem; color: #1d3557; }
.row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
label { font: 600 13px system-ui, sans-serif; color: #444; }
input[type="number"] { width: 4.5rem; font: 600 14px ui-monospace, monospace; padding: .35rem .5rem;
border: 1px solid #adb1b8; border-radius: 6px; }
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; }
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; }
.col h4 { margin: 0 0 .3rem; font-size: .85rem; color: #1d3557; }
.log { font: 12px ui-monospace, monospace; background: #f7f9fb; border: 1px solid #e1e6ea;
border-radius: 6px; padding: .4rem .5rem; min-height: 90px; max-height: 140px; overflow-y: auto;
white-space: pre-wrap; line-height: 1.4; }
.tally { font: 700 13px system-ui, sans-serif; margin-top: .35rem; color: #1d3557; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
@media (max-width: 480px) { .cols { grid-template-columns: 1fr; } }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Fixed coefficients, highest degree first: 2x^5 - 3x^4 + 0x^3 + 5x^2 - 1x + 4.
const COEFFS = [2, -3, 0, 5, -1, 4]; // 2x^5 - 3x^4 + 0x^3 + 5x^2 - 1x + 4
const N = COEFFS.length - 1; // degree
function renderPoly(){
document.getElementById('poly').textContent =
'p(x) = 2x^5 - 3x^4 + 0x^3 + 5x^2 - 1x + 4';
}
// Naive evaluation: for each term, rebuild x^power by repeated multiplication, then scale by its coefficient.
function naiveEval(x){
let total = 0, mults = 0, adds = 0;
const log = [];
for (let j = 0; j <= N; j++){
const power = N - j;
let val = 1;
for (let k = 0; k < power; k++) { val *= x; mults++; }
const term = COEFFS[j] * val;
if (power > 0) mults++; // one multiplication to scale the power by its coefficient
total += term;
adds++;
log.push(fmt('coeff %coeff% * x^%power% = %term%', { coeff: COEFFS[j], power: power, term: term }));
}
return { total, mults, adds, log };
}
// Horner's method: start from the leading coefficient and repeatedly do result = result * x + next coefficient.
function hornerEval(x){
let result = COEFFS[0];
let mults = 0, adds = 0;
const log = [fmt('start: result = %c0%', { c0: COEFFS[0] })];
for (let i = 1; i < COEFFS.length; i++){
const before = result;
result = result * x + COEFFS[i];
mults++; adds++;
log.push(fmt('result = %before% * %x% + %coeff% = %after%', { before: before, x: x, coeff: COEFFS[i], after: result }));
}
return { result, mults, adds, log };
}
function setStatus(t, cls){
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + (cls || '');
}
function run(){
const x = Number(document.getElementById('xval').value) || 0;
const naive = naiveEval(x);
const horner = hornerEval(x);
document.getElementById('naiveLog').textContent = naive.log.join('\n');
document.getElementById('hornerLog').textContent = horner.log.join('\n');
document.getElementById('naiveTally').textContent =
fmt('%mults% multiplications, %adds% additions', { mults: naive.mults, adds: naive.adds });
document.getElementById('hornerTally').textContent =
fmt('%mults% multiplications, %adds% additions', { mults: horner.mults, adds: horner.adds });
// Sanity check — both methods must land on the exact same value.
if (naive.total === horner.result){
setStatus(fmt('Both methods agree: p(x) = %value%. Horner used %saved% fewer multiplications.', { value: horner.result, saved: naive.mults - horner.mults }), 'ok');
console.log('Match:', naive.total, horner.result);
} else {
setStatus('Something is wrong — the two results should always match.', '');
console.log('Mismatch:', naive.total, horner.result);
}
}
document.getElementById('run').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', () => {
document.getElementById('xval').value = 3;
document.getElementById('naiveLog').textContent = '';
document.getElementById('hornerLog').textContent = '';
document.getElementById('naiveTally').textContent = '';
document.getElementById('hornerTally').textContent = '';
setStatus('Pick a value of x and press Evaluate.', '');
});
renderPoly();
<p class="hint">This fixed degree-5 polynomial gets evaluated two ways at the same x: the naive method recomputes each power of x from scratch, while Horner's method nests the multiplications so no power is ever computed on its own.</p>
<div class="poly" id="poly"></div>
<div class="row">
<label for="xval">x =</label>
<input id="xval" type="number" value="3" step="1" />
<button id="run" type="button">Evaluate</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="cols">
<div class="col">
<h4>Naive (powers from scratch)</h4>
<div class="log" id="naiveLog"></div>
<div class="tally" id="naiveTally"></div>
</div>
<div class="col">
<h4>Horner (nested)</h4>
<div class="log" id="hornerLog"></div>
<div class="tally" id="hornerTally"></div>
</div>
</div>
<div class="status" id="status">Pick a value of x and press Evaluate.</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 .6rem; line-height: 1.45; }
.poly { font: 700 16px ui-monospace, monospace; background: #eef2f6; border: 1px solid #d3dce4;
border-radius: 8px; padding: .5rem .7rem; margin-bottom: .6rem; color: #1d3557; }
.row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
label { font: 600 13px system-ui, sans-serif; color: #444; }
input[type="number"] { width: 4.5rem; font: 600 14px ui-monospace, monospace; padding: .35rem .5rem;
border: 1px solid #adb1b8; border-radius: 6px; }
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; }
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; }
.col h4 { margin: 0 0 .3rem; font-size: .85rem; color: #1d3557; }
.log { font: 12px ui-monospace, monospace; background: #f7f9fb; border: 1px solid #e1e6ea;
border-radius: 6px; padding: .4rem .5rem; min-height: 90px; max-height: 140px; overflow-y: auto;
white-space: pre-wrap; line-height: 1.4; }
.tally { font: 700 13px system-ui, sans-serif; margin-top: .35rem; color: #1d3557; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
@media (max-width: 480px) { .cols { grid-template-columns: 1fr; } }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Fixed coefficients, highest degree first: 2x^5 - 3x^4 + 0x^3 + 5x^2 - 1x + 4.
const COEFFS = [2, -3, 0, 5, -1, 4]; // 2x^5 - 3x^4 + 0x^3 + 5x^2 - 1x + 4
const N = COEFFS.length - 1; // degree
function renderPoly(){
document.getElementById('poly').textContent =
'p(x) = 2x^5 - 3x^4 + 0x^3 + 5x^2 - 1x + 4';
}
// Naive evaluation: for each term, rebuild x^power by repeated multiplication, then scale by its coefficient.
function naiveEval(x){
let total = 0, mults = 0, adds = 0;
const log = [];
for (let j = 0; j <= N; j++){
const power = N - j;
let val = 1;
for (let k = 0; k < power; k++) { val *= x; mults++; }
const term = COEFFS[j] * val;
if (power > 0) mults++; // one multiplication to scale the power by its coefficient
total += term;
adds++;
log.push(fmt('coeff %coeff% * x^%power% = %term%', { coeff: COEFFS[j], power: power, term: term }));
}
return { total, mults, adds, log };
}
// Horner's method: start from the leading coefficient and repeatedly do result = result * x + next coefficient.
function hornerEval(x){
let result = COEFFS[0];
let mults = 0, adds = 0;
const log = [fmt('start: result = %c0%', { c0: COEFFS[0] })];
for (let i = 1; i < COEFFS.length; i++){
const before = result;
result = result * x + COEFFS[i];
mults++; adds++;
log.push(fmt('result = %before% * %x% + %coeff% = %after%', { before: before, x: x, coeff: COEFFS[i], after: result }));
}
return { result, mults, adds, log };
}
function setStatus(t, cls){
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + (cls || '');
}
function run(){
const x = Number(document.getElementById('xval').value) || 0;
const naive = naiveEval(x);
const horner = hornerEval(x);
document.getElementById('naiveLog').textContent = naive.log.join('\n');
document.getElementById('hornerLog').textContent = horner.log.join('\n');
document.getElementById('naiveTally').textContent =
fmt('%mults% multiplications, %adds% additions', { mults: naive.mults, adds: naive.adds });
document.getElementById('hornerTally').textContent =
fmt('%mults% multiplications, %adds% additions', { mults: horner.mults, adds: horner.adds });
// Sanity check — both methods must land on the exact same value.
if (naive.total === horner.result){
setStatus(fmt('Both methods agree: p(x) = %value%. Horner used %saved% fewer multiplications.', { value: horner.result, saved: naive.mults - horner.mults }), 'ok');
console.log('Match:', naive.total, horner.result);
} else {
setStatus('Something is wrong — the two results should always match.', '');
console.log('Mismatch:', naive.total, horner.result);
}
}
document.getElementById('run').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', () => {
document.getElementById('xval').value = 3;
document.getElementById('naiveLog').textContent = '';
document.getElementById('hornerLog').textContent = '';
document.getElementById('naiveTally').textContent = '';
document.getElementById('hornerTally').textContent = '';
setStatus('Pick a value of x and press Evaluate.', '');
});
renderPoly();
Watch the operation counters, not just the final number — both methods land on the same answer (evaluation is one of the easiest correctness checks in computer science), but Horner's sweep gets there using far fewer multiplications. Bump the degree up and the gap only grows.
Comments
Loading comments...