FLINT3.7.0-dev
Algorithm

Multiplication with several primes and CRT

Cost
For two nn-limb integers, transforms of about 128n/β128 n / \beta points modulo kk primes with β≈25k\beta \approx 25k, about 5n5n points in all, so O(nlog⁡n)O(n \log n), plus O(k2)O(k^2) word operations per coefficient for the reconstruction.

A number-theoretic transform modulo a 50-bit prime pp multiplies polynomials over Z/pZ\mathbb{Z}/p\mathbb{Z}. To multiply integers, or polynomials over Z\mathbb{Z} or Z/nZ\mathbb{Z}/n\mathbb{Z}, the product is first computed over Z\mathbb{Z}: its coefficients are bounded, so they can be computed modulo several primes p0,…,pk−1p_0, \ldots, p_{k-1} whose product PP exceeds the bound, and recovered with the Chinese remainder theorem. This multimodular approach goes back to Pollard [Pol1971] (see also [Knu1997, §4.3.3] and [GG2003]); it is the method of FLINT's fft_small module, whose transforms are described in FFTs modulo word-size primes in floating point.

The primes are the eight primes of an mpn_ctx_t, all of 50 bits, with 241∣pi−12^{41} \mid p_i - 1; FLINT's default primes are listed with mpn_ctx_struct. Pk=p0⋯pk−1P_k = p_0 \cdots p_{k-1} has 50, 100, 150, 199, 249, 299, 348 and 398 bits for k=1,…,8k = 1, \ldots, 8.

Exactness#

Theorem 1. Let a=∑i<ℓaaixia = \sum_{i < \ell_a} a_i x^i and b=∑j<ℓbbjxjb = \sum_{j < \ell_b} b_j x^j with integer coefficients 0≤ai,bj<2β0 \le a_i, b_j < 2^\beta, and let KK such products be added. Every coefficient cc of the sum satisfies 0≤c<Kmin⁡(ℓa,ℓb) 22β0 \le c < K \min(\ell_a, \ell_b) \, 2^{2\beta}, so it is determined by its residues modulo p0,…,pk−1p_0, \ldots, p_{k-1} if Kmin⁡(ℓa,ℓb) 22β≤PkK \min(\ell_a, \ell_b) \, 2^{2 \beta} \le P_k. If the products are added and subtracted and ∣c∣<Pk/2|c| < P_k / 2, cc is determined as the representative of its residue class in (−Pk/2,Pk/2](-P_k/2, P_k/2].

Proof. A coefficient of abab is a sum of at most min⁡(ℓa,ℓb)\min(\ell_a, \ell_b) products aibj<22βa_i b_j < 2^{2\beta}. The Chinese remainder theorem determines cc modulo PkP_k, and there is only one representative in an interval of length PkP_k.

The drivers use slightly different bounds, all of this form:

OperandsBound ℓ 22β\ell \, 2^{2\beta} used
integers cut into β\beta-bit chunks⌈64bn/β⌉22β\lceil 64 b_n / \beta \rceil 2^{2\beta} (bnb_n limbs of the second operand)
polynomials over Z/nZ\mathbb{Z}/n\mathbb{Z}, nn of kk bitsbn22kb_n 2^{2k}
polynomials over Z\mathbb{Z} with βa\beta_a- and βb\beta_b-bit coefficientsbn2βa+βb+1b_n 2^{\beta_a + \beta_b + 1} (symmetric range)
sums of products in transform spacelen_bound ⋅2prod_bits\cdot 2^{\mathtt{prod\_bits}}

Reconstruction#

Lemma 2 (Chinese remaindering with folded normalisation). Let Qi=Pk/piQ_i = P_k / p_i and ei=Qi mod pie_i = Q_i \bmod p_i. If yi≡c ei−1(modpi)y_i \equiv c \, e_i^{-1} \pmod{p_i} with 0≤yi<pi0 \le y_i < p_i, then s=∑i<kyiQis = \sum_{i < k} y_i Q_i satisfies s≡c(modPk)s \equiv c \pmod{P_k} and 0≤s<kPk0 \le s < k P_k.

Proof. Modulo pip_i every term but the ii-th vanishes, and the ii-th is yiQi≡cei−1ei=cy_i Q_i \equiv c e_i^{-1} e_i = c. Each term is less than piQi=Pkp_i Q_i = P_k.

So c=s−jPkc = s - jP_k for the unique j<kj < k with s−jPk<Pks - jP_k < P_k, which the code finds by repeated subtraction. The factor ei−1e_i^{-1} costs nothing: it is multiplied into the constant mi=(ei2L)−1 mod pim_i = (e_i 2^L)^{-1} \bmod p_i that the pointwise product applies anyway to undo the factor 2L2^L of the inverse transform. A context stores, for each kk, the cofactors QiQ_i and PkP_k as multi-limb integers of a common length large enough to hold kPkk P_k (1, 2, 3, 4, 4, 5, 6 and 7 limbs), and the reduced cofactors eie_i. Reconstructing one coefficient costs kk products of a limb by a cofactor of up to k−1k - 1 limbs, and a few subtractions: O(k2)O(k^2) word operations, specialised at compile time for each kk.

For two primes the reconstruction is Garner's formula [Gar1959]: with ri≡c(modpi)r_i \equiv c \pmod{p_i} in [0,pi)[0, p_i),

c=r0+p0((r1−r0) p0−1 mod p1),c = r_0 + p_0 \left( (r_1 - r_0) \, p_0^{-1} \bmod p_1 \right),

which lies in [0,p0p1)[0, p_0 p_1). The two-prime pipeline computes it in floating point on vectors of eight, with the normalisations 2−L2^{-L} applied in the same step, and then needs only one 50×5050 \times 50-bit product and an addition per coefficient.

The result is then converted to the output: for integers, the coefficients are added into the product with carries; for polynomials over Z/nZ\mathbb{Z}/n\mathbb{Z}, the integer cc of up to seven limbs is reduced modulo nn (with two- and three-limb reductions for k≤3k \le 3); for polynomials over Z\mathbb{Z}, cc is lifted to (−Pk/2,Pk/2](-P_k/2, P_k/2] and stored as an fmpz. With one prime pp and n≠pn \ne p, c<pc < p is simply reduced modulo nn.

Integers#

To multiply integers of ana_n and bnb_n limbs, both are cut into chunks of β\beta bits, a=∑iai2βia = \sum_i a_i 2^{\beta i}, giving polynomials of lengths ℓa=⌈64an/β⌉\ell_a = \lceil 64 a_n / \beta \rceil and ℓb=⌈64bn/β⌉\ell_b = \lceil 64 b_n / \beta \rceil. Their product ∑kckxk\sum_k c_k x^k evaluated at x=2βx = 2^\beta is abab. Larger chunks give shorter transforms but need more primes, since the chunk products have 2β2\beta bits; roughly 2β+log⁡2ℓb≤log⁡2Pk2 \beta + \log_2 \ell_b \le \log_2 P_k, so β≈25k\beta \approx 25 k and the number of transform points per limb of the product, 64k/β64 k / \beta, is nearly independent of kk (it slowly decreases with kk). The real trade-off is between the cost of the transforms, which grows with klog⁡ℓk \log \ell, and that of the conversions, which is smaller for fewer, smaller chunks.

The two-prime pipeline#

Up to 40000 limbs (FLINT_MPN_MUL_NP2_MAX_AN) FLINT uses k=2k = 2 and β∈[40,45]\beta \in [40, 45], the depth dd being the least with ℓa+ℓb−1≤2d\ell_a + \ell_b - 1 \le 2^d and β\beta then the largest with ℓb22β<P2\ell_b 2^{2\beta} < P_2 (_fft_small_np2_choose; P2P_2 has 99 bits). The chunks are smaller than both primes, so the input conversion is plain bit slicing (one unaligned 64-bit load per chunk on x86-64); the reconstruction is the vectorised Garner step; and each coefficient, below 21002^{100}, is added at bit offset kβk \beta into a sliding window of four limbs. In this range conversion and reconstruction dominate the cost of the wider pipeline, which this avoids.

Profiles#

Above 40000 limbs the chunk size and the number of primes come from a table of fifteen profiles, each with a vectorised conversion that reduces the β\beta-bit chunks modulo all kk primes at once, using tables of 2j mod pi2^j \bmod p_i for j<256j < 256 and four primes per vector. A profile applies to second operands of at most bn_bound limbs, the largest bnb_n with ⌈64bn/β⌉22β≤Pk\lceil 64 b_n / \beta \rceil 2^{2\beta} \le P_k:

kkβ\betabn_boundkkβ\betabn_bound
484253663751161442337
488103805837160613493872
4924239071642456369
51123282270071689826
511613279081842177184315
512053481888689506
6136144789875819234662
6140582218

For each kk the profile with the largest admissible β\beta is a candidate, and the candidate with the smallest estimated cost

(1−r4)k d t,t=the product length rounded up to a multiple of 256,2d≥t,r=t/2d,\left(1 - \frac{r}{4}\right) k \, d \, t, \qquad t = \text{the product length rounded up to a multiple of 256}, \quad 2^d \ge t, \quad r = t / 2^d,

is chosen; the factor 1−r/41 - r/4 rewards truncated transforms that fill little of their length. When the work is spread over several threads, only profiles whose kk is a multiple of the number of threads qualify, so that each thread transforms the same number of primes. If bnb_n exceeds even the largest bound, a scalar conversion with a computed chunk size is used instead. For balanced products with one thread this gives:

an=bna_n = b_n (limbs)5005000400005000010510^510610^610710^710810^8
kk22255676
β\beta444341116116136160136
dd1114171617202327

Reassembly#

Coefficient ckc_k starts at bit kβk \beta and occupies the limb count of the Chinese remainder data, so consecutive coefficients overlap and are added with carries. The reconstruction is split into ranges of whole blocks of 256 coefficients, one per thread: each writes the limbs that only its coefficients reach and returns the few limbs that spill into the next range, which are added afterwards in one serial pass.

Windows and wrap-around#

Often only part of a product is needed: the high limbs of an integer product, the low or middle coefficients of a polynomial product Middle products and the transposition principle. Only the coefficients in the window, or for integers the chunk coefficients ckc_k with clo≤k<chic_{\mathrm{lo}} \le k < c_{\mathrm{hi}} whose bits reach the limb window, need to be reconstructed. Moreover, the transform can be shorter than the product.

Theorem 3 (Wrap-around). Let c=abc = ab have length znz_n, and let 2d≥ℓa,ℓb2^d \ge \ell_a, \ell_b. The cyclic convolution of length 2d2^d of aa and bb has coefficients ck+ck+2dc_k + c_{k + 2^d} for k<2dk < 2^d. If zn≤zl+2dz_n \le z_l + 2^d, these equal ckc_k for all zl≤k<min⁡(2d,zn)z_l \le k < \min(2^d, z_n); so if also zh≤2dz_h \le 2^d, the window [zl,zh)[z_l, z_h) of cc is computed by a transform of length 2d2^d.

Proof. Modulo x2d−1x^{2^d} - 1, the term ckxkc_k x^k for k≥2dk \ge 2^d becomes ckxk−2dc_k x^{k - 2^d}; since zn=ℓa+ℓb−1<2d+1z_n = \ell_a + \ell_b - 1 < 2^{d+1}, no coefficient wraps twice. For k≥zlk \ge z_l the wrapped coefficient ck+2dc_{k + 2^d} has index at least zl+2d≥znz_l + 2^d \ge z_n, so it is zero.

The plans use it when max⁡(ℓa,ℓb,zh)≤2d≤zn≤zl+2d\max(\ell_a, \ell_b, z_h) \le 2^d \le z_n \le z_l + 2^d for 2d2^d the largest power of two not exceeding znz_n. For a middle product of lengths 2m2m and mm, the window [m−1,2m)[m-1, 2m) of a product of length 3m−13m - 1, this applies when a power of two lies between 2m2m and 3m−13m - 1, and the transform then has that length instead of a truncated length of about 3m3m; for a high product of integers it can halve the transform (_mpn_ctx_mpn_mul_range uses it on the multi-prime pipeline only, above 40000 limbs). Transposed products (Transposed products) give the shorter length for middle products without this condition. For integers the coefficients below cloc_{\mathrm{lo}} are dropped. Their contribution to the window is a carry of at most a few units into its lowest limb, so the window is a lower approximation, as for every high or middle product of integers in FLINT.

Polynomials over Z/nZ\mathbb{Z}/n\mathbb{Z} and Z\mathbb{Z}#

For polynomials over Z/nZ\mathbb{Z}/n\mathbb{Z} the coefficients are converted to residues modulo each prime directly (with one reduction if n>pin > p_i). The number of primes is the least kk with bn22⌈log⁡2n⌉≤Pkb_n 2^{2 \lceil \log_2 n \rceil} \le P_k: one up to about 16 bits, two up to about 44 bits and three above, depending slowly on the length (Number of primes); the lengths from which nmod_poly uses these products, with the number of primes at each cutoff, are tabulated in Cutoffs, and Which pipeline and how many primes summarises the choices for integers and polynomials. There are two shortcuts:

  • If nn is itself one of the context primes, a single transform modulo nn gives the result directly.
  • If nn is a prime of at least 20 bits that fft_small can use, that is 2d∣n−12^d \mid n - 1 for the transform length 2d2^d and nn passes the arithmetic check, and the second operand has at least 100 coefficients, the transform is computed modulo nn itself, with twiddle tables computed for the call. This replaces two or three primes by one.

For very small moduli nmod_poly reduces the work further by Kronecker substitution Kronecker substitution: for n≤23n \le 23 it packs two coefficients into one, a2i+a2i+1Ma_{2i} + a_{2i+1} M with M=216M = 2^{16} or 2172^{17}, provided norm bounds show that the packed product does not overflow modulo p0p_0, and multiplies the half-length polynomials modulo p0p_0 with one prime.

For polynomials over Z\mathbb{Z}, signed coefficients are reduced to residues, and the result is lifted to the symmetric range, which needs one more bit. With eight primes, products whose coefficients exceed about 397 bits are beyond reach; FLINT's fmpz_poly multiplication then uses Kronecker substitution or Schönhage–Strassen Schönhage–Strassen multiplication, whose pointwise products modulo 2N+12^N + 1 are themselves computed with this module (Multiplication modulo 2^N + 1).

Sums of products and fixed operands#

The transform is linear, so a sum of products ∑jajbj\sum_j a_j b_j can be formed pointwise and transformed back once: each operand is transformed once, each product costs one pointwise multiplication, and only one inverse transform and one reconstruction are needed. The bound of Theorem 1 must then allow KK products; for differences of products the result must be read in the symmetric range, which costs one more bit. Products carry the normalisation constant mim_i, so a product must not be multiplied again; sums of primal operands, on the other hand, represent sums of the inputs, with correspondingly larger coefficients. This is the interface of plans and operands (Arithmetic in transform space), used by FLINT for complex integer products (arbr−aibia_r b_r - a_i b_i and arbi+aibra_r b_i + a_i b_r from four transforms instead of eight), matrix products and the transformed rings of gr.

When one operand bb is used many times, its transforms can be kept. A product with bb then costs one forward transform, one pointwise product and one inverse transform per prime instead of two forward transforms. FLINT stores such an operand for cyclic convolutions of a fixed length 2d2^d (_mul_precomp_init) and computes each window of a later product with Theorem 3. Division with remainder by a fixed divisor bb of length bnb_n uses two stored operands, bb and the reversed power series inverse BB of the reversed divisor to precision BnB_n [GG2003]:

Proposition 4 (Division with stored operands). Let aa have length ana_n with qn=an−bn+1≤Bnq_n = a_n - b_n + 1 \le B_n. The coefficients [Bn−1,Bn−1+qn)[B_n - 1, B_n - 1 + q_n) of the product of the top qnq_n coefficients of aa with BB are the quotient qq of aa by bb, and the remainder is r=(a−bq) mod (xN−1)r = (a - bq) \bmod (x^N - 1) for any N≥bn−1N \ge b_n - 1.

Proof. Write rev⁡mf=xm−1f(1/x)\operatorname{rev}_m f = x^{m-1} f(1/x) for polynomials of length mm. From a=bq+ra = bq + r with deg⁡r<bn−1\deg r < b_n - 1 we get rev⁡a≡rev⁡b⋅rev⁡q(modxqn)\operatorname{rev} a \equiv \operatorname{rev} b \cdot \operatorname{rev} q \pmod{x^{q_n}}, so rev⁡q=rev⁡a⋅(rev⁡b)−1 mod xqn\operatorname{rev} q = \operatorname{rev} a \cdot (\operatorname{rev} b)^{-1} \bmod x^{q_n}. Since rev⁡BnB\operatorname{rev}_{B_n} B is that inverse to precision Bn≥qnB_n \ge q_n, reversing back expresses qq as the stated window of a product of the top qnq_n coefficients of aa and BB. For the remainder, rr has length less than bn−1≤Nb_n - 1 \le N, so it equals its own reduction modulo xN−1x^N - 1, which is the reduction of a−bqa - bq.

The remainder therefore needs only a cyclic product of length NN, the least power of two at least max⁡(bn−1,256)\max(b_n - 1, 256), instead of a product of length ana_n. The condition qn≤Bnq_n \le B_n is the caller's responsibility (see Products and divisions with a fixed operand).

Integers with a common factor#

For integers the same saving applies to a factor xx shared by several products. _fft_small_mpn_mul_shared forms axax and bxbx from three forward and two inverse transforms per prime instead of four and two, and _fft_small_mpn_mul_precomp keeps the transform of xx for any number of products, saving one conversion and forward transform per product (about 30% of a two-prime product). The transform of xx is computed for the longest product, and each partner is transformed only to the truncation of its own product, so a short partner costs less; only the transform points that the longest product reads need to be stored. In the two-prime range each product is computed in one pass over its data (conversion, forward transform, pointwise product and inverse transform modulo one prime, then the other, then the Garner step). Making these stages separate passes over all operands, as the plan and operand interface does, was measured to cost as much as the shared transform saves. The binary splitting of mp_real uses both functions; see Integer products with a common operand.

Transposed products#

A middle product is the transpose of a product (Middle product as transposed multiplication). Transposing the polynomial pipeline (Polynomials over Z/nZ\mathbb{Z}/n\mathbb{Z} and Z\mathbb{Z}) step by step gives a pipeline for it: the conversion of the operand that slides, the transposed inverse transform, the same pointwise product with the same normalisation, the transposed forward transform truncated to the output length, and the same reconstruction (Theorem 4 of Middle products and the transposition principle). The coefficient bound is that of the product it transposes: an output coefficient ∑iaivi+j\sum_i a_i v_{i+j} is a sum of at most ana_n products, so the plan of the product of lengths ana_n and znz_n applies unchanged, including the transform modulo nn itself. _nmod_poly_mul_tr_mpn_ctx implements this with the convolution engine in a transposed mode.

A general window [zl,zh)[z_l, z_h) of a product is such a transposed product of the reversed shorter operand (length ss) and the longer one, shifted by zl−(s−1)z_l - (s - 1) coefficients, at the cost of a product of lengths ss and zh−zlz_h - z_l; the drivers use it when the window is shorter than the longer operand and the transform is strictly shorter than the one of Windows and wrap-around. For integers (_mpn_ctx_mpn_mul_range, on both the two-prime and the multi-prime pipeline) the shift is rounded down so that it is a whole number of limbs, and the shorter operand is padded with zero chunks, so that the reconstruction sees the same chunk coefficients at the same bit positions as for the full product and returns the same window; the chunk size and primes are those chosen for the full product. radix_mulmid_fft_small does the same with the radix-BB reconstruction. With a fixed long operand vv, the images of vv through the transposed inverse transform can be stored (_mul_precomp_init_tr); each product then costs the transform of the short operand and the transposed forward transform, of a length about vnv_n (_nmod_poly_mul_tr_precomp, the convolution engine in its second transposed mode).

Parallelism#

The work for different primes is independent until the reconstruction: the multi-prime drivers transform the operands for different primes on different threads (one thread per prime when enough are available, and for large operands a second thread per prime that transforms the other operand), and then reconstruct ranges of coefficients in parallel. The two-prime integer pipeline runs on one thread. The thresholds differ by coefficient type; see Memory and threads.

Referenced from#

References#

  • [Pol1971] J. M. Pollard. The fast Fourier transform in a finite field. Mathematics of Computation 25 (1971), 365–374.
  • [Knu1997] Knuth, D. E. The Art of Computer Programming, volume 2: Seminumerical algorithms, 1997
  • [GG2003] J. von zur Gathen and J. Gerhard, Modern Computer Algebra, second edition, Cambridge University Press (2003)
  • [Gar1959] H. L. Garner. The residue number system. IRE Transactions on Electronic Computers EC-8:2 (1959), 140–147. https://doi.org/10.1109/TEC.1959.5219515
Cite this page

The FLINT team, “Multiplication with several primes and CRT,” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/fft/multimodular.html (generated 2026-11-02).