FLINT3.7.0-dev

Multiplication modulo 2^N + 1

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

Sets r to the product of the reduced residues i1 and i2 modulo 2^{nw} + 1, reduced, where nw is a multiple of \mathtt{FLINT\_BITS}. 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 nw/\mathtt{FLINT\_BITS} 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 2nw/\mathtt{FLINT\_BITS} limbs.

slong fft_adjust_limbs(mp_size_t limbs)source

Returns the least limb count \ge 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.

slong fft_adjust_limbs_sd_fft(slong tight, slong n_outer, slong * m_out)source

Chooses the coefficient ring of a Schönhage–Strassen convolution with transform parameter n = n_outer and coefficients of at least tight limbs, taking the negacyclic engine of fft_small into account. If a ring N' = mb suitable for that engine is found, sets *m_out to its digit count m and returns N'/\mathtt{FLINT\_BITS}; 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_RINGsource

The smallest ring size N 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)source

Sets r1 to the reduced product of i1 and i2 modulo 2^N + 1, N = \mathit{r\_limbs} \cdot \mathtt{FLINT\_BITS}, with a negacyclic transform of length 2n, n = 2^{\mathit{depth}}, modulo 2^{nw} + 1 combined with a one-limb convolution. The inputs must be less than 2^N (top limbs zero), and depth and w must be as chosen by fft_mulmod_2expp1: nw = N/n, with N/(2n) a multiple of \mathtt{FLINT\_BITS}.

void fft_naive_convolution_1(mp_limb_t * r, mp_limb_t * ii, mp_limb_t * jj, mp_size_t m)source

Sets r_k = \sum_{i + j = k} \mathit{ii}_i \mathit{jj}_j - \sum_{i + j = k + m} \mathit{ii}_i \mathit{jj}_j \bmod B for 0 \le k < m: the negacyclic convolution of two vectors of m limbs, with wrapping word arithmetic. r must not alias the inputs.

The pointwise products of a Schönhage–Strassen convolution are products modulo 2^N + 1. fft_mulmod_2expp1 computes them, by a full product followed by a reduction for N up to 8192 bits and by a negacyclic convolution, the original idea of Schönhage and Strassen [SS1971], for larger N. Since the negacyclic convolution needs N 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 \mathit{limbs} + 1 limbs, where \mathit{limbs} = nw/\mathtt{FLINT\_BITS}: only the product nw matters, and fft_convolution passes the transform parameters n and w of the outer transform. The function accepts any N up to FFT_MULMOD_2EXPP1_CUTOFF limbs, which is 128 on all targets. Beyond that, N 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 2^{64 \cdot 129} + 1 or 2^{64 \cdot 1000} + 1 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 2 \cdot \mathit{limbs} limbs of scratch space for the full product of the basecase; FLINT's callers pass 2(\mathit{limbs} + 1) limbs. The output may alias either input.

Algorithm#

If one of the inputs is 2^N \equiv -1, the result is the negation of the other. Up to the cutoff, flint_mpn_mulmod_2expp1_basecase computes the full 2N-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 2n pieces of b = N/(2n) bits, with n = 2^{\mathit{depth}} and \mathit{depth} = \lfloor \lceil \log_2 N \rceil / 2 \rfloor - k, where k comes from the tuning table MULMOD_TAB (between 1 and 4). Since 2^{2nb} = 2^N \equiv -1, the product modulo 2^N + 1 is the negacyclic convolution of the pieces, evaluated at 2^b. The convolution is computed with fft_negacyclic and ifft_negacyclic in the ring \mathbb{Z}/(2^{2b} + 1)\mathbb{Z}, with flint_mpn_mulmod_2expp1_basecase for its pointwise products, and, to recover coefficients that exceed 2^{2b}, combined by the Chinese remainder theorem with the same convolution modulo B, 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 2^N + 1. Some values:

N (bits)pieces 2npiece bits binner ring bits 2b
163841610242048
655363220484096
26214412820484096
104857625640968192
4194304512819216384

(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 d_1 chosen for limbs and for the next power of two divides it correctly: the limb count becomes a multiple of 2^{d_1 + 1} and the bit count a multiple of 2^{2 d_1}, where d_1 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 N' = mb with m \ge 16 a power of two and b 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 = n_outer and coefficients of at least tight limbs:

  • m is the least power of two \ge 16 with mb \ge \mathtt{FLINT\_BITS} \cdot \mathit{tight};
  • mb must be a multiple of \mathtt{FLINT\_BITS}, at least FLINT_FFT_SD_FFT_MIN_RING = 16384 bits, and a multiple of 2n, so that the outer transform has an even number w of bits per point;
  • the number of primes needed for 2b + \log_2 m + 1 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 \mathit{np} \cdot m \cdot (\log_2 m + 4 + b/16) is chosen.

It returns the limb count mb/\mathtt{FLINT\_BITS} and stores m in *m_out, or returns fft_adjust_limbs(tight) with *m_out = 0 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 m \ne 0; see the second example in Examples.

Example#

A product modulo 2^N + 1 with N near 64000 bits, checked against GMP.

Examplefft_mulmod_2expp1.cchecked by make checkdoc
#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;
}
Output
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.
Cite this page

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