Polynomial<T> — 多項式テンプレート

概要

Polynomial<T> は calx の多項式テンプレートクラスである。 係数型 Tdouble, Rational, Complex<double> など任意の数値型を使用できる。

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

内部表現は昇べきの順: coeffs_[i] が $x^i$ の係数。

  • 任意の係数型double から Rational (厳密計算) や Complex<T> まで
  • Horner 評価 — $O(n)$ で $p(x)$ を計算
  • 多項式除算 — 商と剰余 (divmod)
  • ユークリッド GCD — 多項式の最大公約因子
  • 微分・積分derivative(), integral()
  • 直交多項式 — Chebyshev, Legendre, Hermite, Laguerre, Bessel, Jacobi, Gegenbauer
  • C++23 conceptsPolynomialCoeff で型制約

コンストラクタ

シグネチャ説明
Polynomial()零多項式
Polynomial(const T& c)定数多項式
Polynomial(std::vector<T> coeffs)係数ベクトルから (昇べきの順)
Polynomial(std::initializer_list<T> il)初期化子リストから
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

アクセサ

メンバ関数説明
degree()次数 (零多項式は $-1$)
isZero()零多項式か
leadingCoefficient()主係数 $c_n$
constantTerm()定数項 $c_0$
operator[](i) const$x^i$ の係数 (範囲外は $0$)
operator[](i)$x^i$ の係数への参照 (範囲外は自動拡張)
coefficients()係数ベクトルへの参照

算術演算子

演算子説明
p + q加算
p - q減算
p * q乗算 ($O(nm)$)
p / q商 (多項式除算)。有理式モード時は RationalFunction を返す
p % q剰余 (多項式除算)
p.pow(n)$p^n$ (二分累乗法)

スカラーとの演算: p + c, c * p, p / c もサポート。 複合代入: +=, -=, *=, /=

除算と剰余

auto [q, r] = p.divmod(d);  // p = d * q + r, deg(r) < deg(d)
関数説明
p.divmod(d)構造化束縛で商 q と剰余 r を返す
p.quo(d)商のみ (常に多項式を返す。モードに依存しない)
p / dデフォルト: 商のみ。有理式モード時は RationalFunction
p % d剰余のみ

除算モード — operator/ の動作切り替え

Polynomial / Polynomial の戻り値型は、コンパイル時マクロで切り替えられる。

モードp / q の戻り値用途
デフォルト Polynomial<T> (商) 多項式環での演算。int / int と同じ意味
CALX_POLY_DIV_RATIONAL RationalFunction<T> (有理式) 情報を失わない除算。数式処理向き

切り替え方法 1: ソースコード内で定義

// 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 (商が必要な場合)

切り替え方法 2: 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()

この設定でプロジェクト全体の operator/ の動作が切り替わる。 どちらのモードでも quo()divmod() は常に多項式除算として使える。

評価

関数説明
p(x)Horner 法で $p(x)$ を計算。$O(n)$
p.evaluateEstrin(x)Estrin 法 (並列性が高い)

p(x)xT と異なる型でもよい (例: Polynomial<double>Complex<double> を代入)。

x に多項式を渡すと 多項式合成 $p(q(x))$ になる。 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

合成後の次数は $\deg(p(q)) = \deg(p) \cdot \deg(q)$ となる。

微分・積分

関数説明
p.derivative()$p'(x)$
p.derivative(n)$n$ 階微分 $p^{(n)}(x)$
p.integral()不定積分 $\int p(x)\,dx$ (定数項 = 0)
definiteIntegral(p, a, b)定積分 $\int_a^b p(x)\,dx$

代数的操作

関数説明
gcd(p, q)ユークリッド GCD
approximateGCD(p, q, tol)近似 GCD (浮動小数点向け)
content(p)内容 (content) — 全係数の GCD
primitivePart(p)原始部分 (primitive part) — $p / \text{content}(p)$
compose(p, q)合成 $p(q(x))$
monomial(c, n)単項式 $c \cdot x^n$
X<T>()変数 $x$ (次数 1 の多項式)

因数分解・終結式

整数係数多項式の厳密な因数分解と、終結式・判別式を提供する。 ヘッダ math/core/Polynomial_factorization.hpp をインクルードする。

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

factorize() — 既約因子への分解

整数多項式 $f(x) \in \mathbb{Z}[x]$ を既約因子に分解する。

シグネチャ説明
factorize(const Polynomial<T>& f)$f = c \cdot \prod f_i^{e_i}$ に分解。FactoredInteger<Polynomial<T>> を返す

係数型 Tint または int64_t

パイプライン:

  1. content 抽出 — 全係数の GCD を定数因子として分離
  2. 有理根定理 — $f(b/a) = 0$ を $b \mid a_0$, $a \mid a_n$ の範囲で探索し、$(ax - b)$ の 1 次因子を全て取り除く
  3. Yun の無平方分解 — $\gcd(f, f')$ を用いて $f = \prod f_i^i$ ($f_i$ は無平方かつ互いに素)
  4. Kronecker 法 — 各無平方因子について $\deg f / 2 + 1$ 点での値の約数の組合せから Lagrange 補間で因子候補を構成
// 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

上記は core 版 (ヘッダのみ、リンク不要) で、 Kronecker 法の組合せ上限 100,000 により $\deg f \lesssim 10$ が実用範囲。

高速版 (calx_factorize.lib)

calx_factorize をリンクすると、自動的に高速パイプラインに切り替わる:

  1. $\mathrm{GF}(p)$ 因数分解 — Cantor-Zassenhaus (DDF + EDF)
  2. Hensel lifting — 線形 + 多因子二分木 Hensel で $\mathbb{Z}[x]$ に引き上げ
  3. 因子復元 — $r \leq 12$: Zassenhaus 組合せ、$r > 12$: van Hoeij 格子復元 (LLL)
対応型リンク実用範囲
coreint, int64_t不要$\deg \lesssim 10$
proint, int64_t, Int, Rationalcalx_factorize$\deg \lesssim 100+$

Int 型は内部で int64_t に委譲 (全係数が 64-bit に収まる場合)。 Rational 型は分母の LCM で整数化してから Int パスに委譲する。

// CMake: target_link_libraries(your_target PRIVATE calx_factorize)

squareFreeFactorization() — Yun の無平方分解

シグネチャ説明
squareFreeFactorization(const Polynomial<T>& f)$f = c \cdot \prod f_i^i$ の分解のみ実行 (既約判定はしない)

Yun のアルゴリズム (1976) を $\gcd(f, f')$ の subresultant PRS で実装。 重複因子を冪指数付きで分離する。既約性は保証しない。

整数多項式 GCD と擬剰余

関数説明
intPolyGcd(a, b)整数多項式の GCD (subresultant PRS)。擬剰余と primitive 化を組合せ、係数の爆発を抑える
pseudoRemainder(f, g)擬剰余 $\mathrm{lc}(g)^{\deg f - \deg g + 1} \cdot f \bmod g$。結果は必ず整数係数

擬剰余と通常の剰余の違い

通常の剰余は係数体 (体上での割り算) を仮定するので、整数係数では使えない:

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

例: $f = x^2,\ g = 2x + 1$ を整数で割ろうとすると $q = \dfrac{x}{2} - \dfrac{1}{4}$ となり、商に分数が現れる ($\mathbb{Z}[x]$ では商も剰余も求まらない)。

擬剰余 (pseudo-remainder) は、先に $g$ の主係数の冪を掛けることで分数を回避する:

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

主係数 $\mathrm{lc}(g)$ を予め乗算しておくため、$g$ で割っても係数が整数のまま保たれる

同じ例で: $\mathrm{lc}(g)^{2-1+1} = 2^2 = 4$ を掛けてから割ると

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

擬剰余 $r = 1$ (整数のみ)。

観点通常の剰余擬剰余
必要な代数構造体 ($\mathbb{Q}, \mathbb{R}$ 等)環 ($\mathbb{Z}$ で OK)
結果厳密だが分数を含む$\mathrm{lc}(g)^k$ 倍される
用途体上の Euclidean GCD$\mathbb{Z}[x]$ の subresultant GCD、因数分解

calx の pseudoRemainder()intPolyGcd() (整数多項式 GCD) のために用意されている — 整数係数のまま GCD を計算する subresultant PRS の基本演算である。

終結式・判別式

関数説明
sylvesterMatrix(f, g)$(m+n) \times (m+n)$ Sylvester 行列
resultant(f, g)終結式 $\mathrm{Res}(f, g) = \det(\mathrm{Sylvester}(f, g))$
discriminant(f)判別式 $\mathrm{Disc}(f) = (-1)^{n(n-1)/2} \mathrm{Res}(f, f') / a_n$
// f(x) = x^2 - 2 の判別式 = 4·1·2 = 8
Polynomial<int> f = {-2, 0, 1};
std::cout << discriminant(f) << std::endl;  // 8

判別式が $0$ でなければ $f$ は無平方 (重根を持たない)。

直交多項式

漸化式により生成される古典的な直交多項式。

関数多項式
chebyshevT<T>(n)第 1 種 Chebyshev $T_n(x)$
chebyshevU<T>(n)第 2 種 Chebyshev $U_n(x)$
legendre<T>(n)Legendre $P_n(x)$
hermite<T>(n)Hermite $H_n(x)$ (確率論者版)
laguerre<T>(n)Laguerre $L_n(x)$
bessel<T>(n)Bessel 多項式 $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

型特性

特性説明
is_polynomial_v<T>TPolynomial<U> なら true

使用例

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

int main() {
    // 多項式の構築
    Polynomial<double> p = {1.0, -3.0, 2.0};  // 1 - 3x + 2x^2
    Polynomial<double> q = {1.0, 1.0};         // 1 + x

    // 四則演算
    std::cout << "p = " << p << std::endl;
    std::cout << "q = " << q << std::endl;
    std::cout << "p * q = " << p * q << std::endl;

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

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

    // 微分・積分
    std::cout << "p' = " << p.derivative() << std::endl;  // -3 + 4x
    std::cout << "∫p dx = " << 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;

    // 直交多項式
    auto L3 = legendre<double>(3);
    std::cout << "P_3(x) = " << L3 << std::endl;
}

関連する数学的背景

以下の記事では、Polynomial クラスの基盤となる数学的概念を解説している。