Skip to content
The Lumenary

Math laboratory

Synthetic division and the remainder theorem

Divides polynomials by x - a with exact fractions (synthetic division), confirms on 2,000 random cases that the remainder is p(a), and finds all rational roots of 2x^3 - 3x^2 - 11x + 6 with the rational root test. The random check is evidence for the cases tried; the lesson's proof covers all.

Back to the labExact computationExperimental evidence
Synthetic division and the remainder theoremPythonExact computationExperimental evidenceComputationally tested
"""Synthetic division, the remainder theorem and the rational root test.

Divides polynomials by x - a with exact rational arithmetic (synthetic
division, i.e. Horner's scheme), checks on thousands of random cases that the
remainder equals p(a), and uses the rational root test to factorise a cubic
completely. The random check is experimental evidence; the lesson's proof of
the remainder theorem covers every polynomial and every number a.
"""

import random
from fractions import Fraction

def synthetic_division(coeffs, a):
    """Divide p by (x - a). ``coeffs`` lists p's coefficients, highest first.

    Returns (quotient coefficients, remainder), computed exactly.
    """
    if not coeffs:
        raise ValueError("p must have at least one coefficient")
    running = []
    value = Fraction(0)
    for c in coeffs:
        value = value * a + c
        running.append(value)
    return running[:-1], running[-1]

def evaluate(coeffs, x):
    """p(x) computed term by term (not by Horner), for an independent check."""
    n = len(coeffs) - 1
    return sum(Fraction(c) * Fraction(x) ** (n - i) for i, c in enumerate(coeffs))

def divisors(n):
    """Positive divisors of the non-zero integer n."""
    n = abs(n)
    if n == 0:
        raise ValueError("0 has infinitely many divisors")
    return [d for d in range(1, n + 1) if n % d == 0]

def rational_root_candidates(coeffs):
    """All +-p/q with p | constant term and q | leading coefficient (sorted)."""
    lead, const = coeffs[0], coeffs[-1]
    cands = {s * Fraction(p, q) for p in divisors(const)
             for q in divisors(lead) for s in (1, -1)}
    return sorted(cands)

def rational_roots(coeffs):
    """The candidates of the rational root test that really are roots."""
    return [r for r in rational_root_candidates(coeffs)
            if evaluate(coeffs, r) == 0]

def show(coeffs):
    """Readable form of a polynomial, e.g. 2x^2 + 3x - 2."""
    n = len(coeffs) - 1
    terms = []
    for i, c in enumerate(coeffs):
        if c == 0:
            continue
        k = n - i
        mag = abs(c)
        body = "" if (mag == 1 and k > 0) else str(mag)
        body += "" if k == 0 else ("x" if k == 1 else f"x^{k}")
        terms.append(("- " if c < 0 else "+ ") + body)
    if not terms:
        return "0"
    text = " ".join(terms)
    return text[2:] if text.startswith("+ ") else "-" + text[2:]

def random_check(trials, seed=2024):
    """Compare the synthetic-division remainder with p(a) on random cases.

    Returns the number of disagreements (the theorem predicts 0).
    """
    rng = random.Random(seed)
    failures = 0
    for _ in range(trials):
        coeffs = [rng.randint(-9, 9) for _ in range(rng.randint(1, 7))]
        a = Fraction(rng.randint(-20, 20), rng.randint(1, 6))
        _, remainder = synthetic_division(coeffs, a)
        failures += remainder != evaluate(coeffs, a)
    return failures

def main():
    p = [2, -3, -11, 6]
    print(f"p(x) = {show(p)}")
    q, r = synthetic_division(p, 3)
    print(f"divide by x - 3:  quotient {show(q)},  remainder {r}")
    print(f"p(3) = {evaluate(p, 3)}, so x - 3 is a factor (factor theorem)")
    cands = rational_root_candidates(p)
    print(f"rational root test: {len(cands)} candidates +-p/q,")
    print("  p | 6 and q | 2:", ", ".join(str(c) for c in cands if c > 0))
    roots = rational_roots(p)
    print("  roots found:", ", ".join(str(x) for x in roots))
    print("  so p(x) = (x + 2)(2x - 1)(x - 3)")
    print()
    _, r2 = synthetic_division([1, 0, 0, -2], Fraction(5, 4))
    print(f"x^3 - 2 divided by x - 5/4: remainder {r2} = p(5/4)")
    found = rational_roots([1, 0, 0, -2])
    print(f"x^3 - 2: candidates +-1, +-2; rational roots: {found or 'none'}")
    print("  so the cube root of 2 is irrational")
    trials = 2000
    print(f"random check, {trials} polynomials and points a:"
          f" {random_check(trials)} disagreements")
    print()
    print("Epistemic status: the worked divisions and the root search are exact")
    print("computations with fractions. The random check is experimental evidence")
    print("for 2,000 cases only; the remainder theorem's proof in the lesson is")
    print("what guarantees remainder = p(a) for every polynomial and every a.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Used in