FLINT3.7.0-dev

Multiplication modulo 2^N + 1

Synopsis#include <flint/fft_small.h>
sd_fft_mpn_mulmod_2expp1_ctx_struct
sd_fft_mpn_mulmod_2expp1_ctx_t
sd_fft_mpn_mulmod_2expp1_scratch_struct
sd_fft_mpn_mulmod_2expp1_scratch_t

A context for one ring \mathbb{Z}/(2^N + 1)\mathbb{Z}, holding N, the digit size b, the digit count m = N/b, the number of primes and the twisting tables, and a scratch object with the mutable state of one multiplication.

void sd_fft_mpn_mulmod_2expp1_ctx_init(sd_fft_mpn_mulmod_2expp1_ctx_struct * C, mpn_ctx_struct * R, slong N, slong m)source
void sd_fft_mpn_mulmod_2expp1_ctx_clear(sd_fft_mpn_mulmod_2expp1_ctx_struct * C)source

Initialises C for multiplication modulo 2^N + 1 with m digits of b = N/m bits, taking the primes from R, normally get_default_mpn_ctx(), and frees it. N must be a positive multiple of FLINT_BITS, and m a power of two at least 16 dividing N, or 0 to let the function choose. Throws if N is not a positive multiple of FLINT_BITS or no configuration exists.

The number of primes is the least k \in [2, 8] whose product exceeds 2^{2b + \log_2 m + 1} and for which digits of b bits can be converted: any b \le 50, or b > 50 with a vectorised conversion for k primes, which exists for b \in \{64, 84, 88, 92, 112, 116, 120, 126, 128, 136, 140, 144, 160, 164, 168, 184, 188, 192\} and suitable k. With m = 0 the digit count is chosen among the powers of two m \ge 16 with m \le N/20, m \mid N and 20 \le b \le 200 by an estimated cost w_k \, k\, m (\log_2 m + 4 + b/16), with weights w_k measured per prime count. For example N = 2^{10}, \ldots, 2^{24} give b = 64 with three primes, N = 47104 gives b = 92 with four primes, and N = 344064 gives b = 42 with two primes. That N is a multiple of FLINT_BITS is not checked: other values give wrong results.

void sd_fft_mpn_mulmod_2expp1_scratch_init(sd_fft_mpn_mulmod_2expp1_scratch_struct * S, const sd_fft_mpn_mulmod_2expp1_ctx_struct * C)source
void sd_fft_mpn_mulmod_2expp1_scratch_clear(sd_fft_mpn_mulmod_2expp1_scratch_struct * S)source

Initialise and free the scratch object for multiplications with C. The context is read-only during multiplication, so several threads can share it, each with its own scratch object.

Sets z = xy \bmod 2^N + 1. z may alias x or y; if x == y a square is computed with one forward transform.

slong sd_fft_mpn_mulmod_2expp1_transformed_size(const sd_fft_mpn_mulmod_2expp1_ctx_struct * C)source
void sd_fft_mpn_mulmod_2expp1_transform(sd_fft_mpn_mulmod_2expp1_ctx_struct * C, double * F, nn_srcptr y, sd_fft_mpn_mulmod_2expp1_scratch_struct * S)source
void sd_fft_mpn_mulmod_2expp1_mul_cached(sd_fft_mpn_mulmod_2expp1_ctx_struct * C, nn_ptr z, nn_srcptr x, const double * F, sd_fft_mpn_mulmod_2expp1_scratch_struct * S)source

Multiplication by a fixed operand. transform stores the transform of y in F, an array of transformed_size(C) doubles; mul_cached then sets z = xy \bmod 2^N + 1, like sd_fft_mpn_mulmod_2expp1 with one forward transform less.

slong sd_fft_mpn_mulmod_2expp1_choose_np(mpn_ctx_struct * R, slong b, slong depth)source

Returns the number of primes the context uses for digits of b bits and m = 2^{\mathtt{depth}} digits, as described for the context, or -1 if there is none.

Schönhage–Strassen multiplication reduces a large product to many products modulo 2^N + 1 for a moderately large N (Schönhage–Strassen multiplication). The fft module computes these pointwise products with the functions here when the ring has at least 16384 bits (FLINT_FFT_SD_FFT_MIN_RING), choosing N with fft_adjust_limbs_sd_fft so that a good digit splitting exists. They compute a product modulo 2^N + 1 as a negacyclic convolution of b-bit digits with the small-prime transforms.

Usage#

  1. Create a context for N with sd_fft_mpn_mulmod_2expp1_ctx_init, passing get_default_mpn_ctx() and m = 0 to let it choose the digit count. N must be a positive multiple of FLINT_BITS; otherwise an exception is raised.
  2. Create one scratch object per thread with sd_fft_mpn_mulmod_2expp1_scratch_init. The context is read-only during multiplications and can be shared.
  3. Multiply with sd_fft_mpn_mulmod_2expp1. Operands and result have n + 1 limbs, n = N / \mathtt{FLINT\_BITS}; operands may have top limb 0 or 1 with arbitrary low limbs, and the result is fully reduced into [0, 2^N]. The result may alias an operand.
  4. For many products with the same y, store its transform once with sd_fft_mpn_mulmod_2expp1_transform in an array of sd_fft_mpn_mulmod_2expp1_transformed_size(C) doubles, aligned to 32 bytes, and multiply with sd_fft_mpn_mulmod_2expp1_mul_cached.

Algorithm#

Write x = \sum_{i<m} x_i 2^{bi} with digits 0 \le x_i < 2^b, where N = mb and a top limb 1 contributes 2^N \equiv -1, which is subtracted from x_0. Since 2^{bm} \equiv -1, the product is \sum_k z_k 2^{bk} with the negacyclic convolution

z_k = \sum_{i + j = k} x_i y_j - \sum_{i + j = k + m} x_i y_j, \qquad |z_k| < m 2^{2b} = 2^{2b + d},

where m = 2^d. Modulo each prime p, with \theta a primitive 2m-th root of unity (\theta^m = -1), the weighted sequences \theta^i x_i and \theta^j y_j have the cyclic convolution \theta^k z_k [CF1994], which a transform of length m computes. The residues are untwisted by \theta^{-k}, the factor m^{-1} of the inverse transform and the Chinese remainder normalisation being folded into the same table. To use the unsigned reconstruction, 2^{2b + d} is added to every coefficient, which makes them lie in [0, 2^{2b + d + 1}), so the product of the primes must be at least 2^{2b + d + 1}. After reconstructing \sum_k (z_k + 2^{2b+d}) 2^{bk} the bias polynomial is subtracted, and the part of the signed result above 2^N is folded back by subtraction.

The number of primes is the least k \ge 2 with enough capacity for which digits of b bits can be converted: digits of at most 50 bits are converted directly, larger ones need one of the vectorised conversions of the integer multiplication profiles. With m = 0 the context compares the admissible digit counts m \ge 16 (with 20 \le b \le 200) by an estimate w_k \, k \, m (d + 4 + b/16) with measured weights w_k per prime count, which favours two primes slightly and penalises eight.

Nmbprimes
2^{10} to 2^{24}N / 64643
10752256422
307202561206
47104512924
3440648192422
AlgorithmMultiplication with several primes and CRTHow fft_small multiplies integers and polynomials whose product coefficients exceed one prime: chunk sizes and the number of primes, the two-prime and multi-prime pipelines, Chinese remaindering with the normalisation folded in, windows computed with wrap-around, sums of products in transform space, precomputed and shared operands, and transposed products for middle products.

Example#

Examplefft_small_mulmod_2expp1.cneeds fft_smallchecked by make checkdoc
#include <flint/flint.h>
#include <flint/fmpz.h>
#include <flint/fft_small.h>

int main(void)
{
    slong N = 65536, n = N / FLINT_BITS, i;
    sd_fft_mpn_mulmod_2expp1_ctx_t C;
    sd_fft_mpn_mulmod_2expp1_scratch_t S;
    nn_ptr x = flint_calloc(n + 1, sizeof(ulong));
    nn_ptr y = flint_calloc(n + 1, sizeof(ulong));
    nn_ptr z = flint_calloc(n + 1, sizeof(ulong));
    fmpz_t X, Y, Z, M;

    sd_fft_mpn_mulmod_2expp1_ctx_init(C, get_default_mpn_ctx(), N, 0);
    sd_fft_mpn_mulmod_2expp1_scratch_init(S, C);
    flint_printf("N = %wd: %wd digits of %wd bits, %wd primes\n",
                 C->N, C->m, C->b, C->np);

    for (i = 0; i < n; i++)
    {
        x[i] = UWORD(0x0123456789abcdef) * (i + 1);
        y[i] = UWORD(0xfedcba9876543210) ^ i;
    }
    y[n] = 0;
    x[n] = 1;       /* x = 2^N + (low limbs), not reduced */

    sd_fft_mpn_mulmod_2expp1(C, z, x, y, S);

    fmpz_init(X); fmpz_init(Y); fmpz_init(Z); fmpz_init(M);
    fmpz_set_ui_array(X, x, n + 1);
    fmpz_set_ui_array(Y, y, n + 1);
    fmpz_one(M);
    fmpz_mul_2exp(M, M, N);
    fmpz_add_ui(M, M, 1);
    fmpz_mul(Z, X, Y);
    fmpz_mod(Z, Z, M);
    fmpz_set_ui_array(X, z, n + 1);
    flint_printf("x*y mod 2^N + 1: %s\n", fmpz_equal(X, Z) ? "agrees" : "WRONG");

    fmpz_clear(X); fmpz_clear(Y); fmpz_clear(Z); fmpz_clear(M);
    sd_fft_mpn_mulmod_2expp1_scratch_clear(S);
    sd_fft_mpn_mulmod_2expp1_ctx_clear(C);
    flint_free(x);
    flint_free(y);
    flint_free(z);
    flint_cleanup_master();
    return 0;
}
Output
N = 65536: 1024 digits of 64 bits, 3 primes
x*y mod 2^N + 1: agrees

References#

  • [CF1994] R. Crandall and B. Fagin. Discrete weighted transforms and large-integer arithmetic. Mathematics of Computation 62 (1994), 305–324.
Cite this page

The FLINT team, “Multiplication modulo 2^N + 1 (fft_small.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fft_small/mulmod_2expp1.html (generated 2026-10-20).