Below is p(x)=(x−1)2(x+2)3, a degree-5 polynomial with a double root at x=1 and a triple root at x=−2. Step through the process: differentiate, then compute the gcd of p and p′, then divide it out.
<p class="hint">p(x) = (x-1)²(x+2)³ has a double root at x = 1 and a triple root at x = -2. Step through the pipeline to strip the repeats using nothing but a derivative, a gcd, and a division.</p>
<div class="poly-row"><span class="poly-label">p(x) =</span><span class="poly-expr" id="expr-p"></span></div>
<div class="poly-row" id="row-dp"><span class="poly-label">p’(x) =</span><span class="poly-expr" id="expr-dp">—</span></div>
<div class="poly-row" id="row-gcd"><span class="poly-label">gcd =</span><span class="poly-expr" id="expr-gcd">—</span></div>
<div class="poly-row" id="row-sf"><span class="poly-label">squarefree =</span><span class="poly-expr" id="expr-sf">—</span></div>
<div class="axis-wrap">
<div class="axis" id="axis-before"></div>
<div class="axis-title">Roots of p(x), with multiplicity</div>
<div class="axis" id="axis-after"></div>
<div class="axis-title">Roots after dividing out the gcd</div>
</div>
<div class="status" id="status">Press "Differentiate" to begin.</div>
<div class="btns">
<button id="step-deriv" type="button">Differentiate</button>
<button id="step-gcd" type="button">Compute gcd</button>
<button id="step-divide" type="button">Divide it out</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; }
.poly-row { display: flex; gap: .5rem; align-items: baseline; font: 600 15px ui-monospace, monospace;
margin: .15rem 0; opacity: .35; transition: opacity .25s; }
.poly-row.active { opacity: 1; }
.poly-label { min-width: 8ch; color: #1d3557; font-family: system-ui, sans-serif; font-weight: 700; font-size: 13px; }
.poly-expr { color: #222; }
.axis-wrap { margin: .9rem 0 .6rem; }
.axis-title { font-size: .78rem; color: #555; margin: .15rem 0 .5rem; }
.axis { position: relative; height: 34px; border-bottom: 2px solid #cdd9e3; margin: 0 6px; }
.tick { position: absolute; bottom: -3px; width: 2px; height: 8px; background: #adb1b8; }
.root-dot { position: absolute; bottom: -11px; width: 22px; height: 22px; border-radius: 50%;
display: flex; align-items: center; justify-content: center; transform: translateX(-50%);
font: 700 11px ui-monospace, monospace; color: #fff; background: #e63946; border: 2px solid #c92f3c;
transition: left .4s ease; }
.root-dot.mult1 { background: #0a7d33; border-color: #086e2c; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.info { color: #1d3557; }
.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:disabled { opacity: .45; cursor: default; }
button.ghost { background: #fff; color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Squarefree factorization demo: strip repeated roots from a fixed polynomial
// using only its derivative and a polynomial gcd — no root-finding involved.
// p(x) = (x-1)^2 (x+2)^3, low-to-high coefficients
const P = [8, -4, -10, 1, 4, 1];
// Drop trailing near-zero high-degree coefficients so degree stays accurate.
function trim(p){ p = p.slice(); while (p.length > 1 && Math.abs(p[p.length-1]) < 1e-9) p.pop(); return p; }
// Derivative of p(x) = sum a_i x^i is sum i*a_i x^(i-1), coefficient-wise.
function deriv(p){ const d = []; for (let i=1;i<p.length;i++) d.push(i*p[i]); return trim(d.length?d:[0]); }
// Polynomial long division, returning quotient and remainder (low-to-high coeffs).
function divMod(a,b){
a = trim(a.slice()); b = trim(b.slice());
let rem = a.slice();
const degb = b.length-1;
const dega = a.length-1;
const quot = new Array(Math.max(dega-degb+1,0)).fill(0);
while (true){
rem = trim(rem);
const degr = rem.length-1;
if (degr < degb || (rem.length===1 && Math.abs(rem[0])<1e-9)) break;
const coef = rem[degr]/b[degb];
const shift = degr-degb;
quot[shift] = coef;
for (let i=0;i<=degb;i++) rem[shift+i] -= coef*b[i];
}
return { quot: trim(quot), rem: trim(rem) };
}
// Euclidean algorithm for polynomials, normalized to a monic result.
function gcd(a,b){
a = trim(a.slice()); b = trim(b.slice());
while (!(b.length===1 && Math.abs(b[0])<1e-9)){
const { rem } = divMod(a,b);
a = b; b = trim(rem);
}
const lc = a[a.length-1];
return a.map(x => Math.round((x/lc)*1e6)/1e6);
}
// Render a coefficient array (low-to-high) as a human-readable polynomial string.
function fmtPoly(p){
const deg = p.length-1;
const terms = [];
for (let i=deg;i>=0;i--){
const c = p[i];
if (Math.abs(c) < 1e-9) continue;
const ac = Math.abs(c);
let t;
if (i===0) t = String(ac);
else if (i===1) t = (ac===1?'':String(ac)) + 'x';
else t = (ac===1?'':String(ac)) + 'x^' + i;
terms.push((c<0 ? '− ' : (terms.length ? '+ ' : '')) + t);
}
return terms.join(' ') || '0';
}
// Known roots of P and their multiplicities, used only to draw the number line.
// The two real roots of P and their multiplicity in P, hardcoded because
// this demo is about *separating* known multiplicities, not root-finding.
const ROOTS = [ { x: -2, mult: 3 }, { x: 1, mult: 2 } ];
const SF_ROOTS = [ { x: -2, mult: 1 }, { x: 1, mult: 1 } ];
function drawAxis(elId, roots){
const el = document.getElementById(elId);
el.innerHTML = '';
const min = -4, max = 3;
const toPct = x => ((x - min) / (max - min)) * 100;
for (let t = Math.ceil(min); t <= Math.floor(max); t++){
const tick = document.createElement('div');
tick.className = 'tick';
tick.style.left = toPct(t) + '%';
el.appendChild(tick);
}
roots.forEach(r => {
const dot = document.createElement('div');
dot.className = 'root-dot' + (r.mult === 1 ? ' mult1' : '');
dot.style.left = toPct(r.x) + '%';
dot.textContent = r.mult > 1 ? ('×' + r.mult) : '1';
dot.title = i18nFmt('root x = %x%, multiplicity %m%', { x: r.x, m: r.mult });
el.appendChild(dot);
});
}
let stage = 0; // 0=start, 1=derivative shown, 2=gcd shown, 3=divided
let DP = null, G = null, SF = null;
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + cls; }
function render(){
document.getElementById('expr-p').textContent = fmtPoly(P);
document.getElementById('row-dp').classList.toggle('active', stage >= 1);
document.getElementById('row-gcd').classList.toggle('active', stage >= 2);
document.getElementById('row-sf').classList.toggle('active', stage >= 3);
document.getElementById('expr-dp').textContent = DP ? fmtPoly(DP) : '—';
document.getElementById('expr-gcd').textContent = G ? fmtPoly(G) : '—';
document.getElementById('expr-sf').textContent = SF ? fmtPoly(SF) : '—';
drawAxis('axis-before', ROOTS);
drawAxis('axis-after', stage >= 3 ? SF_ROOTS : []);
document.getElementById('step-deriv').disabled = stage >= 1;
document.getElementById('step-gcd').disabled = stage < 1 || stage >= 2;
document.getElementById('step-divide').disabled = stage < 2 || stage >= 3;
}
function stepDeriv(){
DP = deriv(P);
stage = 1;
setStatus('Derivative computed. Now take the gcd of p(x) and p’(x).', 'info');
render();
}
function stepGcd(){
G = gcd(P, DP);
stage = 2;
setStatus(i18nFmt('gcd(p, p’) has degree %deg% — that is exactly the repeated part of p. Now divide it out.', { deg: G.length - 1 }), 'info');
render();
}
function stepDivide(){
SF = divMod(P, G).quot;
stage = 3;
setStatus('Done. Each root now appears exactly once — this is the squarefree part of p(x).', 'ok');
console.log('Squarefree part obtained via gcd(p, p’), no root-finding used.');
render();
}
function resetAll(){
DP = null; G = null; SF = null; stage = 0;
setStatus('Press "Differentiate" to begin.', '');
render();
}
document.getElementById('step-deriv').addEventListener('click', stepDeriv);
document.getElementById('step-gcd').addEventListener('click', stepGcd);
document.getElementById('step-divide').addEventListener('click', stepDivide);
document.getElementById('reset').addEventListener('click', resetAll);
render();
<p class="hint">p(x) = (x-1)²(x+2)³ has a double root at x = 1 and a triple root at x = -2. Step through the pipeline to strip the repeats using nothing but a derivative, a gcd, and a division.</p>
<div class="poly-row"><span class="poly-label">p(x) =</span><span class="poly-expr" id="expr-p"></span></div>
<div class="poly-row" id="row-dp"><span class="poly-label">p’(x) =</span><span class="poly-expr" id="expr-dp">—</span></div>
<div class="poly-row" id="row-gcd"><span class="poly-label">gcd =</span><span class="poly-expr" id="expr-gcd">—</span></div>
<div class="poly-row" id="row-sf"><span class="poly-label">squarefree =</span><span class="poly-expr" id="expr-sf">—</span></div>
<div class="axis-wrap">
<div class="axis" id="axis-before"></div>
<div class="axis-title">Roots of p(x), with multiplicity</div>
<div class="axis" id="axis-after"></div>
<div class="axis-title">Roots after dividing out the gcd</div>
</div>
<div class="status" id="status">Press "Differentiate" to begin.</div>
<div class="btns">
<button id="step-deriv" type="button">Differentiate</button>
<button id="step-gcd" type="button">Compute gcd</button>
<button id="step-divide" type="button">Divide it out</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; }
.poly-row { display: flex; gap: .5rem; align-items: baseline; font: 600 15px ui-monospace, monospace;
margin: .15rem 0; opacity: .35; transition: opacity .25s; }
.poly-row.active { opacity: 1; }
.poly-label { min-width: 8ch; color: #1d3557; font-family: system-ui, sans-serif; font-weight: 700; font-size: 13px; }
.poly-expr { color: #222; }
.axis-wrap { margin: .9rem 0 .6rem; }
.axis-title { font-size: .78rem; color: #555; margin: .15rem 0 .5rem; }
.axis { position: relative; height: 34px; border-bottom: 2px solid #cdd9e3; margin: 0 6px; }
.tick { position: absolute; bottom: -3px; width: 2px; height: 8px; background: #adb1b8; }
.root-dot { position: absolute; bottom: -11px; width: 22px; height: 22px; border-radius: 50%;
display: flex; align-items: center; justify-content: center; transform: translateX(-50%);
font: 700 11px ui-monospace, monospace; color: #fff; background: #e63946; border: 2px solid #c92f3c;
transition: left .4s ease; }
.root-dot.mult1 { background: #0a7d33; border-color: #086e2c; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.info { color: #1d3557; }
.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:disabled { opacity: .45; cursor: default; }
button.ghost { background: #fff; color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Squarefree factorization demo: strip repeated roots from a fixed polynomial
// using only its derivative and a polynomial gcd — no root-finding involved.
// p(x) = (x-1)^2 (x+2)^3, low-to-high coefficients
const P = [8, -4, -10, 1, 4, 1];
// Drop trailing near-zero high-degree coefficients so degree stays accurate.
function trim(p){ p = p.slice(); while (p.length > 1 && Math.abs(p[p.length-1]) < 1e-9) p.pop(); return p; }
// Derivative of p(x) = sum a_i x^i is sum i*a_i x^(i-1), coefficient-wise.
function deriv(p){ const d = []; for (let i=1;i<p.length;i++) d.push(i*p[i]); return trim(d.length?d:[0]); }
// Polynomial long division, returning quotient and remainder (low-to-high coeffs).
function divMod(a,b){
a = trim(a.slice()); b = trim(b.slice());
let rem = a.slice();
const degb = b.length-1;
const dega = a.length-1;
const quot = new Array(Math.max(dega-degb+1,0)).fill(0);
while (true){
rem = trim(rem);
const degr = rem.length-1;
if (degr < degb || (rem.length===1 && Math.abs(rem[0])<1e-9)) break;
const coef = rem[degr]/b[degb];
const shift = degr-degb;
quot[shift] = coef;
for (let i=0;i<=degb;i++) rem[shift+i] -= coef*b[i];
}
return { quot: trim(quot), rem: trim(rem) };
}
// Euclidean algorithm for polynomials, normalized to a monic result.
function gcd(a,b){
a = trim(a.slice()); b = trim(b.slice());
while (!(b.length===1 && Math.abs(b[0])<1e-9)){
const { rem } = divMod(a,b);
a = b; b = trim(rem);
}
const lc = a[a.length-1];
return a.map(x => Math.round((x/lc)*1e6)/1e6);
}
// Render a coefficient array (low-to-high) as a human-readable polynomial string.
function fmtPoly(p){
const deg = p.length-1;
const terms = [];
for (let i=deg;i>=0;i--){
const c = p[i];
if (Math.abs(c) < 1e-9) continue;
const ac = Math.abs(c);
let t;
if (i===0) t = String(ac);
else if (i===1) t = (ac===1?'':String(ac)) + 'x';
else t = (ac===1?'':String(ac)) + 'x^' + i;
terms.push((c<0 ? '− ' : (terms.length ? '+ ' : '')) + t);
}
return terms.join(' ') || '0';
}
// Known roots of P and their multiplicities, used only to draw the number line.
// The two real roots of P and their multiplicity in P, hardcoded because
// this demo is about *separating* known multiplicities, not root-finding.
const ROOTS = [ { x: -2, mult: 3 }, { x: 1, mult: 2 } ];
const SF_ROOTS = [ { x: -2, mult: 1 }, { x: 1, mult: 1 } ];
function drawAxis(elId, roots){
const el = document.getElementById(elId);
el.innerHTML = '';
const min = -4, max = 3;
const toPct = x => ((x - min) / (max - min)) * 100;
for (let t = Math.ceil(min); t <= Math.floor(max); t++){
const tick = document.createElement('div');
tick.className = 'tick';
tick.style.left = toPct(t) + '%';
el.appendChild(tick);
}
roots.forEach(r => {
const dot = document.createElement('div');
dot.className = 'root-dot' + (r.mult === 1 ? ' mult1' : '');
dot.style.left = toPct(r.x) + '%';
dot.textContent = r.mult > 1 ? ('×' + r.mult) : '1';
dot.title = i18nFmt('root x = %x%, multiplicity %m%', { x: r.x, m: r.mult });
el.appendChild(dot);
});
}
let stage = 0; // 0=start, 1=derivative shown, 2=gcd shown, 3=divided
let DP = null, G = null, SF = null;
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + cls; }
function render(){
document.getElementById('expr-p').textContent = fmtPoly(P);
document.getElementById('row-dp').classList.toggle('active', stage >= 1);
document.getElementById('row-gcd').classList.toggle('active', stage >= 2);
document.getElementById('row-sf').classList.toggle('active', stage >= 3);
document.getElementById('expr-dp').textContent = DP ? fmtPoly(DP) : '—';
document.getElementById('expr-gcd').textContent = G ? fmtPoly(G) : '—';
document.getElementById('expr-sf').textContent = SF ? fmtPoly(SF) : '—';
drawAxis('axis-before', ROOTS);
drawAxis('axis-after', stage >= 3 ? SF_ROOTS : []);
document.getElementById('step-deriv').disabled = stage >= 1;
document.getElementById('step-gcd').disabled = stage < 1 || stage >= 2;
document.getElementById('step-divide').disabled = stage < 2 || stage >= 3;
}
function stepDeriv(){
DP = deriv(P);
stage = 1;
setStatus('Derivative computed. Now take the gcd of p(x) and p’(x).', 'info');
render();
}
function stepGcd(){
G = gcd(P, DP);
stage = 2;
setStatus(i18nFmt('gcd(p, p’) has degree %deg% — that is exactly the repeated part of p. Now divide it out.', { deg: G.length - 1 }), 'info');
render();
}
function stepDivide(){
SF = divMod(P, G).quot;
stage = 3;
setStatus('Done. Each root now appears exactly once — this is the squarefree part of p(x).', 'ok');
console.log('Squarefree part obtained via gcd(p, p’), no root-finding used.');
render();
}
function resetAll(){
DP = null; G = null; SF = null; stage = 0;
setStatus('Press "Differentiate" to begin.', '');
render();
}
document.getElementById('step-deriv').addEventListener('click', stepDeriv);
document.getElementById('step-gcd').addEventListener('click', stepGcd);
document.getElementById('step-divide').addEventListener('click', stepDivide);
document.getElementById('reset').addEventListener('click', resetAll);
render();
Watch the root plot as you go. The starting polynomial shows only two distinct dots, doubled and tripled up. After dividing out the gcd, the result is x2+x−2=(x−1)(x+2) — the same two roots, but now each appears exactly once. Nothing was solved for; the repeats simply fell out of arithmetic on coefficients.
Comments
Loading comments...