Introduction

Take two polynomials, say f(x)=x2−5x+6f(x) = x^2 - 5x + 6 and g(x)=x2−8x+15g(x) = x^2 - 8x + 15. Do they share a root? You could factor both: f(x)=(x−2)(x−3)f(x) = (x-2)(x-3) and g(x)=(x−3)(x−5)g(x) = (x-3)(x-5), and spot the common factor (x−3)(x-3). That works fine for small, tidy examples.

But factoring is not always an option. What if the coefficients are symbolic, or the polynomials have degree 20? You need a way to answer "do they share a root?" using nothing but the coefficients themselves — arithmetic, not guesswork.

That is exactly what the resultant does. Build a certain matrix from the coefficients of ff and gg — the Sylvester matrix — take its determinant, and the result is a single number: the resultant Res⁡(f,g)\operatorname{Res}(f,g). It is zero if and only if ff and gg share a root (over the complex numbers). No factoring, no root-finding — just arithmetic on the coefficients.

Try It

Fix g(x)=x2−5x+6=(x−2)(x−3)g(x) = x^2 - 5x + 6 = (x-2)(x-3). Now take a family f(x)=x2+bx+2bf(x) = x^2 + bx + 2b and slide bb. For most values of bb, ff and gg have no root in common. But the resultant Res⁡(f,g)\operatorname{Res}(f,g), computed straight from the coefficients as a 4×44\times4 determinant, tells you exactly when a shared root appears — without ever solving for the roots themselves.

<p class="hint">Blue is f(x) = x&sup2; + bx + 2b, red is the fixed g(x) = x&sup2; - 5x + 6. The chart on the right plots the resultant Res(f,g) as you change b — pure arithmetic on the coefficients, no root-finding.</p>
<div class="controls">
  <label for="bslider">b =</label>
  <input type="range" id="bslider" min="-3" max="1" step="0.01" value="0.3">
  <span id="bval" class="bval">b = 0.30</span>
</div>
<div class="panels">
  <div class="panel">
    <canvas id="curves" width="320" height="220"></canvas>
    <div class="caption">f (blue) and g (red)</div>
  </div>
  <div class="panel">
    <canvas id="reschart" width="320" height="220"></canvas>
    <div class="caption">Resultant Res(f,g) vs. b</div>
  </div>
</div>
<div class="status" id="status">Drag the slider to change b.</div>
<div class="btns">
  <button id="snap1" type="button">Snap to shared root x=2</button>
  <button id="snap2" type="button">Snap to shared root x=3</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; }
.controls { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; flex-wrap: wrap; }
.controls label { font-size: .85rem; font-weight: 600; color: #1d3557; }
.controls input[type="range"] { flex: 1; min-width: 160px; accent-color: #1d3557; }
.bval { font: 700 14px ui-monospace, monospace; color: #1d3557; min-width: 80px; }
.panels { display: flex; gap: .8rem; flex-wrap: wrap; margin-bottom: .6rem; }
.panel { flex: 1; min-width: 260px; }
canvas { width: 100%; max-width: 320px; height: auto; background: #f7f9fb; border: 1px solid #cdd9e3; border-radius: 8px; display: block; }
.caption { font-size: .78rem; color: #555; text-align: center; margin-top: .3rem; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #444; }
.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.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Demo: f(x) = x^2 + b*x + 2b (slider controls b), g(x) = x^2 - 5x + 6 fixed.
// g factors as (x-2)(x-3), so its roots are always x=2 and x=3.
// The resultant Res(f,g) is the determinant of their 4x4 Sylvester matrix.
const G = [1, -5, 6]; // coefficients of the fixed polynomial g(x) = x^2 - 5x + 6
const ROOT_TOL = 0.03;

// coefficients of f(x) = x^2 + b*x + 2b for the current slider value
function fCoeffs(b){ return [1, b, 2 * b]; }

// generic 4x4 determinant via cofactor expansion along the first row
function det3(a){
  return a[0][0]*(a[1][1]*a[2][2]-a[1][2]*a[2][1])
       - a[0][1]*(a[1][0]*a[2][2]-a[1][2]*a[2][0])
       + a[0][2]*(a[1][0]*a[2][1]-a[1][1]*a[2][0]);
}
function det4(m){
  let result = 0;
  for(let col=0; col<4; col++){
    const minor = [];
    for(let r=1;r<4;r++){ const row=[]; for(let c=0;c<4;c++) if(c!==col) row.push(m[r][c]); minor.push(row); }
    const sign = (col % 2 === 0) ? 1 : -1;
    result += sign * m[0][col] * det3(minor);
  }
  return result;
}
// build the 4x4 Sylvester matrix from f and g's coefficients and take its determinant
function resultant(b){
  const [a0,a1,a2] = fCoeffs(b);
  const [b0,b1,b2] = G;
  const M = [
    [a0,a1,a2,0],
    [0,a0,a1,a2],
    [b0,b1,b2,0],
    [0,b0,b1,b2]
  ];
  return det4(M);
}

function fVal(b,x){ const [a0,a1,a2]=fCoeffs(b); return a0*x*x + a1*x + a2; }
function gVal(x){ const [b0,b1,b2]=G; return b0*x*x + b1*x + b2; }

const slider = document.getElementById('bslider');
const bvalEl = document.getElementById('bval');
const statusEl = document.getElementById('status');
const curvesCv = document.getElementById('curves');
const resCv = document.getElementById('reschart');
const cctx = curvesCv.getContext('2d');
const rctx = resCv.getContext('2d');

// the two values of b where f and g share a root (solved analytically: f(2)=0 at b=-1, f(3)=0 at b=-1.8)
const ZEROS = [-1, -1.8]; // verified offline: resultant(-1) = 0 and resultant(-1.8) = 0

function drawCurves(b){
  const W = curvesCv.width, H = curvesCv.height;
  cctx.clearRect(0,0,W,H);
  const xmin=-1, xmax=7, ymin=-6, ymax=14;
  const sx = x => (x - xmin) / (xmax - xmin) * W;
  const sy = y => H - (y - ymin) / (ymax - ymin) * H;
  // draw x and y axes
  cctx.strokeStyle = '#cdd9e3'; cctx.lineWidth = 1;
  cctx.beginPath(); cctx.moveTo(0, sy(0)); cctx.lineTo(W, sy(0)); cctx.stroke();
  cctx.beginPath(); cctx.moveTo(sx(0), 0); cctx.lineTo(sx(0), H); cctx.stroke();
  // g(x) fixed
  cctx.strokeStyle = '#e63946'; cctx.lineWidth = 2;
  cctx.beginPath();
  for(let px=0; px<=W; px++){ const x = xmin + (px/W)*(xmax-xmin); const y = gVal(x);
    const py = sy(y); if(px===0) cctx.moveTo(px,py); else cctx.lineTo(px,py); }
  cctx.stroke();
  // f(x;b)
  cctx.strokeStyle = '#1d3557'; cctx.lineWidth = 2;
  cctx.beginPath();
  for(let px=0; px<=W; px++){ const x = xmin + (px/W)*(xmax-xmin); const y = fVal(b,x);
    const py = sy(y); if(px===0) cctx.moveTo(px,py); else cctx.lineTo(px,py); }
  cctx.stroke();
  // highlight the touching point in green when b is within tolerance of a zero crossing
  const nearZero = ZEROS.some(z => Math.abs(b - z) < ROOT_TOL);
  if(nearZero){
    const z = ZEROS.find(zz => Math.abs(b - zz) < ROOT_TOL);
    const rx = z === -1 ? 2 : 3;
    cctx.fillStyle = '#0a7d33';
    cctx.beginPath(); cctx.arc(sx(rx), sy(gVal(rx)), 6, 0, Math.PI*2); cctx.fill();
  }
}

function drawResChart(b){
  const W = resCv.width, H = resCv.height;
  rctx.clearRect(0,0,W,H);
  const bmin=-3, bmax=1, rmin=-20, rmax=70;
  const sx = bb => (bb - bmin) / (bmax - bmin) * W;
  const sy = rr => H - (rr - rmin) / (rmax - rmin) * H;
  rctx.strokeStyle = '#cdd9e3'; rctx.lineWidth = 1;
  rctx.beginPath(); rctx.moveTo(0, sy(0)); rctx.lineTo(W, sy(0)); rctx.stroke();
  rctx.strokeStyle = '#457b9d'; rctx.lineWidth = 2;
  rctx.beginPath();
  for(let px=0; px<=W; px++){ const bb = bmin + (px/W)*(bmax-bmin); const rr = resultant(bb);
    const py = sy(Math.max(rmin, Math.min(rmax, rr))); if(px===0) rctx.moveTo(px,py); else rctx.lineTo(px,py); }
  rctx.stroke();
  // mark the true zero crossings of the resultant curve in green
  rctx.fillStyle = '#0a7d33';
  ZEROS.forEach(z => { rctx.beginPath(); rctx.arc(sx(z), sy(0), 5, 0, Math.PI*2); rctx.fill(); });
  // current b marker
  rctx.fillStyle = '#e63946';
  rctx.beginPath(); rctx.arc(sx(b), sy(Math.max(rmin, Math.min(rmax, resultant(b)))), 4, 0, Math.PI*2); rctx.fill();
}

function render(){
  const b = parseFloat(slider.value);
  bvalEl.textContent = fmt('b = %b%', { b: b.toFixed(2) });
  drawCurves(b);
  drawResChart(b);
  const r = resultant(b);
  const nearZero = ZEROS.some(z => Math.abs(b - z) < ROOT_TOL);
  if(nearZero){
    statusEl.textContent = fmt('Resultant = %r% (~0) — the curves touch at a common root!', { r: r.toFixed(2) });
    statusEl.className = 'status ok';
  } else {
    statusEl.textContent = fmt('Resultant = %r% (nonzero) — no shared root right now.', { r: r.toFixed(2) });
    statusEl.className = 'status bad';
  }
}

slider.addEventListener('input', render);
document.getElementById('snap1').addEventListener('click', () => { slider.value = '-1'; render(); });
document.getElementById('snap2').addEventListener('click', () => { slider.value = '-1.8'; render(); });
document.getElementById('reset').addEventListener('click', () => { slider.value = '0.3'; render(); });
render();
Result
Console

Drag the slider slowly. Watch the resultant curve and the two parabolas at the same time: every time the resultant crosses zero, the two curves are touching at a common point. The determinant "feels" the tangency algebraically, before you ever look at the picture.

The Real Complexity

The construction is completely mechanical. For f(x)=a0xm+a1xm−1+⋯+amf(x) = a_0 x^m + a_1 x^{m-1} + \dots + a_m and g(x)=b0xn+b1xn−1+⋯+bng(x) = b_0 x^n + b_1 x^{n-1} + \dots + b_n, the Sylvester matrix is an (m+n)×(m+n)(m+n)\times(m+n) matrix built from shifted copies of the coefficients of ff (n rows) stacked on shifted copies of the coefficients of gg (m rows). Its determinant is the resultant:

Res⁡(f,g)=det⁡S(f,g)\operatorname{Res}(f,g) = \det S(f,g)

  • Why it works: Res⁡(f,g)=0\operatorname{Res}(f,g) = 0 exactly when ff and gg have a nontrivial common factor — equivalently, a common root in the complex numbers (or a shared root "at infinity" if leading coefficients vanish). This falls out of linear algebra: the rows of the Sylvester matrix encode the coefficients of xif(x)x^i f(x) and xjg(x)x^j g(x), and a common root forces a linear dependency among them, which is exactly what a zero determinant means.
  • It is a polynomial in the coefficients. Res⁡(f,g)\operatorname{Res}(f,g) is itself a polynomial expression in the aia_i and bjb_j — no radicals, no case-splitting. That makes it exact and symbolic, unlike numerically hunting for roots.
  • It generalizes. Setting g=f′g = f' (the derivative) gives the discriminant, which detects repeated roots of a single polynomial — the same determinant machine, one polynomial away from the resultant.
  • Cost: computing an (m+n)×(m+n)(m+n)\times(m+n) determinant directly costs O((m+n)3)O((m+n)^3) with standard methods, though faster resultant-specific algorithms exist. It is polynomial-time, symbolic, and exact — a very different profile from problems like P vs NP, where no such efficient exact recipe is known.

Where It Matters

"Eliminate a variable between two equations" sounds narrow, but it is one of the most-needed operations in computational mathematics:

  • Elimination theory: given two polynomial equations in xx and yy, treating them as polynomials in xx with coefficients in yy and taking the resultant produces a single equation in yy alone — describing exactly where two curves intersect.
  • Computer-aided design and robotics: solving for the configurations where two algebraic curves or surfaces meet (collision detection, linkage kinematics) routinely reduces to a resultant computation.
  • Computer algebra systems: resultants underlie variable-elimination routines that complement Gröbner bases for solving systems of polynomial equations exactly.
  • Number theory and cryptanalysis: resultants help detect common factors between polynomials over finite fields, relevant to constructing and attacking certain cryptographic schemes.

Any time "do these two algebraic conditions simultaneously hold" needs a symbolic, no-approximation answer, the resultant is often the tool that gets there fastest.

Conclusion

The resultant turns a question that looks like it needs solving — "do these two polynomials share a root?" — into a question about a determinant, answerable by pure arithmetic on the coefficients. Zero means yes, they touch; nonzero means no, they never do.

It is a small piece of a bigger idea: sometimes the fastest way to answer a hard-looking question is not to solve the problem in front of you, but to build the right algebraic object — a matrix, a determinant, an invariant — that already knows the answer. The same spirit shows up throughout Gröbner bases and the rest of computational algebraic geometry.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/resultant-polynomials/Content licensed under CC BY-NC 4.0.