DEV Community

Alex Towell
Alex Towell

Posted on • Originally published at metafunctor.com

Polynomials as Euclidean Domains

The same GCD algorithm works for integers and polynomials. That's not a coincidence. It's because both are Euclidean domains.

The Observation

// For integers: gcd(48, 18) = 6
// For polynomials: gcd(x^3 - 1, x^2 - 1) = x - 1

// Same algorithm, different types
template<euclidean_domain E>
E gcd(E a, E b) {
    while (b != E(0)) {
        a = std::exchange(b, a % b);
    }
    return a;
}
Enter fullscreen mode Exit fullscreen mode

That template compiles and works correctly for both integers and polynomials. The reason it works is algebraic: both types support division with remainder, and the remainder is always "smaller" than the divisor in a well-defined sense.

Quick Start

#include <polynomials/polynomial.hpp>
#include <iostream>

using namespace poly;

int main() {
    // Create polynomial x^2 - 1 = (x-1)(x+1)
    auto p = polynomial<double>{-1, 0, 1};

    // Create polynomial x^3 - 1 = (x-1)(x^2+x+1)
    auto q = polynomial<double>{-1, 0, 0, 1};

    // GCD should be (x - 1)
    auto g = gcd(p, q);

    std::cout << "gcd(x^2-1, x^3-1) has degree " << g.degree() << "\n";  // 1

    // Find roots of x^2 - 1
    auto roots = find_roots(p, -10.0, 10.0);
    for (double r : roots) {
        std::cout << "Root: " << r << "\n";  // -1 and 1
    }
}
Enter fullscreen mode Exit fullscreen mode

API Reference

Creating Polynomials

// From dense coefficients (a[i] = coefficient of x^i)
polynomial<double> p{1, -2, 1};  // 1 - 2x + x^2

// Monomial: coefficient * x^degree
auto m = polynomial<double>::monomial(3.0, 4);  // 3x^4

// The variable x
auto x = polynomial<double>::x();  // x

// Constant
polynomial<double> c{5.0};  // 5
Enter fullscreen mode Exit fullscreen mode

Arithmetic

auto sum = p + q;
auto diff = p - q;
auto prod = p * q;
auto [quot, rem] = divmod(p, q);  // Division with remainder
auto quot_only = p / q;
auto rem_only = p % q;
Enter fullscreen mode Exit fullscreen mode

GCD and Related

auto g = gcd(p, q);                    // Greatest common divisor
auto [g, s, t] = extended_gcd(p, q);   // Bezout: g = p*s + q*t
auto l = lcm(p, q);                    // Least common multiple
bool d = divides(p, q);                // Does p divide q?
Enter fullscreen mode Exit fullscreen mode

Evaluation and Calculus

double val = evaluate(p, x);           // p(x)
auto dp = derivative(p);               // p'(x)
auto integral = antiderivative(p);     // integral of p
auto roots = find_roots(p, -10, 10);   // All real roots in interval
auto crit = stationary_points(p, -10, 10);  // Where p'(x) = 0
Enter fullscreen mode Exit fullscreen mode

The Euclidean Domain Structure

What makes this work is shared algebraic structure. A Euclidean domain has a norm function and a division algorithm where the remainder is always smaller than the divisor:

Property Integers Polynomials
Norm abs(n) degree(p)
Division a = b*q + r, abs(r) < abs(b) a = b*q + r, deg(r) < deg(b)
GCD gcd(48, 18) = 6 gcd(x^2-1, x-1) = x-1

The GCD algorithm doesn't care which type it's operating on. It only needs the division-with-remainder property. Stepanov's whole point is exactly this: algorithms arise from algebraic structure. When you recognize that polynomials and integers share the same abstract structure, you immediately get:

  • GCD
  • Extended GCD (Bezout's identity)
  • Unique factorization
  • The Chinese Remainder Theorem

One structure, many types, same algorithms.

Sparse Representation

Polynomials are stored as sorted (degree, coefficient) pairs. For a polynomial like x^1000 + 1, this means 2 stored terms instead of 1001.

Why This Matters

I keep coming back to this example because it captures something important about programming. The GCD algorithm is over two thousand years old. Euclid described it for integers. It works for polynomials, Gaussian integers, and any other Euclidean domain without changing a single line. The algorithm's correctness comes from the structure, not from the type.

That's what generic programming is about. Not templates as a syntax feature. Templates as a way to express that a single algorithm works for an entire family of types, and to have the compiler enforce it.

Top comments (0)