Products and divisions with a fixed operand
#include <flint/fft_small.h>mul_precomp_structA transformed polynomial over : a plan for cyclic convolutions of length and the transform of the operand modulo , with its length bn and the truncation btrunc it was transformed for. If the field transposed is set (by _mul_precomp_init_tr), it holds instead the images of a polynomial through the transposed inverse transform, for transposed products with .
int _mul_precomp_init(mul_precomp_struct * M, const ulong * b, ulong bn, ulong btrunc, ulong depth, ulong len_bound, ulong direct_len, nmod_t mod, mpn_ctx_t R)sourcevoid _mul_precomp_clear(mul_precomp_struct * M)sourceInitialises M with the transform of for convolutions of length , = depth , in which at most len_bound products accumulate in one coefficient, and returns 1; returns 0 if more than eight primes would be needed. btrunc is the truncation of the transforms M will be used with, an upper bound for the output lengths.
len_bound = is always enough; a smaller bound such as for products of linear length at most may save a prime. direct_len enables the transform modulo itself as in fft_small_plan_init_nmod (0 disables it). An operand longer than is reduced modulo . See Products and divisions with a fixed operand.
int _nmod_poly_mul_mid_precomp(ulong * z, ulong zl, ulong zh, const ulong * a, ulong an, const mul_precomp_struct * M, nmod_t mod, mpn_ctx_t R)sourceSets to the coefficients of the product of with the polynomial stored in M and returns 1, or returns 0 if the window needs a transform longer than . Requires .
The window fits if , or if the coefficients beyond fold onto coefficients below , that is, if and . M is only read, so several threads may use it at the same time, each with its own context R (normally its default context); R must have the primes of the context M was made with.
int _mul_precomp_init_tr(mul_precomp_struct * M, const ulong * v, ulong vn, ulong len_bound, ulong direct_len, nmod_t mod, mpn_ctx_t R)sourceInitialises M for the transposed products with computed by _nmod_poly_mul_tr_precomp, in which at most len_bound products accumulate in one coefficient, and returns 1; returns 0 if no plan is available. M is freed with _mul_precomp_clear.
These transposed products are transposes of products of length at most , so one plan serves them all: that of the window of a convolution of length , whose transform length is rounded up to a multiple of 256 (for ) rather than a power of two. M stores the images of through the transposed inverse transform (fft_small_ifft_tr_nmod). direct_len enables the transform modulo itself as in fft_small_plan_init_nmod (0 disables it).
int _nmod_poly_mul_tr_precomp(ulong * z, ulong zn, const ulong * r, ulong rn, const mul_precomp_struct * M, nmod_t mod, mpn_ctx_t R)sourceSets for , with the polynomial of M made by _mul_precomp_init_tr, and returns 1; returns 0, writing nothing, if . Requires , at most len_bound nonzero coefficients in and reduced coefficients.
is transformed, multiplied pointwise by the stored images of , and the transposed forward transform gives the coefficients: two transforms of the length of M instead of the three of _nmod_poly_mul_tr_mpn_ctx (the convolution engine in its second transposed mode). With the reversal of , zero padded in front, this is a window of the product ; this is how the evaluation and extrapolation at geometric progressions use it. M is only read, as for _nmod_poly_mul_mid_precomp.
nmod_poly_divrem_precomp_structA divisor prepared for division with remainder: the transform of the reversed power series inverse of the reversed , and the transform of itself for the cyclic product that gives the remainder.
void _nmod_poly_divrem_precomp_init(nmod_poly_divrem_precomp_struct * M, const ulong * b, ulong bn, ulong Bn, nmod_t mod, mpn_ctx_t R)sourcevoid _nmod_poly_divrem_precomp_clear(nmod_poly_divrem_precomp_struct * M)sourcePrepares the divisor for quotients of length at most , that is, for dividends of length at most . Requires and an invertible leading coefficient.
int _nmod_poly_divrem_precomp(ulong * q, ulong * r, const ulong * a, ulong an, nmod_poly_divrem_precomp_struct * M, nmod_t mod, mpn_ctx_t R)sourceSets and to the quotient and remainder of by the divisor prepared in M, and returns 1. Requires . Returns 0 without computing anything if the quotient length exceeds the precision of the stored inverse or needs a longer transform than the one prepared.
A product costs, per prime, two forward transforms, a pointwise product and one inverse transform. When one operand is multiplied by many polynomials, its transforms can be computed once and kept, which saves about a third of the work of every later product. For middle products in which the fixed polynomial is the long operand, a transposed version keeps its images through the transposed inverse transform instead. nmod_poly uses both for evaluation, interpolation and extrapolation at geometric progressions, where the same polynomial multiplies several inputs. Division with remainder by a fixed divisor builds on it: the divisor and its power series inverse are both kept in transformed form.
Products#
- Call
_mul_precomp_initwith and a depth such that is at least the lengths of the products (or windows) needed later. Passbtruncunless all later outputs are shorter, andlen_boundunless a smaller bound on the number of products per coefficient is known, such as . Pass a nonzerodirect_len(at least 100) to allow a transform modulo itself when is a suitable prime. - Call
_nmod_poly_mul_mid_precompfor each . It returns 0, writing nothing, if the window does not fit in a cyclic convolution of length . - Free the data with
_mul_precomp_clear.
A window of the product of lengths and fits if , or if and : the coefficients of index then fold onto , outside the window. This is the wrap-around trick for middle products [BZ2011]: a middle product of lengths and fits in any transform length , where the full product would need .
_nmod_poly_mul_mid_precomp only reads M: it works on a copy of the plan whose primes, Chinese remaindering data and scratch memory come from the context R. Several threads can use one M at the same time, each with its own context, normally get_default_mpn_ctx() of the thread. All default contexts have the same primes, which is required.
Transposed products#
The transposed product , , is computed by _nmod_poly_mul_tr_mpn_ctx as , with the forward transform of and , the transposes of the forward and inverse transforms (Transposed FFT). For a fixed , the images can be stored:
- Call
_mul_precomp_init_trwith , a boundlen_boundon the number of nonzero coefficients of the later (at most ), anddirect_lenas above. The plan is that of the window of a product of length , whose truncated transform length is rounded up to a multiple of 256 (for ). - Call
_nmod_poly_mul_tr_precompfor each with (it returns 0 otherwise). Each call converts and transforms , multiplies pointwise and applies : two transforms instead of three. - Free the data with
_mul_precomp_clear.
With the reversal of , preceded by zeros, the output is the window of . Storing the forward transform of with _mul_precomp_init instead would need a cyclic length into which that window fits; the transposed version needs transforms of length about only. As for _nmod_poly_mul_mid_precomp, M is only read.
A polynomial of length 3000 prepared once, and the window of its products with three polynomials of length 1000:
#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 vn = 3000, an = 1000, zn = 2000, i, k;
nmod_t mod;
nn_ptr v, a, r, z, ref;
mul_precomp_struct M[1];
mpn_ctx_struct * R = get_default_mpn_ctx();
nmod_init(&mod, 1000003);
v = _nmod_vec_init(vn);
a = _nmod_vec_init(an);
r = _nmod_vec_init(an);
z = _nmod_vec_init(zn + 2);
ref = _nmod_vec_init(zn);
for (i = 0; i < vn; i++)
v[i] = nmod_pow_ui(3, (ulong) i * i, mod);
/* store the images of v through the transposed inverse transform,
for short operands of length at most an */
if (!_mul_precomp_init_tr(M, v, vn, an, 0, mod, R))
flint_abort();
for (k = 1; k <= 3; k++)
{
for (i = 0; i < an; i++)
a[i] = nmod_pow_ui(i + k, 3, mod);
/* r = rev(a): the output is the window [an - 1, an - 1 + zn) of a*v */
_nmod_poly_reverse(r, a, an, an);
_nmod_poly_mulmid(ref, v, vn, a, an, an - 1, an - 1 + zn, mod);
flint_printf("k = %wd: %s\n", k,
(_nmod_poly_mul_tr_precomp(z, zn, r, an, M, mod, R)
&& _nmod_vec_equal(z, ref, zn)) ? "agrees" : "WRONG");
}
/* too long: an + zn - 1 > vn */
flint_printf("window of length %wd returns %d\n", zn + 2,
_nmod_poly_mul_tr_precomp(z, zn + 2, r, an, M, mod, R));
_mul_precomp_clear(M);
_nmod_vec_clear(v);
_nmod_vec_clear(a);
_nmod_vec_clear(r);
_nmod_vec_clear(z);
_nmod_vec_clear(ref);
flint_cleanup_master();
return 0;
}k = 1: agrees k = 2: agrees k = 3: agrees window of length 2002 returns 0
Division with remainder#
For a divisor of length with invertible leading coefficient, _nmod_poly_divrem_precomp_init computes the power series inverse of the reversal of to precision and stores the transforms of and of . A dividend of length is then divided as follows, with :
- the quotient is the window of the product of the top coefficients of with , which is the reversal of [GG2003, §9.1];
- the remainder is , computed modulo for the power of two . Since , the reduction modulo loses nothing, and only the residue of modulo and the cyclic product are needed.
_nmod_poly_divrem_mpn_ctx performs the same steps without precomputation.
_nmod_poly_divrem_precomp returns 0 without computing anything when , or when the quotient window does not fit in the prepared transform.
Examples#
A polynomial of length 1000 transformed once for length and multiplied by three others, with full products of length 3999 and with a middle product that uses the wrap-around:
#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 = 4000, bn = 1000, i, k;
nmod_t mod;
nn_ptr a, b, full, z;
mul_precomp_struct M[1];
mpn_ctx_struct * R = get_default_mpn_ctx();
nmod_init(&mod, 1000003);
a = _nmod_vec_init(an);
b = _nmod_vec_init(bn);
full = _nmod_vec_init(an + bn - 1);
z = _nmod_vec_init(an + bn - 1);
for (i = 0; i < bn; i++)
b[i] = nmod_pow_ui(2, i, mod);
/* transform b once, for cyclic convolutions of length 2^12 */
if (!_mul_precomp_init(M, b, bn, 4096, 12, 4096, 0, mod, R))
flint_abort();
for (k = 1; k <= 3; k++)
{
for (i = 0; i < an; i++)
a[i] = nmod_pow_ui(i + k, 3, mod);
/* a product of lengths 3000 and 1000 fits into 4096 coefficients */
_nmod_poly_mul(full, a, 3000, b, bn, mod);
flint_printf("k = %wd: full product %s", k,
(_nmod_poly_mul_mid_precomp(z, 0, 3999, a, 3000, M, mod, R)
&& _nmod_vec_equal(z, full, 3999)) ? "agrees" : "WRONG");
/* lengths 4000 and 1000: only windows that wrap below 999 fit */
_nmod_poly_mul(full, a, an, b, bn, mod);
flint_printf(", full product of length 4999 returns %d",
_nmod_poly_mul_mid_precomp(z, 0, 4999, a, an, M, mod, R));
flint_printf(", middle product %s\n",
(_nmod_poly_mul_mid_precomp(z, 999, 4000, a, an, M, mod, R)
&& _nmod_vec_equal(z, full + 999, 3001)) ? "agrees" : "WRONG");
}
_mul_precomp_clear(M);
_nmod_vec_clear(a);
_nmod_vec_clear(b);
_nmod_vec_clear(full);
_nmod_vec_clear(z);
flint_cleanup_master();
return 0;
}k = 1: full product agrees, full product of length 4999 returns 0, middle product agrees k = 2: full product agrees, full product of length 4999 returns 0, middle product agrees k = 3: full product agrees, full product of length 4999 returns 0, middle product agrees
Division by a fixed divisor of length 500, prepared for quotients of length up to 1500:
#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)
{
ulong n = 998244353;
slong bn = 500, Bn = 1500, an, i;
nmod_t mod;
nmod_poly_t A, B, Q, R;
nn_ptr q, r;
nmod_poly_divrem_precomp_struct M[1];
mpn_ctx_struct * ctx = get_default_mpn_ctx();
nmod_init(&mod, n);
nmod_poly_init(A, n);
nmod_poly_init(B, n);
nmod_poly_init(Q, n);
nmod_poly_init(R, n);
for (i = 0; i < bn; i++)
nmod_poly_set_coeff_ui(B, i, nmod_pow_ui(7, i + 1, mod));
/* the divisor serves dividends of length up to bn + Bn - 1 = 1999 */
_nmod_poly_divrem_precomp_init(M, B->coeffs, bn, Bn, mod, ctx);
q = _nmod_vec_init(Bn);
r = _nmod_vec_init(bn - 1);
for (an = 1000; an <= 1999; an += 333)
{
nmod_poly_zero(A);
for (i = 0; i < an; i++)
nmod_poly_set_coeff_ui(A, i, nmod_pow_ui(i + 2, 5, mod));
nmod_poly_divrem(Q, R, A, B);
if (!_nmod_poly_divrem_precomp(q, r, A->coeffs, an, M, mod, ctx))
flint_printf("length %wd: transform too short\n", an);
else
flint_printf("length %wd: quotient %s, remainder %s\n", an,
_nmod_vec_equal(q, Q->coeffs, an - bn + 1) ? "agrees" : "WRONG",
(R->length <= bn - 1
&& _nmod_vec_equal(r, R->coeffs, R->length)
&& _nmod_vec_is_zero(r + R->length, bn - 1 - R->length))
? "agrees" : "WRONG");
}
_nmod_poly_divrem_precomp_clear(M);
_nmod_vec_clear(q);
_nmod_vec_clear(r);
nmod_poly_clear(A);
nmod_poly_clear(B);
nmod_poly_clear(Q);
nmod_poly_clear(R);
flint_cleanup_master();
return 0;
}length 1000: quotient agrees, remainder agrees length 1333: quotient agrees, remainder agrees length 1666: quotient agrees, remainder agrees length 1999: quotient agrees, remainder agrees
References#
- [BZ2011] R. P. Brent and P. Zimmermann, Modern Computer Arithmetic, Cambridge University Press (2011), http://www.loria.fr/~zimmerma/mca/pub226.html
- [GG2003] J. von zur Gathen and J. Gerhard, Modern Computer Algebra, second edition, Cambridge University Press (2003)
The FLINT team, “Products and divisions with a fixed operand (fft_small.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fft_small/precomp.html (generated 2026-11-02).
@misc{flint-doc-fft-small-precomp,
author = {{The FLINT team}},
title = {{Products and divisions with a fixed operand (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/precomp.html},
note = {Generated 2026-11-02}
}