FLINT3.7.0-dev

Multiplying limb arrays

Synopsis#include <flint/mpn_extras.h>
mp_limb_t flint_mpn_mul(mp_ptr z, mp_srcptr x, mp_size_t xn, mp_srcptr y, mp_size_t yn)source

Sets (z,xn+yn)(z, \mathit{xn} + \mathit{yn}) to the product of (x,xn)(x, \mathit{xn}) and (y,yn)(y, \mathit{yn}) and returns its top limb zxn+yn−1z_{xn + yn - 1}. Requires xn≥yn≥1\mathit{xn} \ge \mathit{yn} \ge 1; zz must not overlap the inputs.

For short operands this calls a hard-coded routine from a table (assembly on x86-64 with ADX for xn≤16\mathit{xn} \le 16 and on ARM64 for yn≤15\mathit{yn} \le 15; portable C for xn≤7\mathit{xn} \le 7 elsewhere); otherwise _flint_mpn_mul selects between mpn_mul_1, GMP's mpn_mul_basecase (only without FLINT assembly), flint_mpn_mul_toom22, GMP's mpn_mul and the FFT.

void flint_mpn_mul_n(mp_ptr z, mp_srcptr x, mp_srcptr y, mp_size_t n)source

Sets (z,2n)(z, 2n) to the product of (x,n)(x, n) and (y,n)(y, n). Requires n≥1n \ge 1; zz must not overlap the inputs.

mp_limb_t flint_mpn_sqr(mp_ptr z, mp_srcptr x, mp_size_t n)source

Sets (z,2n)(z, 2n) to the square of (x,n)(x, n) and returns its top limb. Requires n≥1n \ge 1; zz must not overlap xx.

void flint_mpn_mul_shared(mp_ptr z1, mp_srcptr a, mp_size_t an, mp_ptr z2, mp_srcptr b, mp_size_t bn, mp_srcptr x, mp_size_t xn)source

Sets (z1,an+xn)(z_1, \mathit{an} + \mathit{xn}) to axax and (z2,bn+xn)(z_2, \mathit{bn} + \mathit{xn}) to bxbx. All lengths must be at least 1, in any order; z1z_1 and z2z_2 must overlap neither each other nor any input. When both products are long enough, the transform of xx is computed only once (Integer products with a common operand).

flint_mpn_mul, flint_mpn_mul_n and flint_mpn_sqr are FLINT's replacements for GMP's mpn_mul, mpn_mul_n and mpn_sqr, with the same interface except for the order requirement of flint_mpn_mul (xn≥yn\mathit{xn} \ge \mathit{yn}) and the returned top limb. They are faster than GMP's functions at both ends of the size range: for short operands because of hand-written assembly routines for each size, and for long operands because of the small-prime FFT of fft_small. flint_mpn_mul_shared computes two products with a common factor.

OverviewMultiplying integersHow FLINT multiplies integers of each size — its own assembly basecases, Karatsuba, GMP's Toom–Cook algorithms, and FFT multiplication with fft_small or Schönhage–Strassen — with the algorithms, their costs and the dispatch in flint_mpn_mul.

Algorithm selection#

The functions are inline. On x86-64 with the ADX and BMI2 instructions (Broadwell and later, Zen and later) and on ARM64, products of short operands are computed by calling one routine from a table of hard-coded assembly kernels, with no further dispatch:

Architectureflint_mpn_mulflint_mpn_mul_nflint_mpn_sqr
x86-64 with ADXxn≤16\mathit{xn} \le 16 (any yn≤xn\mathit{yn} \le \mathit{xn})n≤16n \le 16n≤14n \le 14
ARM64yn≤15\mathit{yn} \le 15 (any xn\mathit{xn})n≤15n \le 15n≤9n \le 9
otherxn≤7\mathit{xn} \le 7, or xn≤14\mathit{xn} \le 14 and yn=1\mathit{yn} = 1n≤7n \le 7none

On machines without FLINT assembly the table holds portable C code. The ARM64 kernels are indexed by the shorter length and loop over the longer operand, so they serve any xn\mathit{xn}. Beyond the table, _flint_mpn_mul uses, in order:

  1. mpn_mul_1 if yn=1\mathit{yn} = 1 (on ARM64 also flint_mpn_mul_2 if yn=2\mathit{yn} = 2); on machines without FLINT assembly, GMP's mpn_mul_basecase for xn≤16\mathit{xn} \le 16;
  2. on x86-64 with ADX and on ARM64, flint_mpn_mul_toom22 (Karatsuba recursing into the assembly kernels) if the operands are nearly balanced, 5 yn≥4 xn5\,\mathit{yn} \ge 4\,\mathit{xn}, and yn\mathit{yn} is at most 230 (x86-64) or 483 (ARM64);
  3. GMP's mpn_mul (Toom–Cook) below the FFT range;
  4. the FFT: mpn_mul_default_mpn_ctx from fft_small if available, and otherwise the Schönhage–Strassen FFT of the fft module (flint_mpn_mul_fft_main, from FLINT_FFT_MUL_THRESHOLD limbs).

With fft_small and the threshold TT = FLINT_FFT_SMALL_MUL_THRESHOLD (1000 limbs in the generic flint-mparam.h, between 240 and 1000 limbs in the machine-specific ones), the FFT is used when yn≥T\mathit{yn} \ge T, and for unbalanced operands already when xn+yn>2T\mathit{xn} + \mathit{yn} > 2T and yn>T/3\mathit{yn} > T/3: the cost of GMP's multiplication grows like (xn/yn)M(yn)(\mathit{xn}/\mathit{yn}) M(\mathit{yn}) while that of the FFT depends on xn+yn\mathit{xn} + \mathit{yn}. _flint_mpn_mul_n follows the same steps, and _flint_mpn_sqr uses GMP's mpn_sqr below FLINT_FFT_SMALL_SQR_THRESHOLD and the FFT above.

Shared operands#

flint_mpn_mul_shared computes axax and bxbx. With fft_small, when _flint_mpn_mul_shared_use_fft says so, it calls _fft_small_mpn_mul_shared, which transforms xx once and uses the transform for both products; otherwise it makes two calls to flint_mpn_mul. With TT = FLINT_MPN_MUL_SHARED_THRESHOLD (by default the fft_small multiplication threshold), the shared transform is used when the longer product (by max⁡(an,bn)\max(\mathit{an}, \mathit{bn})) satisfies the FFT rule of flint_mpn_mul above with threshold TT and the shorter product satisfies it with threshold T/2T/2: once the longer product is transformed, the shorter one costs about its share of the transforms and need not reach the FFT range on its own. This is used for instance by the Chinese remaindering trees of Multimodular reduction and Chinese remaindering, where each node is multiplied by two cofactors.

Example#

The product of two 2000-limb numbers (in the fft_small range) agrees with GMP's, and a shared product agrees with two separate ones:

Examplempn_mul_basic.cneeds fft_smallchecked by make checkdoc
#include <gmp.h>
#include <flint/flint.h>
#include <flint/mpn_extras.h>

#define N 2000

int main(void)
{
    flint_rand_t state;
    mp_limb_t x[N], y[N], a[N / 2], z[2 * N], w[2 * N], z1[2 * N], z2[2 * N];

    flint_rand_init(state);
    flint_mpn_urandomb(x, state, N * FLINT_BITS);
    flint_mpn_urandomb(y, state, N * FLINT_BITS);
    flint_mpn_urandomb(a, state, N / 2 * FLINT_BITS);

    flint_mpn_mul_n(z, x, y, N);
    mpn_mul_n(w, x, y, N);
    flint_printf("mul_n agrees with GMP: %d\n", mpn_cmp(z, w, 2 * N) == 0);

    flint_mpn_sqr(z, x, N);
    mpn_sqr(w, x, N);
    flint_printf("sqr agrees with GMP: %d\n", mpn_cmp(z, w, 2 * N) == 0);

    /* z1 = y x and z2 = a x with one transform of x */
    flint_mpn_mul_shared(z1, y, N, z2, a, N / 2, x, N);
    flint_printf("shared transform: %d\n", _flint_mpn_mul_shared_use_fft(N, N / 2, N));
    flint_mpn_mul_n(z, y, x, N);
    flint_mpn_mul(w, x, N, a, N / 2);
    flint_printf("shared products agree: %d\n",
        mpn_cmp(z1, z, 2 * N) == 0 && mpn_cmp(z2, w, 3 * N / 2) == 0);

    flint_rand_clear(state);
    return 0;
}
Output
mul_n agrees with GMP: 1
sqr agrees with GMP: 1
shared transform: 1
shared products agree: 1

Guides#

Cite this page

The FLINT team, “Multiplying limb arrays (mpn_extras.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/mpn_extras/mul.html (generated 2026-11-02).