FLINT3.7.0-dev

Multiplying polynomials

Synopsis#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)source
void _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)source

Sets (z,zh−zl)(z, z_h - z_l) to the coefficients [zl,zh)[z_l, z_h) of the product of (a,an)(a, a_n) and (b,bn)(b, b_n) modulo nn. Requires an,bn≥1a_n, b_n \ge 1, in either order, and reduced coefficients; any modulus n≥1n \ge 1 is allowed.

Nothing is written if zl≥zhz_l \ge z_h, and coefficients from index an+bn−1a_n + b_n - 1 on are zero. With kk the bit length of nn, the number of primes is the least whose product is at least m22km 2^{2k}, with m=min⁡(an,bn)m = \min(a_n, b_n): one for moduli up to about 2(50−log⁡2m)/22^{(50 - \log_2 m)/2}, two up to about 2(99−log⁡2m)/22^{(99 - \log_2 m)/2} and three for all other 64-bit moduli. A single prime suffices also when nn is one of the context primes, or when nn is a prime of at least 20 bits with 2d∣n−12^d \mid n - 1 for the transform length 2d2^d and max⁡(an,bn)≥100\max(a_n, b_n) \ge 100; the transform is then computed modulo nn 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)source
void _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)source

Transposed product: sets zj=∑iaivi+j mod nz_j = \sum_i a_i v_{i+j} \bmod n for 0≤j<zn0 \le j < z_n, with (v,vn)(v, v_n) read as padded with zeros to length an+zn−1a_n + z_n - 1. This is the transpose of the multiplication by (a,an)(a, a_n) of polynomials of length znz_n; with aa the reversal of ff it is the window [an−1,an−1+zn)[a_n - 1, a_n - 1 + z_n) of the product fvfv. Requires an,vn,zn≥1a_n, v_n, z_n \ge 1, vn≤an+zn−1v_n \le a_n + z_n - 1 and reduced coefficients; the output must not overlap the inputs.

The cost is that of a product of lengths ana_n and znz_n, whereas _nmod_poly_mul_mid_mpn_ctx computes the same window from a product of lengths ana_n and vnv_n, unless a power of two allows a cyclic convolution. The plan, and with it the primes and the transform modulo nn itself, is that of the product of lengths ana_n and znz_n; the convolution engine runs in its transposed mode, sending vv through sd_ifft_trunc_tr, multiplying pointwise by the transform of aa 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)source
int _fmpz_poly_mul_mid_default_mpn_ctx(fmpz * z, slong zl, slong zh, const fmpz * a, slong an, const fmpz * b, slong bn)source

Sets (z,zh−zl)(z, z_h - z_l) to the coefficients [zl,zh)[z_l, z_h) of the product of (a,an)(a, a_n) and (b,bn)(b, b_n) over Z\mathbb{Z} and returns 1, if eight primes suffice for the coefficient size; otherwise returns 0 and leaves z unchanged. Requires an,bn≥1a_n, b_n \ge 1; z must hold initialised fmpz values.

With βa\beta_a and βb\beta_b the maximal bit sizes of the coefficients of aa and bb, the product of the primes must be at least bn2βa+βb+1b_n 2^{\beta_a + \beta_b + 1}, so the function succeeds roughly when βa+βb+log⁡2bn<397\beta_a + \beta_b + \log_2 b_n < 397. Signed coefficients are lifted to the symmetric range. See Multiplying polynomials.

These functions compute a window [zl,zh)[z_l, z_h) of the coefficients of a product of two polynomials, over Z/nZ\mathbb{Z}/n\mathbb{Z} for any word-size nn or over Z\mathbb{Z}. With zl=0z_l = 0 and zh=an+bn−1z_h = a_n + b_n - 1 this is the full product, with zl=0z_l = 0 a low product, and with zl=bn−1z_l = b_n - 1, zh=anz_h = a_n 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#

  • an,bn≥1a_n, b_n \ge 1, in either order. Coefficients over Z/nZ\mathbb{Z}/n\mathbb{Z} must be reduced; any n≥1n \ge 1 is allowed. The output must not overlap the inputs and receives zh−zlz_h - z_l coefficients; nothing is written if zl≥zhz_l \ge z_h, and coefficients from index an+bn−1a_n + b_n - 1 on are zero.
  • The fmpz functions need initialised output coefficients. They return 0 without writing anything if the coefficients are too large for eight primes, roughly when βa+βb+log⁡2bn≥397\beta_a + \beta_b + \log_2 b_n \ge 397 for coefficients of at most βa\beta_a and βb\beta_b bits; the caller then uses another algorithm, as fmpz_poly_mul does.
  • Over Z/nZ\mathbb{Z}/n\mathbb{Z} the bound on the number of products in a coefficient uses min⁡(an,bn)\min(a_n, b_n) and nothing depends on the operand order. The fmpz functions use bnb_n, 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 [0,n)[0, n) is a sum of at most bnb_n products less than 22k2^{2k}, where kk is the bit length of nn. The number of primes is the least whose product PP is at least bn22kb_n 2^{2k}, which gives for products of two polynomials of length ℓ\ell:

bits of nnℓ=102\ell = 10^210310^310410^410510^510610^6
811111
1611112
2011222
24 to 3222222
4022223
4422333
48 to 6433333

One prime suffices, without Chinese remaindering, in two further cases:

  • nn is one of the eight primes of the context, which have 50 bits;
  • nn is a prime of at least 20 bits with 2d∣n−12^d \mid n - 1, where 2d2^d is the transform length, and bn≥100b_n \ge 100. The transform is then computed modulo nn itself, with twiddle tables set up for the call.

For fmpz polynomials the coefficients are bounded by bn2βa+βbb_n 2^{\beta_a + \beta_b} in absolute value and are reconstructed in the symmetric range, which needs P≥bn2βa+βb+1P \ge b_n 2^{\beta_a + \beta_b + 1}.

Algorithm#

Each operand is reduced modulo each prime and transformed with a truncated transform of length 2d2^d, 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 2d≥zh2^d \ge z_h fold onto coefficients below zlz_l, the convolution is computed cyclically with length 2d2^d. Finally each output coefficient is reconstructed from its residues and reduced modulo nn. 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.

AlgorithmMultiplication with several primes and CRTHow 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 zj=∑i<anaivi+jz_j = \sum_{i < a_n} a_i v_{i+j} for 0≤j<zn0 \le j < z_n, the transpose of the multiplication by aa of polynomials of length znz_n. With aa the reversal of ff this is the window [an−1,an−1+zn)[a_n - 1, a_n - 1 + z_n) of fvfv, the usual middle product, and every window [zl,zh)[z_l, z_h) of a product reduces to this form after shifting and zero-padding the longer operand. The transposed product costs one product of lengths ana_n and znz_n: the plan is that of that product, and the convolution engine replaces the forward transform of vv 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 ff and vv, of length an+vn−1a_n + v_n - 1, which is up to 2an+zn−22 a_n + z_n - 2. It can use a cyclic convolution when a power of two lies between an+zn−1a_n + z_n - 1 and that length (Lemma 3 of Middle products and the transposition principle); otherwise its transform is longer. For the balanced middle product, an=zna_n = z_n, that is a transform of length about 3an3 a_n against 2an2 a_n. _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 [n−1,2n−1)[n - 1, 2n - 1) of lengths 2n−12n - 1 and nn modulo 30- and 60-bit primes (nn from 300 to 10510^5), 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 vv used with many aa, _mul_precomp_init_tr stores the transposed inverse transform of vv 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:

Examplefft_small_poly_mul.cneeds fft_smallchecked by make checkdoc
#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;
}
Output
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 500⋅2149+151+1<2310500 \cdot 2^{149 + 151 + 1} < 2^{310}, 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 bb, and the same coefficients from nmod_poly_mulmid:

Examplefft_small_poly_mul_tr.cneeds fft_smallchecked by make checkdoc
#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;
}
Output
transposed product: agrees

Guides#

Cite this page

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).