Convolutions of large integers
#include <flint/fft.h>void fft_convolution(mp_limb_t ** ii, mp_limb_t ** jj, slong depth, slong limbs, slong trunc, mp_limb_t ** t1, mp_limb_t ** t2, mp_limb_t ** s1, mp_limb_t ** tt)sourceMultiplies the polynomials with coefficients ii and jj modulo , , with a truncated sqrt2 transform of length , , and writes the first trunc coefficients of the product, reduced, to ii. Requires (for and an even , suffices; other values can crash or hang) and that the product has at most trunc coefficients; for the product is otherwise computed modulo . Requires to divide and, if limbs exceeds FFT_MULMOD_2EXPP1_CUTOFF, limbs to be a value returned by fft_adjust_limbs. Both arrays have entries, and the entries beyond each input must be zero. jj is overwritten unless jj == ii (squaring). The scratch arrays need one entry per thread.
void fft_convolution_sd_fft(struct sd_fft_mpn_mulmod_2expp1_ctx_struct * sdctx, mp_limb_t ** ii, mp_limb_t ** jj, slong depth, slong limbs, slong trunc, mp_limb_t ** t1, mp_limb_t ** t2, mp_limb_t ** s1, mp_limb_t ** tt)sourceAs fft_convolution, but if sdctx is not NULL, the pointwise products modulo are computed by sd_fft_mpn_mulmod_2expp1 of fft_small with this context, which must have been initialised for and the digit count returned by fft_adjust_limbs_sd_fft. With NULL it is fft_convolution.
Plans a convolution of which only the coefficients with indices in of a product of length len_out are needed, where . Returns the depth and sets trunc for fft_convolution: either the truncated transform with , or a cyclic transform of length whose wrapped terms all fall below nlo, whichever processes fewer points. The truncated choice violates when , so callers handle that case separately.
fft_convolution multiplies two polynomials whose coefficients are residues modulo , . When the coefficients of the true integer product are known to be smaller than (or than in absolute value), it computes the product of two polynomials with integer coefficients exactly. This is how fmpz_poly implements Schönhage–Strassen multiplication, and with integer coefficients obtained by cutting integers into chunks, it is also the core of mul_mfa_truncate_sqrt2. The guide Multiplying integers and polynomials explains the whole procedure.
Parameters and layout#
The transform has length , , and only trunc of its points are computed. To multiply polynomials of lengths and , take and , so that as required (for ). The ring size must be a multiple of , so that the transform has bits per point, and if it must be a value returned by fft_adjust_limbs (or fft_adjust_limbs_sd_fft) because the pointwise products use fft_mulmod_2expp1.
iiandjjare arrays of pointers to blocks of limbs. The first , respectively , entries hold the input coefficients as -limb two's complement integers (negative values are allowed); all other entries must be zero. Only the entries belowtrunc, rounded up to an even number or, for , to a multiple of , are read, but zeroing all of them is simplest.- On return,
ii[k]holds the coefficient of the product, reduced to , for . If the true coefficients satisfy , then is the residue if it is less than and the residue minus otherwise. jjis overwritten by its transform. Passing the same array twice (ii == jj) computes a square with one forward transform less.t1,t2ands1are arrays of at leastflint_get_num_threads()pointers to limbs each, andttan array of as many pointers to limbs each. For only the first entries are used.
The bound on follows from the sizes of the inputs: if and , then , so
suffices, with if any coefficient is negative and otherwise. _fmpz_poly_mulmid_SS uses exactly this bound, rounds it up to a multiple of and of , and adjusts the limb count with fft_adjust_limbs_sd_fft.
If the product has more than trunc coefficients, the result is meaningless, except for , where fft_convolution computes the cyclic product modulo (each coefficient with is added to ). fft_convolution_choose_mid exploits this.
Algorithm#
For , fft_convolution uses fft_truncate_sqrt2 on both operands, multiplies the transformed coefficients with fft_mulmod_2expp1, applies ifft_truncate_sqrt2 and divides by . For larger depths it uses the matrix Fourier algorithm with columns, in three threaded stages: fft_mfa_truncate_sqrt2_outer (sqrt2 layer and column transforms) on each operand, then fft_mfa_truncate_sqrt2_inner, which handles one row at a time (row transforms of both operands, pointwise products, inverse row transform) while the row is in cache, and finally ifft_mfa_truncate_sqrt2_outer, which also divides by . The total cost is that of three truncated transforms of trunc points plus trunc multiplications modulo , each a Schönhage–Strassen product of its own for large .
fft_convolution_sd_fft computes the pointwise products with sd_fft_mpn_mulmod_2expp1 from fft_small instead, a negacyclic convolution modulo several word-size primes (Multiplication modulo 2^N + 1), which is about twice as fast on a recent x86-64 processor for rings of bits and more. Its context must be initialised with sd_fft_mpn_mulmod_2expp1_ctx_init for and the digit count that fft_adjust_limbs_sd_fft returned together with limbs. For large depths it keeps the fused inner stage only when there are at least eight work units (columns plus truncated rows) per thread; otherwise it performs the transforms in separate single-threaded passes and only the pointwise products, which dominate, in threads.
Partial products#
When only the coefficients with indices in of a product of length are wanted, a cyclic transform can be shorter than the truncated one. In the product modulo , coefficient receives . If and , every wrapped term with has and is zero, so the window is unaffected. fft_convolution_choose_mid takes the least power of two with this property and returns it as trunc if it is smaller than the number of points the truncated transform of length would process; otherwise it returns (at least 0) with . For the middle product of by coefficients, where the wanted window has length , this gives points instead of about when is a power of two.
Examples#
The product of two polynomials of length 300 with signed 31-bit coefficients. The coefficients of the product are below in absolute value, so would do, but must be a multiple of , so the example uses four limbs.
#include <gmp.h>
#include <flint/flint.h>
#include <flint/fft.h>
#include <flint/fmpz.h>
#include <flint/fmpz_poly.h>
#include <flint/mpn_extras.h>
/* one coefficient c with |c| < 2^63, as a (limbs + 1)-limb two's complement value */
static void set_si(mp_limb_t * x, slong c, slong limbs)
{
flint_mpn_store(x, limbs + 1, c < 0 ? ~UWORD(0) : 0);
x[0] = (mp_limb_t) c;
}
/* a reduced residue modulo 2^N + 1, read in the symmetric range */
static void get_fmpz(fmpz_t c, mp_limb_t * x, slong limbs, const mpz_t p)
{
mpz_t t;
mpz_init(t);
fermat_to_mpz(t, x, limbs);
if (mpz_sizeinbase(t, 2) >= limbs * FLINT_BITS) /* t >= 2^(N-1) */
mpz_sub(t, t, p);
fmpz_set_mpz(c, t);
mpz_clear(t);
}
int main(void)
{
slong len = 300, len_out = 2 * len - 1;
slong depth = FLINT_CLOG2(len_out) - 2; /* 4n >= len_out > 2n */
slong n = WORD(1) << depth; /* 256 */
slong limbs = 4, size = limbs + 1; /* N = 256 >= 70 bits, n | N */
slong nt = flint_get_num_threads(), i;
mp_limb_t ** ii, ** jj, ** t1, ** t2, ** s1, ** tt, * mem;
fmpz_poly_t f, g, h, h2;
mpz_t p;
fmpz_poly_init(f); fmpz_poly_init(g); fmpz_poly_init(h); fmpz_poly_init(h2);
for (i = 0; i < len; i++)
{
fmpz_poly_set_coeff_si(f, i, (i * 1103515245 + 12345) % (WORD(1) << 30) - (WORD(1) << 29) * (i % 3));
fmpz_poly_set_coeff_si(g, i, (i * 69069 + 1) % (WORD(1) << 30) - (WORD(1) << 30) * (i % 2));
}
ii = flint_malloc((8 * n + 4 * nt) * sizeof(mp_limb_t *));
jj = ii + 4 * n;
t1 = jj + 4 * n; t2 = t1 + nt; s1 = t2 + nt; tt = s1 + nt;
mem = flint_malloc((8 * n + 5 * nt) * size * sizeof(mp_limb_t));
for (i = 0; i < 8 * n; i++)
ii[i] = mem + i * size;
for (i = 0; i < nt; i++)
{
t1[i] = mem + (8 * n + 5 * i) * size;
t2[i] = t1[i] + size;
s1[i] = t2[i] + size;
tt[i] = s1[i] + size; /* 2 (limbs + 1) limbs */
}
for (i = 0; i < 4 * n; i++)
{
set_si(ii[i], i < len ? fmpz_get_si(f->coeffs + i) : 0, limbs);
set_si(jj[i], i < len ? fmpz_get_si(g->coeffs + i) : 0, limbs);
}
fft_convolution(ii, jj, depth, limbs, len_out, t1, t2, s1, tt);
mpz_init(p);
mpz_ui_pow_ui(p, 2, limbs * FLINT_BITS);
mpz_add_ui(p, p, 1);
fmpz_poly_fit_length(h, len_out);
for (i = 0; i < len_out; i++)
get_fmpz(h->coeffs + i, ii[i], limbs, p);
_fmpz_poly_set_length(h, len_out);
_fmpz_poly_normalise(h);
fmpz_poly_mul(h2, f, g);
flint_printf("depth = %wd, trunc = %wd, equal: %d\n", depth, len_out, fmpz_poly_equal(h, h2));
flint_printf("coefficient 299: ");
fmpz_print(h->coeffs + 299);
flint_printf("\n");
mpz_clear(p);
flint_free(mem); flint_free(ii);
fmpz_poly_clear(f); fmpz_poly_clear(g); fmpz_poly_clear(h); fmpz_poly_clear(h2);
return 0;
}depth = 8, trunc = 599, equal: 1 coefficient 299: -889640045340373524
A product with 8000-bit coefficients, where the ring has 16384 bits and the pointwise products can use the negacyclic engine of fft_small. Here fft_adjust_limbs_sd_fft rounds 251 limbs up to 256 and returns digits of 64 bits. In builds without fft_small it returns and the program uses fft_convolution.
#include <gmp.h>
#include <flint/flint.h>
#include <flint/fft.h>
#include <flint/fmpz.h>
#include <flint/fmpz_poly.h>
#include <flint/mpn_extras.h>
#if FLINT_HAVE_FFT_SMALL
#include <flint/fft_small.h>
#endif
int main(void)
{
slong len = 40, len_out = 2 * len - 1, i, m;
slong depth = FLINT_CLOG2(len_out) - 2, n = WORD(1) << depth; /* n = 32 */
slong bits = 8000, res_bits, limbs, size;
slong nt = flint_get_num_threads();
mp_limb_t ** ii, ** jj, ** t1, ** t2, ** s1, ** tt, * mem;
fmpz_poly_t f, g, h, h2;
flint_rand_t state;
flint_rand_init(state);
fmpz_poly_init(f); fmpz_poly_init(g); fmpz_poly_init(h); fmpz_poly_init(h2);
fmpz_poly_randtest_unsigned(f, state, len, bits);
fmpz_poly_randtest_unsigned(g, state, len, bits);
fmpz_poly_set_coeff_ui(f, len - 1, 1);
fmpz_poly_set_coeff_ui(g, len - 1, 1);
/* product coefficients < len 2^(2 bits): round up to a multiple of n */
res_bits = 2 * bits + FLINT_CLOG2(len);
res_bits = ((res_bits + n - 1) / n) * n;
limbs = fft_adjust_limbs_sd_fft((res_bits - 1) / FLINT_BITS + 1, n, &m);
size = limbs + 1;
ii = flint_malloc((8 * n + 4 * nt) * sizeof(mp_limb_t *));
jj = ii + 4 * n;
t1 = jj + 4 * n; t2 = t1 + nt; s1 = t2 + nt; tt = s1 + nt;
mem = flint_calloc((8 * n + 5 * nt) * size, sizeof(mp_limb_t));
for (i = 0; i < 8 * n; i++)
ii[i] = mem + i * size;
for (i = 0; i < nt; i++)
{
t1[i] = mem + (8 * n + 5 * i) * size;
t2[i] = t1[i] + size; s1[i] = t2[i] + size; tt[i] = s1[i] + size;
}
for (i = 0; i < len; i++)
{
fmpz_get_ui_array(ii[i], limbs, f->coeffs + i);
fmpz_get_ui_array(jj[i], limbs, g->coeffs + i);
}
#if FLINT_HAVE_FFT_SMALL
if (m != 0)
{
sd_fft_mpn_mulmod_2expp1_ctx_t ctx;
sd_fft_mpn_mulmod_2expp1_ctx_init(ctx, get_default_mpn_ctx(),
limbs * FLINT_BITS, m);
fft_convolution_sd_fft(ctx, ii, jj, depth, limbs, len_out, t1, t2, s1, tt);
sd_fft_mpn_mulmod_2expp1_ctx_clear(ctx);
}
else
#endif
fft_convolution(ii, jj, depth, limbs, len_out, t1, t2, s1, tt);
fmpz_poly_fit_length(h, len_out);
for (i = 0; i < len_out; i++)
fmpz_set_ui_array(h->coeffs + i, ii[i], limbs);
_fmpz_poly_set_length(h, len_out);
fmpz_poly_mul(h2, f, g);
flint_printf("equal: %d\n", fmpz_poly_equal(h, h2));
flint_free(mem); flint_free(ii);
fmpz_poly_clear(f); fmpz_poly_clear(g); fmpz_poly_clear(h); fmpz_poly_clear(h2);
flint_rand_clear(state);
return 0;
}equal: 1
Guides#
The FLINT team, “Convolutions of large integers (fft.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fft/convolution.html (generated 2026-11-02).
@misc{flint-doc-fft-convolution,
author = {{The FLINT team}},
title = {{Convolutions of large integers (fft.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/fft/convolution.html},
note = {Generated 2026-11-02}
}