Multiplication with several primes and CRT
- Cost
- For two -limb integers, transforms of about points modulo primes with , about points in all, so , plus word operations per coefficient for the reconstruction.
A number-theoretic transform modulo a 50-bit prime multiplies polynomials over . To multiply integers, or polynomials over or , the product is first computed over : its coefficients are bounded, so they can be computed modulo several primes whose product 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 ; FLINT's default primes are listed with mpn_ctx_struct. has 50, 100, 150, 199, 249, 299, 348 and 398 bits for .
Exactness#
Theorem 1. Let and with integer coefficients , and let such products be added. Every coefficient of the sum satisfies , so it is determined by its residues modulo if . If the products are added and subtracted and , is determined as the representative of its residue class in .
Proof. A coefficient of is a sum of at most products . The Chinese remainder theorem determines modulo , and there is only one representative in an interval of length .
The drivers use slightly different bounds, all of this form:
| Operands | Bound used |
|---|---|
| integers cut into -bit chunks | ( limbs of the second operand) |
| polynomials over , of bits | |
| polynomials over with - and -bit coefficients | (symmetric range) |
| sums of products in transform space | len_bound |
Reconstruction#
Lemma 2 (Chinese remaindering with folded normalisation). Let and . If with , then satisfies and .
Proof. Modulo every term but the -th vanishes, and the -th is . Each term is less than .
So for the unique with , which the code finds by repeated subtraction. The factor costs nothing: it is multiplied into the constant that the pointwise product applies anyway to undo the factor of the inverse transform. A context stores, for each , the cofactors and as multi-limb integers of a common length large enough to hold (1, 2, 3, 4, 4, 5, 6 and 7 limbs), and the reduced cofactors . Reconstructing one coefficient costs products of a limb by a cofactor of up to limbs, and a few subtractions: word operations, specialised at compile time for each .
For two primes the reconstruction is Garner's formula [Gar1959]: with in ,
which lies in . The two-prime pipeline computes it in floating point on vectors of eight, with the normalisations applied in the same step, and then needs only one -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 , the integer of up to seven limbs is reduced modulo (with two- and three-limb reductions for ); for polynomials over , is lifted to and stored as an fmpz. With one prime and , is simply reduced modulo .
Integers#
To multiply integers of and limbs, both are cut into chunks of bits, , giving polynomials of lengths and . Their product evaluated at is . Larger chunks give shorter transforms but need more primes, since the chunk products have bits; roughly , so and the number of transform points per limb of the product, , is nearly independent of (it slowly decreases with ). The real trade-off is between the cost of the transforms, which grows with , 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 and , the depth being the least with and then the largest with (_fft_small_np2_choose; 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 , is added at bit offset 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 -bit chunks modulo all primes at once, using tables of for and four primes per vector. A profile applies to second operands of at most bn_bound limbs, the largest with :
bn_bound | bn_bound | |||||
|---|---|---|---|---|---|---|
| 4 | 84 | 2536637511 | 6 | 144 | 2337 | |
| 4 | 88 | 10380583 | 7 | 160 | 613493872 | |
| 4 | 92 | 42390 | 7 | 164 | 2456369 | |
| 5 | 112 | 32822700 | 7 | 168 | 9826 | |
| 5 | 116 | 132790 | 8 | 184 | 2177184315 | |
| 5 | 120 | 534 | 8 | 188 | 8689506 | |
| 6 | 136 | 144789875 | 8 | 192 | 34662 | |
| 6 | 140 | 582218 |
For each the profile with the largest admissible is a candidate, and the candidate with the smallest estimated cost
is chosen; the factor rewards truncated transforms that fill little of their length. When the work is spread over several threads, only profiles whose is a multiple of the number of threads qualify, so that each thread transforms the same number of primes. If exceeds even the largest bound, a scalar conversion with a computed chunk size is used instead. For balanced products with one thread this gives:
| (limbs) | 500 | 5000 | 40000 | 50000 | ||||
|---|---|---|---|---|---|---|---|---|
| 2 | 2 | 2 | 5 | 5 | 6 | 7 | 6 | |
| 44 | 43 | 41 | 116 | 116 | 136 | 160 | 136 | |
| 11 | 14 | 17 | 16 | 17 | 20 | 23 | 27 |
Reassembly#
Coefficient starts at bit 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 with whose bits reach the limb window, need to be reconstructed. Moreover, the transform can be shorter than the product.
Theorem 3 (Wrap-around). Let have length , and let . The cyclic convolution of length of and has coefficients for . If , these equal for all ; so if also , the window of is computed by a transform of length .
Proof. Modulo , the term for becomes ; since , no coefficient wraps twice. For the wrapped coefficient has index at least , so it is zero.
The plans use it when for the largest power of two not exceeding . For a middle product of lengths and , the window of a product of length , this applies when a power of two lies between and , and the transform then has that length instead of a truncated length of about ; 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 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 and #
For polynomials over the coefficients are converted to residues modulo each prime directly (with one reduction if ). The number of primes is the least with : 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 is itself one of the context primes, a single transform modulo gives the result directly.
- If is a prime of at least 20 bits that
fft_smallcan use, that is for the transform length and passes the arithmetic check, and the second operand has at least 100 coefficients, the transform is computed modulo 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 it packs two coefficients into one, with or , provided norm bounds show that the packed product does not overflow modulo , and multiplies the half-length polynomials modulo with one prime.
For polynomials over , 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 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 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 products; for differences of products the result must be read in the symmetric range, which costs one more bit. Products carry the normalisation constant , 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 ( and from four transforms instead of eight), matrix products and the transformed rings of gr.
When one operand is used many times, its transforms can be kept. A product with 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 (_mul_precomp_init) and computes each window of a later product with Theorem 3. Division with remainder by a fixed divisor of length uses two stored operands, and the reversed power series inverse of the reversed divisor to precision [GG2003]:
Proposition 4 (Division with stored operands). Let have length with . The coefficients of the product of the top coefficients of with are the quotient of by , and the remainder is for any .
Proof. Write for polynomials of length . From with we get , so . Since is that inverse to precision , reversing back expresses as the stated window of a product of the top coefficients of and . For the remainder, has length less than , so it equals its own reduction modulo , which is the reduction of .
The remainder therefore needs only a cyclic product of length , the least power of two at least , instead of a product of length . The condition 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 shared by several products. _fft_small_mpn_mul_shared forms and from three forward and two inverse transforms per prime instead of four and two, and _fft_small_mpn_mul_precomp keeps the transform of for any number of products, saving one conversion and forward transform per product (about 30% of a two-prime product). The transform of 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 and ) 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 is a sum of at most products, so the plan of the product of lengths and applies unchanged, including the transform modulo itself. _nmod_poly_mul_tr_mpn_ctx implements this with the convolution engine in a transposed mode.
A general window of a product is such a transposed product of the reversed shorter operand (length ) and the longer one, shifted by coefficients, at the cost of a product of lengths and ; 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- reconstruction. With a fixed long operand , the images of 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 (_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#
- FFTs in FLINT (overview)
- Schönhage–Strassen multiplication (algorithm)
- FFTs modulo word-size primes in floating point (algorithm)
- Binary splitting of hypergeometric series in mp_real (algorithm)
- Multimodular reduction and Chinese remaindering (algorithm)
- Multiplying matrices over the integers (overview)
- Middle products and the transposition principle (algorithm)
- Multiplying polynomials over Z (overview)
- Multiplying polynomials over Z/nZ (overview)
- fft_small.h (module)
profile_entry_struct,profile_entry_t,to_ffts_func- Multiplying large integers
- Integer products with a common operand
- Multiplication modulo 2^N + 1
- Multiplying polynomials
- Polynomial multiplication over Z/nZ for multiword n
- nmod_poly.h (module)
- Multiplying polynomial matrices modulo n
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
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).
@misc{flint-doc-algorithms-fft-multimodular,
author = {{The FLINT team}},
title = {{Multiplication with several primes and CRT}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/algorithms/fft/multimodular.html},
note = {Generated 2026-11-02}
}