fft.hMultiplying integers and polynomials
The module offers three levels of functionality: integer multiplication with automatically chosen parameters (flint_mpn_mul_fft_main), convolutions of vectors of large integers with parameters chosen by the caller (fft_convolution and its variants), and the transforms themselves.
Choosing a function#
- To multiply integers, call
flint_mpn_mulorfmpz_mul. For large operands they usempn_mul_default_mpn_ctxin builds withfft_small(on x86-64 with AVX2 and on ARM64), and otherwiseflint_mpn_mul_fft_main. Callflint_mpn_mul_fft_maindirectly only to get this algorithm regardless of the build, for example for comparisons; see Multiplying large integers. - To multiply polynomials with integer coefficients, call
fmpz_poly_muland its relatives, which choose between Schönhage–Strassen convolutions from this module, Kronecker substitution and classical methods. For many products with the same operand,fmpz_poly_mul_SS_precache_initandfmpz_poly_mullow_SS_precacheprecompute its transform. - Call
fft_convolutiondirectly to multiply vectors of large integers that are notfmpz_polyobjects, to control the transform parameters, or to multiply modulo coefficientwise (Convolutions of large integers, Convolutions with a fixed operand). - For a single product modulo , call
fft_mulmod_2expp1with a limb count fromfft_adjust_limbs(Multiplication modulo 2^N + 1).
Memory layout#
A convolution of transform length , , with coefficients modulo , , needs:
- For each operand an array of pointers, each to its own block of limbs. One coefficient is a signed two's complement integer in this block.
- For each thread (
flint_get_num_threads()of them), three blocks of limbs, collected in the pointer arrayst1,t2ands1, and one block of limbs in the arraytt.
The transforms swap pointers between the operand arrays and the scratch arrays, so all blocks should come from a single allocation, freed as a whole at the end, and the coefficients must always be accessed through the pointer array.
Choosing the parameters#
For a product with at most coefficients whose absolute values are below (or nonnegative and below ):
- Take so that , and pass . Products of at most 2 coefficients are not supported.
- Round up to a multiple of and of , so that every point of the transform carries bits.
- If exceeds 128, replace it by
fft_adjust_limbs(limbs), or byfft_adjust_limbs_sd_fft(limbs, n, &m)to allow the faster pointwise engine offft_small.
The coefficient bound comes from the operands: with input coefficients below and in absolute value and the shorter operand of length , the product coefficients are below . When the inputs are chunks of integers, as below, the chunk size is chosen instead so that ; this is the rule of mul_truncate_sqrt2.
A complete integer product#
The following program multiplies two integers of about 6300 bits with a convolution of length 256 modulo , which is what mul_truncate_sqrt2 does with and . The chunks have bits, so the product coefficients are below .
#include <gmp.h>
#include <flint/flint.h>
#include <flint/fft.h>
#include <flint/mpn_extras.h>
int main(void)
{
/* transform length 4n = 256, coefficients modulo 2^128 + 1 */
slong depth = 6, n = WORD(1) << depth, limbs = 2, size = limbs + 1;
flint_bitcnt_t bits = (limbs * FLINT_BITS - depth - 1) / 2; /* 60 */
slong nt = flint_get_num_threads(), i, j1, j2, trunc;
mp_limb_t ** ii, ** jj, ** t1, ** t2, ** s1, ** tt, * mem, * r;
mp_size_t n1, n2;
mpz_t a, b, c;
mpz_init(a); mpz_init(b); mpz_init(c);
mpz_ui_pow_ui(a, 3, 4000);
mpz_ui_pow_ui(b, 5, 2700);
n1 = mpz_size(a);
n2 = mpz_size(b);
/* 2 x 4n coefficients, plus per-thread scratch: t1, t2, s1 of
size limbs + 1 and tt of size 2 (limbs + 1) */
ii = flint_malloc(8 * n * sizeof(mp_limb_t *));
jj = ii + 4 * n;
t1 = flint_malloc(4 * nt * sizeof(mp_limb_t *));
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 + i) * size;
t2[i] = t1[i] + nt * size;
s1[i] = t2[i] + nt * size;
tt[i] = mem + (8 * n + 3 * nt + 2 * i) * size;
}
/* split into 60-bit coefficients and pad with zeros */
j1 = fft_split_bits(ii, mpz_limbs_read(a), n1, bits, limbs);
j2 = fft_split_bits(jj, mpz_limbs_read(b), n2, bits, limbs);
for (i = j1; i < 4 * n; i++) flint_mpn_zero(ii[i], size);
for (i = j2; i < 4 * n; i++) flint_mpn_zero(jj[i], size);
trunc = j1 + j2 - 1; /* must satisfy 2n < trunc <= 4n */
flint_printf("j1 = %wd, j2 = %wd, trunc = %wd, 4n = %wd\n", j1, j2, trunc, 4 * n);
fft_convolution(ii, jj, depth, limbs, trunc, t1, t2, s1, tt);
/* evaluate the product polynomial at 2^bits */
r = flint_calloc(n1 + n2, sizeof(mp_limb_t));
fft_combine_bits(r, ii, trunc, bits, limbs, n1 + n2);
mpz_mul(c, a, b);
flint_printf("equal to mpz_mul: %d\n",
mpn_cmp(r, mpz_limbs_read(c), mpz_size(c)) == 0 &&
(mpz_size(c) == (size_t) (n1 + n2) || r[n1 + n2 - 1] == 0));
flint_free(r); flint_free(mem); flint_free(t1); flint_free(ii);
mpz_clear(a); mpz_clear(b); mpz_clear(c);
return 0;
}j1 = 107, j2 = 105, trunc = 211, 4n = 256 equal to mpz_mul: 1
The same program works for larger transforms, where fft_convolution switches to the threaded matrix Fourier algorithm; with and eight limbs it multiplies integers of half a million bits. The scratch arrays must then really have one entry per thread: the number of threads must not be increased between allocating them and the call.
Polynomials with integer coefficients#
For polynomials, each coefficient goes into its own block instead of being cut from an integer. Negative coefficients are stored in two's complement over all limbs, and a coefficient of the result is negative if its residue exceeds ; _fmpz_vec_get_fft and _fmpz_vec_set_fft convert between fmpz vectors and this format. The example in Examples multiplies two polynomials with signed coefficients this way and compares the result with fmpz_poly_mul; the second example there uses the pointwise engine of fft_small. When only some coefficients of the product are wanted, fft_convolution_choose_mid can select a shorter cyclic transform.
Threads#
fft_convolution with , fft_convolution_sd_fft, mul_mfa_truncate_sqrt2 (and so flint_mpn_mul_fft_main for large operands) and the splitting functions run in parallel on the threads of FLINT's global thread pool, as set by flint_set_num_threads. The precached convolutions without the fft_small engine and, apart from the splitting, mul_truncate_sqrt2 run in the calling thread.
The FLINT team, “Multiplying integers and polynomials (fft.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fft/using.html (generated 2026-11-02).
@misc{flint-doc-fft-using,
author = {{The FLINT team}},
title = {{Multiplying integers and polynomials (fft.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/fft/using.html},
note = {Generated 2026-11-02}
}