Multiplying polynomials
#include <flint/fft_small.h>void _nmod_poly_mul_mid_mpn_ctx(ulong * z, ulong zl, ulong zh, const ulong * a, ulong an, const ulong * b, ulong bn, nmod_t mod, mpn_ctx_t R)sourcevoid _nmod_poly_mul_mid_default_mpn_ctx(nn_ptr z, slong zl, slong zh, nn_srcptr a, slong an, nn_srcptr b, slong bn, nmod_t mod)sourceSets to the coefficients of the product of and modulo . Requires , in either order, and reduced coefficients; any modulus is allowed.
Nothing is written if , and coefficients from index on are zero. With the bit length of , the number of primes is the least whose product is at least , with : one for moduli up to about , two up to about and three for all other 64-bit moduli. A single prime suffices also when is one of the context primes, or when is a prime of at least 20 bits with for the transform length and ; the transform is then computed modulo itself. Nothing depends on the order of the operands. See Multiplying polynomials.
void _nmod_poly_mul_tr_mpn_ctx(ulong * z, ulong zn, const ulong * a, ulong an, const ulong * v, ulong vn, nmod_t mod, mpn_ctx_t R)sourcevoid _nmod_poly_mul_tr_default_mpn_ctx(nn_ptr z, slong zn, nn_srcptr a, slong an, nn_srcptr v, slong vn, nmod_t mod)sourceTransposed product: sets for , with read as padded with zeros to length . This is the transpose of the multiplication by of polynomials of length ; with the reversal of it is the window of the product . Requires , and reduced coefficients; the output must not overlap the inputs.
The cost is that of a product of lengths and , whereas _nmod_poly_mul_mid_mpn_ctx computes the same window from a product of lengths and , unless a power of two allows a cyclic convolution. The plan, and with it the primes and the transform modulo itself, is that of the product of lengths and ; the convolution engine runs in its transposed mode, sending through sd_ifft_trunc_tr, multiplying pointwise by the transform of and applying sd_fft_trunc_tr. No wrap-around is used. nmod_poly_mulmid reaches this function through _nmod_poly_mulmid_fft_small when its transform is shorter. See Transposed products and Transposed FFT.
int _fmpz_poly_mul_mid_mpn_ctx(fmpz * z, ulong zl, ulong zh, const fmpz * a, ulong an, const fmpz * b, ulong bn, mpn_ctx_t R)sourceint _fmpz_poly_mul_mid_default_mpn_ctx(fmpz * z, slong zl, slong zh, const fmpz * a, slong an, const fmpz * b, slong bn)sourceSets to the coefficients of the product of and over and returns 1, if eight primes suffice for the coefficient size; otherwise returns 0 and leaves z unchanged. Requires ; z must hold initialised fmpz values.
With and the maximal bit sizes of the coefficients of and , the product of the primes must be at least , so the function succeeds roughly when . Signed coefficients are lifted to the symmetric range. See Multiplying polynomials.
These functions compute a window of the coefficients of a product of two polynomials, over for any word-size or over . With and this is the full product, with a low product, and with , the middle product of Newton iteration. nmod_poly_mul, nmod_poly_mullow, nmod_poly_mulmid and the corresponding fmpz_poly functions call the default versions when their tuning tables say so, which for most moduli is from lengths of about 100 to 1000 on (see Where FLINT uses fft_small).
Contract#
- , in either order. Coefficients over must be reduced; any is allowed. The output must not overlap the inputs and receives coefficients; nothing is written if , and coefficients from index on are zero.
- The
fmpzfunctions need initialised output coefficients. They return 0 without writing anything if the coefficients are too large for eight primes, roughly when for coefficients of at most and bits; the caller then uses another algorithm, asfmpz_poly_muldoes. - Over the bound on the number of products in a coefficient uses and nothing depends on the operand order. The
fmpzfunctions use , so a very unbalanced product may need one prime less with the shorter operand second.
Number of primes#
A coefficient of the integer product of polynomials with entries in is a sum of at most products less than , where is the bit length of . The number of primes is the least whose product is at least , which gives for products of two polynomials of length :
| bits of | |||||
|---|---|---|---|---|---|
| 8 | 1 | 1 | 1 | 1 | 1 |
| 16 | 1 | 1 | 1 | 1 | 2 |
| 20 | 1 | 1 | 2 | 2 | 2 |
| 24 to 32 | 2 | 2 | 2 | 2 | 2 |
| 40 | 2 | 2 | 2 | 2 | 3 |
| 44 | 2 | 2 | 3 | 3 | 3 |
| 48 to 64 | 3 | 3 | 3 | 3 | 3 |
One prime suffices, without Chinese remaindering, in two further cases:
- is one of the eight primes of the context, which have 50 bits;
- is a prime of at least 20 bits with , where is the transform length, and . The transform is then computed modulo itself, with twiddle tables set up for the call.
For fmpz polynomials the coefficients are bounded by in absolute value and are reconstructed in the symmetric range, which needs .
Algorithm#
Each operand is reduced modulo each prime and transformed with a truncated transform of length , the transforms are multiplied pointwise together with the normalisation factor, and the inverse transform is applied. The truncation is the product length rounded up to a multiple of 256, or the full length for transforms of 256 points or less. If the window starts high enough that the coefficients beyond a power of two fold onto coefficients below , the convolution is computed cyclically with length . Finally each output coefficient is reconstructed from its residues and reduced modulo . The transforms run in parallel over the primes when the second operand has at least 1500 coefficients (for fmpz, 1000 with two or more primes and 300 with four or more), and the reconstruction over ranges of coefficients.
fft_small multiplies integers and polynomials whose product coefficients exceed one prime: chunk sizes and the number of primes, the two-prime and multi-prime pipelines, Chinese remaindering with the normalisation folded in, windows computed with wrap-around, sums of products in transform space, precomputed and shared operands, and transposed products for middle products.
Transposed products#
_nmod_poly_mul_tr_mpn_ctx computes for , the transpose of the multiplication by of polynomials of length . With the reversal of this is the window of , the usual middle product, and every window of a product reduces to this form after shifting and zero-padding the longer operand. The transposed product costs one product of lengths and : the plan is that of that product, and the convolution engine replaces the forward transform of by the transposed inverse transform and the inverse transform by the transposed forward transform (Transposed transforms).
_nmod_poly_mul_mid_mpn_ctx computes the same window from the product of and , of length , which is up to . It can use a cyclic convolution when a power of two lies between and that length (Lemma 3 of Middle products and the transposition principle); otherwise its transform is longer. For the balanced middle product, , that is a transform of length about against . _nmod_poly_mulmid_fft_small, and through it nmod_poly_mulmid, compares the transform lengths of both plans and uses the transposed product when it is strictly shorter. In timings of the window of lengths and modulo 30- and 60-bit primes ( from 300 to ), the transposed product was 1.2 to 1.9 times faster than the window product, except where the latter wraps around at the same transform length and the dispatcher keeps it.
The integer middle products of _mpn_ctx_mpn_mul_range (behind flint_mpn_mulmid) and of radix_mulmid_fft_small choose between the two forms in the same way (Algorithm). For a fixed used with many , _mul_precomp_init_tr stores the transposed inverse transform of and _nmod_poly_mul_tr_precomp computes each transposed product with two transforms (Transposed products).
Example#
A middle product modulo a 62-bit prime, and a product of integer polynomials with large coefficients:
#include <flint/flint.h>
#include <flint/nmod.h>
#include <flint/nmod_vec.h>
#include <flint/nmod_poly.h>
#include <flint/fmpz.h>
#include <flint/fmpz_vec.h>
#include <flint/fmpz_poly.h>
#include <flint/fft_small.h>
int main(void)
{
slong an = 2000, bn = 1000, i;
nmod_t mod;
nn_ptr a, b, full, mid;
fmpz_poly_t f, g, h;
fmpz * w;
int ok;
/* the middle product of lengths 2000 and 1000 modulo a 62-bit prime */
nmod_init(&mod, UWORD(4611686018427387847));
a = _nmod_vec_init(an);
b = _nmod_vec_init(bn);
full = _nmod_vec_init(an + bn - 1);
mid = _nmod_vec_init(an - bn + 1);
for (i = 0; i < an; i++)
a[i] = nmod_pow_ui(3, i, mod);
for (i = 0; i < bn; i++)
b[i] = nmod_pow_ui(5, i * i, mod);
_nmod_poly_mul(full, a, an, b, bn, mod);
_nmod_poly_mul_mid_default_mpn_ctx(mid, bn - 1, an, a, an, b, bn, mod);
flint_printf("middle product: %s\n",
_nmod_vec_equal(mid, full + bn - 1, an - bn + 1) ? "agrees" : "WRONG");
/* integer polynomials: 150-bit coefficients fit in eight primes ... */
fmpz_poly_init(f);
fmpz_poly_init(g);
fmpz_poly_init(h);
for (i = 0; i < 500; i++)
{
fmpz_poly_set_coeff_ui(f, i, i + 1);
fmpz_poly_set_coeff_si(g, i, (i % 2) ? -1 : 1);
}
fmpz_poly_scalar_mul_2exp(f, f, 140);
fmpz_poly_scalar_mul_2exp(g, g, 150);
fmpz_poly_mul(h, f, g);
w = _fmpz_vec_init(999);
ok = _fmpz_poly_mul_mid_default_mpn_ctx(w, 0, 999, f->coeffs, 500, g->coeffs, 500);
flint_printf("149 + 151 bits: returns %d, %s\n", ok,
(ok && _fmpz_vec_equal(w, h->coeffs, 999)) ? "agrees" : "-");
/* ... but 200-bit coefficients do not */
fmpz_poly_scalar_mul_2exp(f, f, 60);
fmpz_poly_scalar_mul_2exp(g, g, 50);
ok = _fmpz_poly_mul_mid_default_mpn_ctx(w, 0, 999, f->coeffs, 500, g->coeffs, 500);
flint_printf("209 + 201 bits: returns %d\n", ok);
_fmpz_vec_clear(w, 999);
fmpz_poly_clear(f);
fmpz_poly_clear(g);
fmpz_poly_clear(h);
_nmod_vec_clear(a);
_nmod_vec_clear(b);
_nmod_vec_clear(full);
_nmod_vec_clear(mid);
flint_cleanup_master();
return 0;
}middle product: agrees 149 + 151 bits: returns 1, agrees 209 + 201 bits: returns 0
The 62-bit modulus needs three primes. For the integer polynomials the bound is , which seven primes (348 bits) cover; with 410 bits of coefficients even eight primes (398 bits) are too few.
The middle product of the example above as a transposed product, by the reversal of , and the same coefficients from nmod_poly_mulmid:
#include <flint/flint.h>
#include <flint/nmod.h>
#include <flint/nmod_vec.h>
#include <flint/nmod_poly.h>
#include <flint/fft_small.h>
int main(void)
{
slong an = 2000, bn = 1000, zn = an - bn + 1, i;
nmod_t mod;
nn_ptr a, b, rb, mid, tr;
nmod_init(&mod, UWORD(4611686018427387847));
a = _nmod_vec_init(an);
b = _nmod_vec_init(bn);
rb = _nmod_vec_init(bn);
mid = _nmod_vec_init(zn);
tr = _nmod_vec_init(zn);
for (i = 0; i < an; i++)
a[i] = nmod_pow_ui(3, i, mod);
for (i = 0; i < bn; i++)
b[i] = nmod_pow_ui(5, i * i, mod);
/* coefficients [bn - 1, an) of a b */
_nmod_poly_mulmid(mid, a, an, b, bn, bn - 1, an, mod);
/* tr[j] = sum_i rb[i] a[i + j] with rb the reversal of b */
_nmod_poly_reverse(rb, b, bn, bn);
_nmod_poly_mul_tr_default_mpn_ctx(tr, zn, rb, bn, a, an, mod);
flint_printf("transposed product: %s\n",
_nmod_vec_equal(mid, tr, zn) ? "agrees" : "WRONG");
_nmod_vec_clear(a);
_nmod_vec_clear(b);
_nmod_vec_clear(rb);
_nmod_vec_clear(mid);
_nmod_vec_clear(tr);
flint_cleanup_master();
return 0;
}transposed product: agrees
Guides#
The FLINT team, “Multiplying polynomials (fft_small.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fft_small/poly_mul.html (generated 2026-11-02).
@misc{flint-doc-fft-small-poly-mul,
author = {{The FLINT team}},
title = {{Multiplying polynomials (fft\_small.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/fft_small/poly_mul.html},
note = {Generated 2026-11-02}
}