Multiplication modulo 2^N + 1
#include <flint/fft.h>void fft_mulmod_2expp1(mp_limb_t * r, mp_limb_t * i1, mp_limb_t * i2, mp_size_t n, mp_size_t w, mp_limb_t * tt)sourceSets r to the product of the reduced residues i1 and i2 modulo , reduced, where is a multiple of . Up to FFT_MULMOD_2EXPP1_CUTOFF limbs (128 on all targets) it uses flint_mpn_mulmod_2expp1_basecase. Above, it uses a negacyclic FFT convolution, which requires the limb count to be a value returned by fft_adjust_limbs; for other limb counts the result is wrong. r may alias the inputs. tt must have room for limbs.
Returns the least limb count limbs that fft_mulmod_2expp1 can handle: limbs itself if it is at most FFT_MULMOD_2EXPP1_CUTOFF, and otherwise a value rounded up to a suitable multiple, at most the next power of two.
Chooses the coefficient ring of a Schönhage–Strassen convolution with transform parameter = n_outer and coefficients of at least tight limbs, taking the negacyclic engine of fft_small into account. If a ring suitable for that engine is found, sets *m_out to its digit count and returns ; otherwise sets *m_out to zero and returns fft_adjust_limbs(tight). Without fft_small it always does the latter.
FLINT_FFT_SD_FFT_MIN_RINGsourceThe smallest ring size in bits (16384) for which fft_adjust_limbs_sd_fft selects the negacyclic engine.
void _fft_mulmod_2expp1(mp_limb_t * r1, mp_limb_t * i1, mp_limb_t * i2, mp_size_t r_limbs, flint_bitcnt_t depth, flint_bitcnt_t w)sourceSets r1 to the reduced product of i1 and i2 modulo , , with a negacyclic transform of length , , modulo combined with a one-limb convolution. The inputs must be less than (top limbs zero), and depth and w must be as chosen by fft_mulmod_2expp1: , with a multiple of .
void fft_naive_convolution_1(mp_limb_t * r, mp_limb_t * ii, mp_limb_t * jj, mp_size_t m)sourceSets for : the negacyclic convolution of two vectors of limbs, with wrapping word arithmetic. r must not alias the inputs.
The pointwise products of a Schönhage–Strassen convolution are products modulo . fft_mulmod_2expp1 computes them, by a full product followed by a reduction for up to 8192 bits and by a negacyclic convolution, the original idea of Schönhage and Strassen [SS1971], for larger . Since the negacyclic convolution needs to have a special form, the outer convolution must choose its coefficient ring with fft_adjust_limbs (or fft_adjust_limbs_sd_fft, which also considers the faster pointwise engine of fft_small).
Usage#
The operands are reduced residues of limbs, where : only the product matters, and fft_convolution passes the transform parameters and of the outer transform. The function accepts any up to FFT_MULMOD_2EXPP1_CUTOFF limbs, which is 128 on all targets. Beyond that, must be a value produced by fft_adjust_limbs; for other values the result is silently wrong. For example, fft_adjust_limbs maps 129 to 144, 200 to 208, 1000 to 1024 and 12345 to 12544, and products modulo or come out wrong. Every power of two and every multiple of three by a power of two above 128 is returned unchanged.
tt must provide limbs of scratch space for the full product of the basecase; FLINT's callers pass limbs. The output may alias either input.
Algorithm#
If one of the inputs is , the result is the negation of the other. Up to the cutoff, flint_mpn_mulmod_2expp1_basecase computes the full -bit product with flint_mpn_mul_n (or flint_mpn_sqr) and subtracts the high half from the low half. Beyond the cutoff, _fft_mulmod_2expp1 cuts both operands into pieces of bits, with and , where comes from the tuning table MULMOD_TAB (between 1 and 4). Since , the product modulo is the negacyclic convolution of the pieces, evaluated at . The convolution is computed with fft_negacyclic and ifft_negacyclic in the ring , with flint_mpn_mulmod_2expp1_basecase for its pointwise products, and, to recover coefficients that exceed , combined by the Chinese remainder theorem with the same convolution modulo , computed by fft_naive_convolution_1 from the lowest limb of each piece. The proof that this gives the exact coefficients is in Multiplication modulo . Some values:
| (bits) | pieces | piece bits | inner ring bits |
|---|---|---|---|
| 16384 | 16 | 1024 | 2048 |
| 65536 | 32 | 2048 | 4096 |
| 262144 | 128 | 2048 | 4096 |
| 1048576 | 256 | 4096 | 8192 |
| 4194304 | 512 | 8192 | 16384 |
(generic tuning; the inner products are handled by the basecase, so there is a single level of recursion).
fft_adjust_limbs rounds limbs up so that the depth chosen for limbs and for the next power of two divides it correctly: the limb count becomes a multiple of and the bit count a multiple of , where is the larger of the two depths.
The negacyclic engine of fft_small#
In builds with fft_small, sd_fft_mpn_mulmod_2expp1 computes the same products with a negacyclic convolution modulo three to eight word-size primes in floating point, for rings with a power of two and one of 18 instantiated digit sizes between 64 and 192 bits. fft_adjust_limbs_sd_fft(tight, n_outer, m_out) looks for such a ring for a convolution with transform parameter = n_outer and coefficients of at least tight limbs:
- is the least power of two with ;
- must be a multiple of , at least
FLINT_FFT_SD_FFT_MIN_RINGbits, and a multiple of , so that the outer transform has an even number of bits per point; - the number of primes needed for bits must be available, and the ring may exceed
fft_adjust_limbs(tight)by at most 35, 25, 15, 12, 5 or 2 percent for 3, 4, …, 8 primes; - among the admissible rings, the one with the least estimated cost is chosen.
It returns the limb count and stores in *m_out, or returns fft_adjust_limbs(tight) with *m_out if no ring qualifies or fft_small is not available. The caps and weights are tuning parameters. fmpz_poly and fmpzi select their rings this way and create a context with sd_fft_mpn_mulmod_2expp1_ctx_init when ; see the second example in Examples.
Example#
A product modulo with near 64000 bits, checked against GMP.
#include <gmp.h>
#include <flint/flint.h>
#include <flint/fft.h>
#include <flint/mpn_extras.h>
int main(void)
{
/* multiply modulo 2^N + 1 with N a multiple of 64 near 64000 bits */
slong limbs = fft_adjust_limbs(1000);
mp_limb_t * a, * b, * r, * tt;
mpz_t A, B, R, P;
flint_printf("limbs: 1000 -> %wd\n", limbs);
a = flint_calloc(limbs + 1, sizeof(mp_limb_t));
b = flint_calloc(limbs + 1, sizeof(mp_limb_t));
r = flint_calloc(limbs + 1, sizeof(mp_limb_t));
tt = flint_malloc(2 * (limbs + 1) * sizeof(mp_limb_t));
mpz_init(A); mpz_init(B); mpz_init(R); mpz_init(P);
mpz_ui_pow_ui(P, 2, limbs * FLINT_BITS);
mpz_add_ui(P, P, 1);
mpz_ui_pow_ui(A, 3, 40000);
mpz_ui_pow_ui(B, 5, 27000);
mpz_mod(A, A, P);
mpz_mod(B, B, P);
/* reduced operands: limbs + 1 limbs, top limb 0 (or 1 for 2^N) */
mpz_export(a, NULL, -1, sizeof(mp_limb_t), 0, 0, A);
mpz_export(b, NULL, -1, sizeof(mp_limb_t), 0, 0, B);
/* n * w = N; here n = limbs and w = FLINT_BITS */
fft_mulmod_2expp1(r, a, b, limbs, FLINT_BITS, tt);
fermat_to_mpz(R, r, limbs);
mpz_mul(A, A, B);
mpz_mod(A, A, P);
flint_printf("equal to mpz: %d\n", mpz_cmp(A, R) == 0);
flint_free(a); flint_free(b); flint_free(r); flint_free(tt);
mpz_clear(A); mpz_clear(B); mpz_clear(R); mpz_clear(P);
return 0;
}limbs: 1000 -> 1024 equal to mpz: 1
Guides#
References#
- [SS1971] A. Schönhage and V. Strassen. Schnelle Multiplikation großer Zahlen. Computing 7 (1971), 281–292.
The FLINT team, “Multiplication modulo 2^N + 1 (fft.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fft/mulmod.html (generated 2026-11-02).
@misc{flint-doc-fft-mulmod,
author = {{The FLINT team}},
title = {{Multiplication modulo 2\textasciicircum{}N + 1 (fft.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/fft/mulmod.html},
note = {Generated 2026-11-02}
}