Multiplying polynomials over Z/nZ
Almost every fast algorithm for polynomials over (division, GCD, evaluation, interpolation, composition, power series) spends its time in polynomial multiplication. FLINT multiplies polynomials with word-size modulus in one of three ways, chosen by the lengths of the operands and the bit length of (NMOD_BITS):
| Method | Entry points | Cost | Best for |
|---|---|---|---|
| Classical | _nmod_poly_mul_classical | word products, reductions | short operands |
| Kronecker substitution | _nmod_poly_mul_KS, _nmod_poly_mul_KS2, _nmod_poly_mul_KS4 | one, two or four integer products, bits in total | medium lengths |
| Small-prime FFT | _nmod_poly_mullow_fft_small, _nmod_poly_mulmid_fft_small | for , with one to four primes | long operands |
Here are the operand lengths. The algorithms are standard; see [GG2003, Ch. 8] and [Ber2008] for surveys.
Products come in four shapes, each with its own selection among these methods:
- the full product (
_nmod_poly_mul); - the low product (
_nmod_poly_mullow), used by Newton iteration and power series arithmetic; - the middle product, coefficients of (
_nmod_poly_mulmid), the transpose of multiplication, see Middle products and the transposition principle; - the high product, coefficients (
_nmod_poly_mulhigh).
Generic version#
gr_poly_mul, gr_poly_mullow and gr_poly_mulmid work over any ring by calling the ring methods GR_METHOD_POLY_MULLOW and GR_METHOD_POLY_MULMID. The gr context for (gr_ctx_init_nmod) installs _nmod_poly_mullow and _nmod_poly_mulmid as these methods, so generic code over this ring (Newton iteration, subproduct trees, half-gcd, composition) makes exactly the choices described here. Other rings have their own methods: fmpz_poly products for and , Kronecker substitution for finite fields , block multiplication for real and complex balls, multi-word kernels and the small-prime FFT for mpn_mod (Polynomial multiplication over Z/nZ for multiword n); the table in Multiplication lists them. A ring without a method of its own gets the generic default, the classical algorithm with one dot product (_gr_vec_dot_rev) per output coefficient, which is the analogue of the classical method below with the ring's dot product in place of the modular one. gr_poly also offers one level of Karatsuba, Toom-3 and Toom-Cook multiplication and bivariate Kronecker substitution (Multiplication algorithms) for rings that want to install them.
Classical multiplication#
The schoolbook method computes each output coefficient as a dot product of a slice of with a reversed slice of , using _nmod_vec_dot_rev. As explained in Dot products modulo a word-size integer, the sum is accumulated exactly in one, two or three words and reduced once, so a product of lengths costs word multiplications but only modular reductions. When a whole product coefficient fits in one word (), _nmod_poly_mul_classical instead accumulates all coefficients with plain word arithmetic, row by row, and reduces at the end; squares are computed with about half the multiplications. Classical low, middle and high products (_nmod_poly_mullow_classical, _nmod_poly_mulmid_classical, _nmod_poly_mulhigh_classical) compute only the requested dot products, so they cost proportionally less.
FLINT does not use Karatsuba's method [KO1962] for nmod_poly: Kronecker substitution reaches the Karatsuba and Toom–Cook range of integer multiplication with less overhead.
Kronecker substitution#
Packing the coefficients into integers with fields of bits turns the product into one integer multiplication, which flint_mpn_mul performs with basecase, Toom–Cook or FFT code as appropriate. Harvey's variants KS2 and KS4 evaluate at (and at ) and replace this product by two or four products of a half or a quarter of the size. Details and proofs are in Kronecker substitution.
The small-prime FFT#
For long operands, nmod_poly uses the fft_small module, which computes convolutions modulo primes with truncated FFTs in double-precision floating point, vectorised with AVX2, AVX-512 or NEON (FFTs modulo word-size primes in floating point, Truncated FFTs). The coefficients of the integer product are reconstructed by Chinese remaindering from their residues modulo primes (Multiplication with several primes and CRT) and then reduced modulo . The default primes have about 50 bits each; their products have , , and bits for . The number of primes is the least with , so
| Modulus | Primes for typical lengths |
|---|---|
| (e.g. and ) | 1 |
| (e.g. and ) | 2 |
| (e.g. , ) | 3 |
| larger | 4 |
Three cases avoid Chinese remaindering altogether:
- If is itself one of the FFT primes of the default context, the transform is done modulo directly. For such (50 bits),
_nmod_poly_mullow_want_fft_smallselects the FFT from length 24 instead of using the table below. - If is any prime with , for the transform length , , and passes the error bound check of the floating-point arithmetic, the transform is done modulo with a temporary context (the primality test is cached per thread).
- If , two coefficients are packed into each FFT slot, which halves the transform length (see Repacking for tiny moduli).
The products are computed by _nmod_poly_mul_mid_default_mpn_ctx in fft_small (Multiplying polynomials), which handles full, low and middle products: for a middle product it chooses a cyclic convolution length so that the wrap-around only pollutes coefficients outside the requested range when a power of two allows it (Classical and FFT middle products). A middle product can also be computed as a transposed product, at the cost of a product of the shorter operand and the output length, with the transposed transforms of fft_small (_nmod_poly_mul_tr_default_mpn_ctx); _nmod_poly_mulmid_fft_small takes whichever of the two has the shorter transform (Transposed FFT). If FLINT's thread pool has workers and the shorter operand has length at least 1500, the transforms for the different primes run in parallel threads (and from length 5000 the two operands are transformed in parallel). Where FLINT uses fft_small lists the other callers of fft_small and their thresholds, and how to call the module directly.
Repacking for tiny moduli#
For a product coefficient has few bits, and a 50-bit prime has room for three base- digits with or . Write and and set , , polynomials of half the length with coefficients below .
Lemma 1. , and . If all coefficients of and of are less than and all coefficients of are less than the prime , then coefficient of , computed modulo , has base- digits , , with and .
The proof is by expanding the products. _nmod_poly_mulmid_fft_small_repack checks the hypotheses with the bound or, failing that, with Cauchy–Schwarz bounds and so on, which are much smaller for random inputs. If neither works it returns 0 and the ordinary FFT is used.
Cutoffs#
The following describes x86-64 with fft_small (FLINT built with AVX2 or AVX-512); without fft_small the FFT branch is absent, see below. "Length" means for full products, so that very unbalanced products are treated like balanced ones of length .
Full products. _nmod_poly_mul, with , tries in order:
- classical if ;
- the FFT if
_nmod_poly_mullow_want_fft_smallsays so: for when and , except for lengths to (where the transform length doubles) and, for , to ; for other when the length is at least a tabulated value for (separate tables for products and squares), or at least 24 for the 50-bit FFT primes; - classical if ;
- KS if ;
- KS2 if ;
- KS4 otherwise.
For balanced operands of length this gives the following ranges:
| classical | KS | KS2 | KS4 | FFT (product) | FFT (square) | FFT primes at the cutoff | |
|---|---|---|---|---|---|---|---|
| 5 () | 7–159 | 160–673 | – | 1 | |||
| 8 | 7–99 | 100–305 | – | 1 | |||
| 12 | 8–66 | 67–191 | – | 1 | |||
| 16 | 11–49 | 50–98 | – | 1 | |||
| 20 | 14–39 | 40–81 | – | 1 | |||
| 22 | 15–36 | 37–189 | 190–437 | 2 | |||
| 24 | 16–33 | 34–159 | 160–323 | 2 | |||
| 28 | 19–28 | 29–118 | 119–267 | 2 | |||
| 32 | 22–24 | 25–91 | 92–175 | 2 | |||
| 40 | – | 27–59 | 60–91 | 2 | |||
| 46 | – | 31–45 | 46–80 | 2 | |||
| 47 | – | 32–43 | 44–176 | 3 | |||
| 50 | – | 34–38 | 39–161 | 3 | |||
| 56 | – | – | 38–163 | 3 | |||
| 60 | – | – | 40–164 | 3 | |||
| 61 | – | – | 41–94 | 3 | |||
| 64 | – | – | 43–93 | 3 |
For squares the KS ranges extend up to the square cutoff. The jumps in the FFT cutoff follow the number of primes (one prime up to , two up to , three above) and the word sizes of the KS fields: for example, the drop at coincides with the point where the -bit digits of KS4 no longer fit in one word at these lengths. The tables are compiled into the library; unlike some other cutoffs in FLINT they do not depend on the processor.
Low products. _nmod_poly_mullow truncates the operands to the output length , calls _nmod_poly_mul if , and otherwise uses classical for , the FFT if reaches a third table (for : and except to ; for the 50-bit FFT primes: ), classical if , and _nmod_poly_mullow_KS otherwise. The tabulated FFT cutoffs for low products decrease from 1115 for to 69 for , and lie between 174 and 220 for . Since KS computes the whole product and the FFT needs a transform as long as for the full product, a truncated product is substantially cheaper than a full one only with the classical method. Low products use only plain KS, not KS2 or KS4, which is one reason why the FFT cutoffs differ from those for full products.
Middle products. _nmod_poly_mulmid first drops the operand coefficients that do not contribute, puts the longer operand first, and then uses classical if either or the output length is at most 5, and _nmod_poly_mul if the full product is requested. Otherwise it uses the FFT if _nmod_poly_mulmid_want_fft_small holds: if the FFT would be used for a full product of lengths and (a middle product is the transpose of such a product, and is computed as one when that is shorter than the window of the full product), and also if for and . Below that it uses _nmod_poly_mulmid_KS for small moduli and long outputs (, and with or with ) and the classical method otherwise.
High products. _nmod_poly_mulhigh uses the classical method for tiny inputs (, or when a coefficient fits in a word) and otherwise _nmod_poly_mulmid with the window , which chooses its method as above, and sets the coefficients below to zero.
Without fft_small. On machines without AVX2 or NEON support, _nmod_poly_mul uses classical, KS, KS2 and KS4 by steps 1 and 3 to 6 above, so KS4 is used for all long products. Its integer products use flint_mpn_mul, that is, GMP's mpn_mul (with its Toom–Cook code) above the basecase and Karatsuba sizes and, above FLINT_FFT_MUL_THRESHOLD limbs, FLINT's Schönhage–Strassen FFT [SS1971] (Schönhage–Strassen multiplication). Low and middle products use classical below output coefficients and KS above.
Which function to call#
- Call
nmod_poly_mul,nmod_poly_mullowornmod_poly_mulmid(or their underscore forms); they make the choices above. The forced variants (nmod_poly_mul_classical,nmod_poly_mul_KS,nmod_poly_mul_KS2,nmod_poly_mul_KS4) are meant for testing and tuning. - Ask only for the coefficients you need. A low product saves about half of the work with the classical method (with the FFT and KS it costs about as much as the full product). A middle product of lengths and costs about as much as a product of lengths and , with every method except KS; Newton iteration in FLINT relies on this (Newton iteration for power series and division).
- To multiply many polynomials by the same polynomial modulo the same modulus, precompute:
nmod_poly_mulmod_precondstores data for a fixed factor, and the geometric progression structures of Geometric progressions store FFTs of the fixed factors. - Squares are detected when both operands are the same pointer with the same length; they use separate cutoffs and are cheaper by a factor of up to two.
Other modules#
fmpz_poly multiplies integer polynomials with a much larger set of methods (classical, Karatsuba, Toom, KS, Schönhage–Strassen and the multimodular fft_small product), all selected in _fmpz_poly_mulmid; see Multiplying polynomials over Z. Polynomials over finite fields are mostly multiplied by Kronecker substitution into or (Use in FLINT), so they too end up in the code described here.
void _nmod_poly_mul(nn_ptr res, nn_srcptr poly1, slong len1, nn_srcptr poly2, slong len2, nmod_t mod)void _nmod_poly_mullow(nn_ptr res, nn_srcptr poly1, slong len1, nn_srcptr poly2, slong len2, slong trunc, nmod_t mod)void _nmod_poly_mulmid(nn_ptr res, nn_srcptr poly1, slong len1, nn_srcptr poly2, slong len2, slong nlo, slong nhi, nmod_t mod)void _nmod_poly_mulhigh(nn_ptr res, nn_srcptr poly1, slong len1, nn_srcptr poly2, slong len2, slong n, nmod_t mod)int _nmod_poly_mullow_want_fft_small(slong len1, slong len2, slong n, int squaring, nmod_t mod)int _nmod_poly_mulmid_want_fft_small(slong len2, slong m, nmod_t mod)Referenced from#
- FFTs in FLINT (overview)
- Multiplication with several primes and CRT (algorithm)
- FFTs modulo word-size primes in floating point (algorithm)
- Univariate polynomials (overview)
- Kronecker substitution (algorithm)
- Middle products and the transposition principle (algorithm)
- Multiplying polynomials over Z (overview)
- Newton iteration for power series and division (algorithm)
- Polynomial arithmetic over generic rings and its specialisations (overview)
- fq_default_poly.h (module)
- fq_nmod_poly.h (module)
- fq_poly.h (module)
- Multiplying polynomials over finite fields
- fq_zech_poly.h (module)
- Applying transforms
- Polynomial multiplication over Z/nZ for multiword n
- nmod_poly.h (module)
- Multiplying polynomials
References#
- [GG2003] J. von zur Gathen and J. Gerhard, Modern Computer Algebra, second edition, Cambridge University Press (2003)
- [Ber2008] D. J. Bernstein, "Fast multiplication and its applications", in Algorithmic Number Theory, MSRI Publications 44 (2008), 325–384
- [KO1962] A. Karatsuba and Yu. Ofman, "Multiplication of multidigit numbers on automata", Doklady Akademii Nauk SSSR 145 (1962), 293–294; English translation in Soviet Physics Doklady 7 (1963), 595–596
- [SS1971] A. Schönhage and V. Strassen. Schnelle Multiplikation großer Zahlen. Computing 7 (1971), 281–292.
The FLINT team, “Multiplying polynomials over Z/nZ,” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/polynomials/multiplication.html (generated 2026-11-02).
@misc{flint-doc-algorithms-polynomials-multiplication,
author = {{The FLINT team}},
title = {{Multiplying polynomials over Z/nZ}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/algorithms/polynomials/multiplication.html},
note = {Generated 2026-11-02}
}