Polynomial<T> — Polynomial Template

Overview

Polynomial<T> is a polynomial template class in calx. The coefficient type T can be double, Rational, Complex<double>, or any type satisfying the PolynomialCoeff concept.

$$p(x) = c_0 + c_1 x + c_2 x^2 + \cdots + c_n x^n$$

Coefficients are stored in ascending order: coeffs_[i] is the coefficient of $x^i$.

  • Any coefficient type — from double to Rational (exact) or Complex<T>
  • Horner evaluation — $O(n)$ evaluation of $p(x)$
  • Polynomial division — quotient and remainder (divmod)
  • Euclidean GCD — greatest common divisor of polynomials
  • Differentiation / Integrationderivative(), integral()
  • Orthogonal polynomials — Chebyshev, Legendre, Hermite, Laguerre, Bessel, Jacobi, Gegenbauer
  • C++23 concepts — type-constrained via PolynomialCoeff

Constructors

SignatureDescription
Polynomial()Zero polynomial
Polynomial(const T& c)Constant polynomial
Polynomial(std::vector<T> coeffs)From coefficient vector (ascending order)
Polynomial(std::initializer_list<T> il)From initializer list
Polynomial<double> p = {1.0, 2.0, 3.0};  // 1 + 2x + 3x^2
Polynomial<Rational> q = {Rational(1,2), Rational(3,4)};  // 1/2 + 3/4 x

Accessors

Member FunctionDescription
degree()Degree ($-1$ for zero polynomial)
isZero()Whether it is the zero polynomial
leadingCoefficient()Leading coefficient $c_n$
constantTerm()Constant term $c_0$
operator[](i) constCoefficient of $x^i$ (returns $0$ if out of range)
operator[](i)Reference to coefficient of $x^i$ (auto-extends if out of range)
coefficients()Reference to coefficient vector

Arithmetic Operators

OperatorDescription
p + qAddition
p - qSubtraction
p * qMultiplication ($O(nm)$)
p / qQuotient (polynomial division). Returns RationalFunction in rational mode
p % qRemainder (polynomial division)
p.pow(n)$p^n$ (binary exponentiation)

Scalar operations: p + c, c * p, p / c. Compound assignment: +=, -=, *=, /=.

Division / Modulo

auto [q, r] = p.divmod(d);  // p = d * q + r, deg(r) < deg(d)
FunctionDescription
p.divmod(d)Returns quotient q and remainder r via structured binding
p.quo(d)Quotient only (always returns Polynomial, mode-independent)
p / dDefault: quotient only. Returns RationalFunction in rational mode
p % dRemainder only

Division Mode — Switching operator/ Behavior

The return type of Polynomial / Polynomial can be switched at compile time via a macro.

Modep / q returnsUse case
Default Polynomial<T> (quotient) Polynomial ring operations. Same semantics as int / int
CALX_POLY_DIV_RATIONAL RationalFunction<T> Lossless division. Suitable for symbolic computation

Method 1: Define in source code

// Define before including RationalFunction.hpp
#define CALX_POLY_DIV_RATIONAL
#include <math/core/RationalFunction.hpp>

Polynomial<double> p = {1, 0, 1};  // x^2 + 1
Polynomial<double> q = {1, 1};     // x + 1

auto rf = p / q;       // RationalFunction<double>: (x^2+1)/(x+1)
auto quot = p.quo(q);  // Polynomial<double>: x - 1 (when quotient is needed)

Method 2: Configure via CMake

# CMakeLists.txt
option(CALX_POLY_DIV_RATIONAL
       "Polynomial / Polynomial returns RationalFunction" OFF)

if(CALX_POLY_DIV_RATIONAL)
    add_compile_definitions(CALX_POLY_DIV_RATIONAL)
endif()

This setting switches operator/ behavior project-wide. quo() and divmod() always perform polynomial division regardless of mode.

Evaluation

FunctionDescription
p(x)Evaluate $p(x)$ using Horner's method. $O(n)$
p.evaluateEstrin(x)Estrin's method (better parallelism)

The argument type may differ from T (e.g., evaluate a Polynomial<double> at Complex<double>).

Passing a polynomial as the argument performs polynomial composition $p(q(x))$, equivalent to compose(p, q).

Polynomial<double> p({1, 0, 1});  // 1 + x^2
Polynomial<double> q({0, 1, 1});  // x + x^2
auto r = p(q);  // p(q(x)) = 1 + (x + x^2)^2 = 1 + x^2 + 2x^3 + x^4

The composed polynomial has degree $\deg(p(q)) = \deg(p) \cdot \deg(q)$.

Calculus

FunctionDescription
p.derivative()$p'(x)$
p.derivative(n)$n$-th derivative $p^{(n)}(x)$
p.integral()Indefinite integral $\int p(x)\,dx$ (constant term = 0)
definiteIntegral(p, a, b)Definite integral $\int_a^b p(x)\,dx$

Algebraic Operations

FunctionDescription
gcd(p, q)Euclidean GCD
approximateGCD(p, q, tol)Approximate GCD (for floating-point coefficients)
content(p)Content (GCD of all coefficients)
primitivePart(p)Primitive part $p / \text{content}(p)$
compose(p, q)Composition $p(q(x))$
monomial(c, n)Monomial $c \cdot x^n$
X<T>()Variable $x$ (degree-1 polynomial)

Factorization & Resultant

Exact factorization of integer polynomials, plus resultant and discriminant. Include math/core/Polynomial_factorization.hpp.

#include <math/core/Polynomial.hpp>
#include <math/core/Polynomial_factorization.hpp>
#include <math/core/FactoredInteger.hpp>

factorize() — Irreducible factorization

Factor an integer polynomial $f(x) \in \mathbb{Z}[x]$ into irreducible factors.

SignatureDescription
factorize(const Polynomial<T>& f)Returns $f = c \cdot \prod f_i^{e_i}$ as a FactoredInteger<Polynomial<T>>

Coefficient type T must be int or int64_t.

Pipeline:

  1. Content extraction — GCD of all coefficients is pulled out as a constant factor.
  2. Rational root theorem — search $f(b/a) = 0$ over $b \mid a_0$, $a \mid a_n$, remove all linear factors $(ax - b)$.
  3. Yun's square-free factorization — use $\gcd(f, f')$ to split $f = \prod f_i^i$ where each $f_i$ is square-free and pairwise coprime.
  4. Kronecker's method — for each square-free factor, interpolate trial factors from divisors of values at $\deg f / 2 + 1$ points.
// x^4 - 1 = (x - 1)(x + 1)(x^2 + 1)
Polynomial<int> f = {-1, 0, 0, 0, 1};
auto factors = factorize(f);

for (size_t i = 0; i < factors.size(); ++i) {
    std::cout << factors[i] << "^" << factors.exponent(i) << std::endl;
}
// (-1 + x)^1
// (1 + x)^1
// (1 + x^2)^1

The above is the core version (header-only, no linking required). Kronecker's method cuts off at 100,000 combinations, practical up to $\deg f \lesssim 10$.

Fast version (calx_factorize lib)

Link calx_factorize to automatically switch to a fast pipeline:

  1. $\mathrm{GF}(p)$ factorization — Cantor-Zassenhaus (DDF + EDF)
  2. Hensel lifting — linear + multi-factor binary-tree Hensel lift to $\mathbb{Z}[x]$
  3. Factor recombination — $r \leq 12$: Zassenhaus enumeration, $r > 12$: van Hoeij lattice (LLL)
VersionTypesLinkingPractical range
coreint, int64_tnone$\deg \lesssim 10$
proint, int64_t, Int, Rationalcalx_factorize$\deg \lesssim 100+$

Int delegates to int64_t when all coefficients fit in 64 bits. Rational clears denominators (LCM) and delegates to the Int path.

// CMake: target_link_libraries(your_target PRIVATE calx_factorize)

squareFreeFactorization() — Yun's algorithm

SignatureDescription
squareFreeFactorization(const Polynomial<T>& f)Returns $f = c \cdot \prod f_i^i$ (no irreducibility test)

Yun's algorithm (1976), implemented over subresultant PRS for $\gcd(f, f')$. Separates repeated factors with exponents; does not guarantee irreducibility.

Integer polynomial GCD and pseudo-remainder

FunctionDescription
intPolyGcd(a, b)GCD of integer polynomials via subresultant PRS; combines pseudo-remainder with primitive part reduction to avoid coefficient explosion
pseudoRemainder(f, g)Pseudo-remainder $\mathrm{lc}(g)^{\deg f - \deg g + 1} \cdot f \bmod g$; result has integer coefficients

Pseudo-remainder vs. ordinary remainder

The ordinary remainder assumes the coefficient ring is a field, so it does not work over the integers:

$$f = q \cdot g + r, \quad \deg r < \deg g$$

Example: dividing $f = x^2$ by $g = 2x + 1$ over the integers gives $q = \dfrac{x}{2} - \dfrac{1}{4}$, introducing fractions in the quotient (so neither quotient nor remainder exists in $\mathbb{Z}[x]$).

The pseudo-remainder avoids fractions by pre-multiplying by a power of the leading coefficient of $g$:

$$\mathrm{lc}(g)^{\deg f - \deg g + 1} \cdot f = q \cdot g + r, \quad \deg r < \deg g$$

Multiplying by $\mathrm{lc}(g)$ in advance keeps all coefficients in the ring when dividing by $g$.

For the same example: with $\mathrm{lc}(g)^{2-1+1} = 2^2 = 4$,

$$4 x^2 = (2x - 1)(2x + 1) + 1$$

so the pseudo-remainder is $r = 1$ — purely integer.

AspectOrdinary remainderPseudo-remainder
Required structureField ($\mathbb{Q}, \mathbb{R}$, …)Ring ($\mathbb{Z}$ is enough)
ResultExact, but contains fractionsScaled by $\mathrm{lc}(g)^k$
Use caseEuclidean GCD over a fieldsubresultant GCD and factorization in $\mathbb{Z}[x]$

In calx, pseudoRemainder() exists to support intPolyGcd() (integer polynomial GCD) — it is the building block of the subresultant PRS that computes GCDs while keeping all coefficients integer.

Resultant & discriminant

FunctionDescription
sylvesterMatrix(f, g)The $(m+n) \times (m+n)$ Sylvester matrix
resultant(f, g)Resultant $\mathrm{Res}(f, g) = \det(\mathrm{Sylvester}(f, g))$
discriminant(f)Discriminant $\mathrm{Disc}(f) = (-1)^{n(n-1)/2} \mathrm{Res}(f, f') / a_n$
// Discriminant of f(x) = x^2 - 2 is 4·1·2 = 8
Polynomial<int> f = {-2, 0, 1};
std::cout << discriminant(f) << std::endl;  // 8

A non-zero discriminant certifies that $f$ is square-free (no repeated roots).

Orthogonal Polynomials

Classical orthogonal polynomials generated by recurrence relations.

FunctionPolynomial
chebyshevT<T>(n)Chebyshev 1st kind $T_n(x)$
chebyshevU<T>(n)Chebyshev 2nd kind $U_n(x)$
legendre<T>(n)Legendre $P_n(x)$
hermite<T>(n)Hermite $H_n(x)$ (probabilist)
laguerre<T>(n)Laguerre $L_n(x)$
bessel<T>(n)Bessel polynomial $y_n(x)$
jacobi<T>(n, α, β)Jacobi $P_n^{(\alpha,\beta)}(x)$
gegenbauer<T>(n, λ)Gegenbauer $C_n^{(\lambda)}(x)$
auto T5 = chebyshevT<double>(5);
std::cout << T5 << std::endl;
// 5x - 20x^3 + 16x^5

Type Traits

TraitDescription
is_polynomial_v<T>true if T is Polynomial<U>

Example

#include <math/core/Polynomial.hpp>
#include <iostream>
using namespace calx;

int main() {
    // Construct polynomials
    Polynomial<double> p = {1.0, -3.0, 2.0};  // 1 - 3x + 2x^2
    Polynomial<double> q = {1.0, 1.0};         // 1 + x

    // Arithmetic
    std::cout << "p = " << p << std::endl;
    std::cout << "q = " << q << std::endl;
    std::cout << "p * q = " << p * q << std::endl;

    // Division
    auto [quot, rem] = p.divmod(q);
    std::cout << "p / q = " << quot << ", remainder = " << rem << std::endl;

    // Evaluation (Horner)
    std::cout << "p(2.0) = " << p(2.0) << std::endl;  // 3.0

    // Calculus
    std::cout << "p' = " << p.derivative() << std::endl;  // -3 + 4x
    std::cout << "integral(p) = " << p.integral() << std::endl;

    // GCD
    Polynomial<double> a = {-1.0, 0.0, 1.0};  // x^2 - 1 = (x-1)(x+1)
    Polynomial<double> b = {-1.0, 1.0};        // x - 1
    std::cout << "gcd(x^2-1, x-1) = " << gcd(a, b) << std::endl;

    // Orthogonal polynomials
    auto L3 = legendre<double>(3);
    std::cout << "P_3(x) = " << L3 << std::endl;
}

Related Mathematical Background

The following articles explain the mathematical concepts underlying the Polynomial class.