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.
Coefficients are stored in ascending order: coeffs_[i] is the coefficient of $x^i$.
- Any coefficient type — from
doubletoRational(exact) orComplex<T> - Horner evaluation — $O(n)$ evaluation of $p(x)$
- Polynomial division — quotient and remainder (divmod)
- Euclidean GCD — greatest common divisor of polynomials
- Differentiation / Integration —
derivative(),integral() - Orthogonal polynomials — Chebyshev, Legendre, Hermite, Laguerre, Bessel, Jacobi, Gegenbauer
- C++23 concepts — type-constrained via
PolynomialCoeff
Build
Required Header
#include <math/core/Polynomial.hpp>
Header-only. No library linkage required.
Constructors
| Signature | Description |
|---|---|
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 Function | Description |
|---|---|
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) const | Coefficient 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
| Operator | Description |
|---|---|
p + q | Addition |
p - q | Subtraction |
p * q | Multiplication ($O(nm)$) |
p / q | Quotient (polynomial division). Returns RationalFunction in rational mode |
p % q | Remainder (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)
| Function | Description |
|---|---|
p.divmod(d) | Returns quotient q and remainder r via structured binding |
p.quo(d) | Quotient only (always returns Polynomial, mode-independent) |
p / d | Default: quotient only. Returns RationalFunction in rational mode |
p % d | Remainder only |
Division Mode — Switching operator/ Behavior
The return type of Polynomial / Polynomial can be switched at compile time via a macro.
| Mode | p / q returns | Use 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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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.
| Signature | Description |
|---|---|
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:
- Content extraction — GCD of all coefficients is pulled out as a constant factor.
- Rational root theorem — search $f(b/a) = 0$ over $b \mid a_0$, $a \mid a_n$, remove all linear factors $(ax - b)$.
- 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.
- 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:
- $\mathrm{GF}(p)$ factorization — Cantor-Zassenhaus (DDF + EDF)
- Hensel lifting — linear + multi-factor binary-tree Hensel lift to $\mathbb{Z}[x]$
- Factor recombination — $r \leq 12$: Zassenhaus enumeration, $r > 12$: van Hoeij lattice (LLL)
| Version | Types | Linking | Practical range |
|---|---|---|---|
| core | int, int64_t | none | $\deg \lesssim 10$ |
| pro | int, int64_t, Int, Rational | calx_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
| Signature | Description |
|---|---|
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
| Function | Description |
|---|---|
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.
| Aspect | Ordinary remainder | Pseudo-remainder |
|---|---|---|
| Required structure | Field ($\mathbb{Q}, \mathbb{R}$, …) | Ring ($\mathbb{Z}$ is enough) |
| Result | Exact, but contains fractions | Scaled by $\mathrm{lc}(g)^k$ |
| Use case | Euclidean GCD over a field | subresultant 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
| Function | Description |
|---|---|
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.
| Function | Polynomial |
|---|---|
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
| Trait | Description |
|---|---|
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.
- Polynomial Representation and Basic Operations — Dense/sparse representation, arithmetic
- Polynomial GCD — Euclidean and subresultant algorithms
- Factorization over Finite Fields — Berlekamp, Cantor-Zassenhaus
- Factorization over Integers — Hensel lifting, LLL lattice basis reduction