Modular arithmetic in floating point
- Cost
- A modular product costs two multiplications, two fused multiply-adds, one rounding and one addition, all vectorisable; a reduction costs one multiplication, one rounding and one fused multiply-add.
Vector units multiply doubles much faster than they multiply 64-bit integers: AVX2 has no instruction for a full -bit product, while it performs four double-precision fused multiply-adds per instruction. FLINT therefore does the modular arithmetic of its small-prime FFT (fft_small) on integers stored exactly in doubles. The functions are vec4d_mulmod, vec4d_reduce_to_pm1n and their relatives in machine_vectors.h, and the ranges stated there are proved below. The approach is of the kind studied by van der Hoeven, Lecerf and Quintin [HLQ2016]; exact linear algebra libraries such as FFLAS have long relied on integer computations in floating point [DGP2008].
Setting#
Doubles have a 53-bit significand. Let be the unit roundoff and write for rounded to the nearest double, with ties to even. We assume round to nearest throughout and ignore overflow and underflow, which cannot occur with the magnitudes below. The standard facts we need [MBD2018] are:
- Every integer with is a double. Consequently, an operation whose exact result is such an integer returns it exactly.
- For any real in range, with .
- If for doubles and , then is a double, so a fused multiply-add computes it exactly: .
Fix an integer modulus and let with . Residues are represented by integers in doubles, not necessarily reduced: a typical range is , and intermediate sums may grow to a few multiples of .
Rounding the quotient#
Every function computes an approximate quotient and returns . The quotient is an integer obtained by rounding the product of a double with . Two ways are used. The mulmod and nmulmod functions, and all functions on NEON, always use native rounding; round_mul, and hence mulmod_fast and the reductions on AVX2, use the method selected per target by FLINT_FFT_SMALL_ROUND_USE_NATIVE:
- Native rounding: with a rounding instruction (
vroundpdon x86,frintnon ARM). This rounds twice. - Magic constant: with , , where the inner operation is one fused multiply-add. On Intel cores since Skylake the rounding instruction is slow, and this is faster.
Lemma 1 (Magic constant). Let be a real number with and . Then is the integer nearest to , with ties to even.
Proof. The sum lies in , where consecutive doubles are exactly 1 apart, so is an integer nearest to . Since is an even integer, is an integer nearest to , and a tie is broken towards an even , that is, an even . The subtraction is exact because .
For the lemma fails: the spacing of the doubles near exceeds 1 when , and is below 1 when , in which case need not even be an integer. Native rounding has no such limit. The difference between the two methods is in the error of the quotient:
Lemma 2. Let and be real numbers with .
- If is the integer nearest to , then .
- If is the integer nearest to , then .
Proof. In the first case . The second case is the first with , .
Reduction#
Input: an integer with , an integer and .
Output: an integer with .
- the rounded value of , by either method.
- Return .
This is vec4d_reduce_to_pm1n (and the identical vec4d_reduce_to_pm1no). Following it with "add if negative" gives vec4d_reduce_to_0n.
Theorem 3. Algorithm 1 is correct. More precisely, with the magic constant and with native rounding.
Proof. With the magic constant, the fused multiply-add rounds the exact value once, and because . So is the integer nearest to by Lemma 1, and Lemma 2 with and gives . With native rounding, the product is rounded once more, and the second case of Lemma 2 gives . Multiplying by gives the bounds on , and . The integer is thus smaller than in absolute value, so it is a double and the fused multiply-add returns it exactly.
The condition is needed by the magic constant: for or and close to the quotient exceeds and the result is wrong. It also gives , so the result lies in , inside the range promised by the name pm1n.
Multiplication#
Input: integers stored as doubles with , an integer and .
Output: an integer .
- .
- the rounded value of .
- , so that exactly.
- Return .
vec4d_mulmod rounds natively in step 2 and vec4d_mulmod_fast uses round_mul, which is the magic constant on most Intel targets. The nmulmod variants compute the same way: they set and return .
Theorem 4. With native rounding, Algorithm 2 returns an integer with
With the magic constant, if moreover , the same holds with .
Proof. Write . With native rounding, and with all , so the second case of Lemma 2 gives , that is, . With the magic constant, the fused multiply-add rounds the exact value where . By the hypothesis, , so is the integer nearest to by Lemma 1, and the first case of Lemma 2 gives the bound with the smaller .
It remains to see that is computed exactly. The double is an integer, because is an integer and every double of absolute value at least is an integer. So and are integers. From we get and , so . Hence the fused multiply-add in step 4 returns exactly, and the final addition returns the integer exactly.
The theorem holds for any , prime or not, odd or even. The ranges used by the transforms follow from it.
Corollary 5. Let . With either rounding method, implies , and with native rounding implies .
Proof. With and , , so . With the same computation gives . The magic constant applies to the first case because .
For the theorem gives only for , and a finer analysis of the rounding in step 2, in the comments of fft_small/mulmod_satisfies_bounds.c, gives . FLINT therefore checks each 50-bit prime individually with fft_small_mulmod_satisfies_bounds, which evaluates that finer bound with the actual error in place of its maximum . A prime passes if the bound gives for and for , and sd_fft_ctx_init_prime throws for a prime that fails. According to the same comments about one random 50-bit prime in 900 fails, and none of 49 bits or less, in agreement with Corollary 5.
Cheap reductions#
Between multiplications, values are brought back into a smaller range by comparisons and conditional additions, which are exact on integers below :
| Function | Input | Output | Method |
|---|---|---|---|
reduce_pm1no_to_0n | add if negative | ||
reduce_2n_to_n | subtract if not negative afterwards | ||
reduce_0n_to_pmhn | subtract if above | ||
reduce_pm1n_to_pmhn | add or subtract if |
The vectorised versions use blendv on a sign bit or a comparison mask instead of branches. Testing the sign bit treats as negative, so vec4d_reduce_pm1no_to_0n on AVX2 maps to , outside the stated output range; the result is still congruent to the input.
Use in FLINT#
- Small-prime FFT.
fft_smallcomputes number-theoretic transforms modulo primes with , with all arithmetic invec8dandvec4d. Twiddle factors are stored in , the butterflies usemulmod_fastandreduce_to_pm1n, and the bound analysis infft_small/mulmod_satisfies_bounds.cshows that the values stay within a few multiples of , so that all sums are exact. That analysis needs the actual ranges of Theorem 3 and Theorem 4 in addition to the documented ones for the truncated inverse transform; it is carried out in Arithmetic and its ranges. - Polynomial and integer multiplication. The conversions in and out of the transform use
mulmod,reduce_to_0nandreduce_0n_to_pmhn. Throughfft_small, this arithmetic underlies the multiplication of large integers (flint_mpn_mul) and of large polynomials innmod_polyandfmpz_poly. - Precomputed transforms.
mpn_modand thegrimplementations of transformed polynomials multiply by fixed scalars withvec8d_mulmod. - Matrices.
nmod_mat_mul_u8reduces its results withvec4d_reduce_to_0non x86. The floating-point kernelnmod_mat_mul_fp50uses its own versions ofround_mul,mulmod_fastandreduce_to_pm1nwith a fixed lane count per instruction set (nmod_mat/mul_fp_vec.h), with the magic constant except on NEON, and refers to the same analysis.
Referenced from#
References#
- [HLQ2016] J. van der Hoeven, G. Lecerf and G. Quintin, "Modular SIMD arithmetic in Mathemagix", ACM Transactions on Mathematical Software 43:1 (2016), article 5, https://doi.org/10.1145/2876503
- [DGP2008] J.-G. Dumas, P. Giorgi and C. Pernet, "Dense linear algebra over word-size prime fields: the FFLAS and FFPACK packages", ACM Transactions on Mathematical Software 35:3 (2008), 1-42, https://doi.org/10.1145/1391989.1391992
- [MBD2018] J.-M. Muller, N. Brunie, F. de Dinechin, C.-P. Jeannerod, M. Joldes, V. Lefèvre, G. Melquiond, N. Revol and S. Torres, Handbook of Floating-Point Arithmetic, second edition, Birkhäuser (2018), https://doi.org/10.1007/978-3-319-76526-6
The FLINT team, “Modular arithmetic in floating point,” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/simd/fp-modular-arithmetic.html (generated 2026-11-02).
@misc{flint-doc-algorithms-simd-fp-modular-arithmetic,
author = {{The FLINT team}},
title = {{Modular arithmetic in floating point}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/algorithms/simd/fp-modular-arithmetic.html},
note = {Generated 2026-11-02}
}