Back to Number Theory Dsa

Polynomial Algorithms, FFT & NTT

September 22, 2026 Wasil Zafar 7 min read

Turn polynomial operations into fast convolution primitives.

Table of Contents

  1. Intuition, Vocabulary and Foundations
  2. Step-by-Step Derivations and Algorithms
  3. Implementation, Complexity and Engineering
  4. Applications, Case Studies and Exercises

Intuition, Vocabulary and Foundations

A polynomial is stored by coefficients, $A(x)=\sum_i a_ix^i\leftrightarrow[a_0,a_1,\ldots]$. Multiplication is convolution:

$$c_k=\sum_{i=0}^{k}a_i b_{k-i}.$$

Thus $(1+2x+3x^2)(4+5x)=4+13x+22x^2+15x^3$. Naive multiplication costs $O(nm)$. FFT and NTT instead evaluate at roots of unity, multiply pointwise, and interpolate. FFT uses approximate complex arithmetic; NTT uses exact finite-field arithmetic. A primitive $N$th root $\omega$ satisfies $\omega^N=1$ but $\omega^j\ne1$ for $0\lt j\lt N$.

Formal power series treat coefficients algebraically to a declared precision. An inverse $G=F^{-1}\bmod x^n$ requires invertible $f_0$; the standard formal logarithm requires $f_0=1$.

ConceptDefinitionExampleBoundaryQuestion answered
Convolution$c_k=\sum_i a_i b_{k-i}$$[1,2]*[3,4]=[3,10,8]$Pointwise products are differentHow are product coefficients formed?
Root of unityPrimitive $\omega$ with $\omega^N=1$Power-of-two NTT rootLower-order roots lose informationWhich evaluation grid is invertible?
FFTComplex transformRounded integer convolutionFloating error may corrupt coefficientsHow is approximate convolution accelerated?
NTTFinite-field transformModulo $998244353$Length must divide $p-1$How is modular convolution exact?
Formal inverse$FG\equiv1\pmod{x^n}$$(1-x)^{-1}=1+x+\cdots$Requires invertible $f_0$How is series division defined?
Formal logarithm$\log F=\int F'/F$$\log(1+x)$Standard form requires $F(0)=1$How are products linearized?

Step-by-Step Derivations and Algorithms

For input lengths $n,m$, choose the smallest supported power of two $N\ge n+m-1$, pad with zeros, transform both arrays, multiply corresponding values, and invert. A radix-two transform splits even and odd coefficients and combines them through butterfly equations.

For NTT modulus $p$, the length must divide $p-1$. The inverse uses reciprocal roots and multiplication by $N^{-1}$. Wide exact integer convolution uses several NTT primes and CRT; their product must exceed the proven coefficient range.

Newton inversion doubles precision:

$$G_{\mathrm{new}}=G(2-FG)\pmod{x^{2m}}.$$

If $FG=1-E$ modulo $x^m$, then $FG_{\mathrm{new}}=1-E^2$, so the error begins at degree $2m$. From inversion follow $\log F=\int F'/F$ and exponential via $G_{\mathrm{new}}=G(1+F-\log G)$.

Polynomial Algorithms, FFT & NTT algorithm map
flowchart TD
    A["Choose coefficient domain and precision"] --> B{"Operation?"}
    B -->|"Convolution"| C["Pad to supported length"]
    C --> D{"FFT or NTT?"}
    D -->|"FFT"| E["Control rounding"]
    D -->|"NTT"| F["Validate prime and root order"]
    E --> G["Transform, multiply, invert"]
    F --> G
    B -->|"Formal series"| H["Check constant term"]
    H --> I["Newton precision doubling"]
    G --> J["Normalize and truncate"]
    I --> J

Each butterfly preserves evaluation on the current root grid, and the inverse restores coefficients after scaling by $N^{-1}$. FFT and NTT cost $O(N\log N)$; Newton iteration reaches precision $n$ in $O(M(n))$ when multiplication costs $M(n)$.

Implementation, Complexity and Engineering

This reference uses $998244353=119\cdot2^{23}+1$ and primitive root $3$. It validates lengths, normalizes coefficients, implements exact convolution, and builds derivative, integral, inverse, and logarithm operations.

MOD = 998_244_353
ROOT = 3
MAX_N = 1 << 23

def ntt(a, invert=False):
    n = len(a)
    if n == 0 or n & (n - 1) or n > MAX_N: raise ValueError("bad length")
    j = 0
    for i in range(1, n):
        bit = n >> 1
        while j & bit: j ^= bit; bit >>= 1
        j ^= bit
        if i < j: a[i], a[j] = a[j], a[i]
    length = 2
    while length <= n:
        wlen = pow(ROOT, (MOD - 1) // length, MOD)
        if invert: wlen = pow(wlen, MOD - 2, MOD)
        for start in range(0, n, length):
            w = 1
            for offset in range(length // 2):
                u = a[start + offset]
                v = a[start + offset + length // 2] * w % MOD
                a[start + offset] = (u + v) % MOD
                a[start + offset + length // 2] = (u - v) % MOD
                w = w * wlen % MOD
        length <<= 1
    if invert:
        inv_n = pow(n, MOD - 2, MOD)
        for i in range(n): a[i] = a[i] * inv_n % MOD

def convolution(a, b):
    if not a or not b: return []
    need = len(a) + len(b) - 1
    if min(len(a), len(b)) <= 24:
        out = [0] * need
        for i, x in enumerate(a):
            for j, y in enumerate(b): out[i + j] = (out[i + j] + x * y) % MOD
        return out
    size = 1
    while size < need: size <<= 1
    if size > MAX_N: raise ValueError("transform too large")
    fa = [x % MOD for x in a] + [0] * (size - len(a))
    fb = [x % MOD for x in b] + [0] * (size - len(b))
    ntt(fa); ntt(fb)
    for i in range(size): fa[i] = fa[i] * fb[i] % MOD
    ntt(fa, True)
    return fa[:need]

def derivative(f): return [i * f[i] % MOD for i in range(1, len(f))]

def integral(f):
    return [0] + [f[i - 1] * pow(i, MOD - 2, MOD) % MOD for i in range(1, len(f) + 1)]

def fps_inverse(f, n):
    if n < 0: raise ValueError("negative precision")
    if n == 0: return []
    if not f or f[0] % MOD == 0: raise ValueError("invertible constant required")
    g = [pow(f[0] % MOD, MOD - 2, MOD)]
    while len(g) < n:
        size = min(2 * len(g), n)
        prefix = [(f[i] if i < len(f) else 0) % MOD for i in range(size)]
        fg = convolution(prefix, g)[:size]
        correction = [(-x) % MOD for x in fg]
        correction[0] = (correction[0] + 2) % MOD
        g = convolution(g, correction)[:size]
    return g

def fps_log(f, n):
    if n == 0: return []
    if not f or f[0] % MOD != 1: raise ValueError("constant one required")
    return integral(convolution(derivative(f), fps_inverse(f, n))[:n-1])[:n]

if __name__ == "__main__":
    assert convolution([1,2,3],[4,5]) == [4,13,22,15]
    a = [i*i % MOD for i in range(64)]
    b = a[:]; ntt(b); ntt(b, True); assert b == a
    inverse = fps_inverse([1,-1], 8)
    assert inverse == [1] * 8
    assert convolution([1,-1], inverse)[:8] == [1] + [0] * 7
    assert derivative([5,7,11,13]) == [7,22,39]
    assert integral([7,22,39]) == [0,7,11,13]
    print("all tests passed")
MethodDomainTimeAdvantageFailure modeMitigation
Naive convolutionAny ring$O(nm)$Simple exact oracleQuadratic runtimeUse for small cases and tests
Complex FFTFloating point$O(N\log N)$Flexible lengthsRounding corruptionBound error and verify
Single NTTSupported finite field$O(N\log N)$Exact modular resultUnsupported lengthValidate modulus and root order
Multiple NTT plus CRTSeveral primes$O(tN\log N)$Wide exact integersPrime product too smallProve coefficient bounds
Newton inverseSuitable field$O(M(n))$Doubles precisionNoninvertible constantCheck $f_0$ first
FPS logarithmField with index inverses$O(M(n))$Turns products into sumsWrong constant termNormalize or reject

Applications, Case Studies and Exercises

Convolution combines independent counts, accelerates large-integer multiplication, correlation, probability distributions, and many dynamic programs. Formal series turn algebraic specifications into coefficient algorithms, but every operation needs explicit precision and domain preconditions.

Practice

Apply Polynomial Algorithms, FFT & NTT

Pair-sum distribution: convolve two six-sided-die coefficient arrays. Confirm the coefficient of $x^7$ is $6$, the coefficient sum is $36$, and the result is symmetric.

Large histogram merge: convolve lengths $200{,}000$ and $150{,}000$. Choose the smallest supported power of two at least $349{,}999$, compare random coefficients with direct sums, and use extra primes plus CRT if exact coefficients can exceed the modulus.

Hands-on series engine: implement inverse, logarithm, and exponential. Verify $FF^{-1}\equiv1$, $\exp(\log F)\equiv F$ for $F(0)=1$, and $\log(\exp G)\equiv G$ for $G(0)=0$, all modulo $x^n$. Differentially test through $n=64$.

Exercises: multiply two small polynomials manually; explain cyclic wraparound; derive butterflies; prove Newton doubling; find the maximum supported transform length; implement three-prime CRT; add formal square roots with documented preconditions.

Use naive multiplication for small inputs, FFT when controlled approximation is acceptable, NTT for exact modular convolution, and multiple primes plus CRT for wider integers. Formal-series operations require explicit precision, valid constant terms, normalization, and independent verification.

Next in the Series

In Finite Fields & Polynomial Factorization, we'll explore Compute in prime and extension fields using polynomial quotients..