FLINT3.7.0-dev
Algorithm

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 64×6464 \times 64-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 u=2−53u = 2^{-53} be the unit roundoff and write fl(x)\mathrm{fl}(x) for xx 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 xx with ∣x∣≤253|x| \le 2^{53} is a double. Consequently, an operation whose exact result is such an integer returns it exactly.
  • For any real xx in range, fl(x)=x(1+δ)\mathrm{fl}(x) = x(1 + \delta) with ∣δ∣≤u|\delta| \le u.
  • If h=fl(ab)h = \mathrm{fl}(ab) for doubles aa and bb, then ab−hab - h is a double, so a fused multiply-add computes it exactly: fma(a,b,−h)=ab−h\mathrm{fma}(a, b, -h) = ab - h.

Fix an integer modulus n≥1n \ge 1 and let ninv=fl(1/n)=(1+δ0)/n\mathit{ninv} = \mathrm{fl}(1/n) = (1 + \delta_0)/n with ∣δ0∣≤u|\delta_0| \le u. Residues are represented by integers in doubles, not necessarily reduced: a typical range is (−n,n)(-n, n), and intermediate sums may grow to a few multiples of nn.

Rounding the quotient#

Every function computes an approximate quotient q≈x/nq \approx x/n and returns x−qnx - qn. The quotient is an integer obtained by rounding the product of a double hh with ninv\mathit{ninv}. 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: q=round(fl(h⋅ninv))q = \mathrm{round}(\mathrm{fl}(h \cdot \mathit{ninv})) with a rounding instruction (vroundpd on x86, frintn on ARM). This rounds twice.
  • Magic constant: with R=3⋅251R = 3 \cdot 2^{51}, q=fl(fl(h⋅ninv+R)−R)q = \mathrm{fl}(\mathrm{fl}(h \cdot \mathit{ninv} + R) - R), 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 xx be a real number with ∣x∣≤251|x| \le 2^{51} and R=3⋅251R = 3 \cdot 2^{51}. Then fl(fl(x+R)−R)\mathrm{fl}(\mathrm{fl}(x + R) - R) is the integer nearest to xx, with ties to even.

Proof. The sum x+Rx + R lies in [252,253][2^{52}, 2^{53}], where consecutive doubles are exactly 1 apart, so s=fl(x+R)s = \mathrm{fl}(x + R) is an integer nearest to x+Rx + R. Since RR is an even integer, s−Rs - R is an integer nearest to xx, and a tie is broken towards an even ss, that is, an even s−Rs - R. The subtraction is exact because ∣s−R∣≤251|s - R| \le 2^{51}.

For ∣x∣>251|x| > 2^{51} the lemma fails: the spacing of the doubles near x+Rx + R exceeds 1 when x>251x > 2^{51}, and is below 1 when x<−251x < -2^{51}, in which case s−Rs - R 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 xx and yy be real numbers with y=x(1+ϵ)y = x(1 + \epsilon).

  1. If qq is the integer nearest to yy, then ∣x−q∣≤1/2+∣ϵx∣|x - q| \le 1/2 + |\epsilon x|.
  2. If qq is the integer nearest to fl(y)\mathrm{fl}(y), then ∣x−q∣≤1/2+∣x∣((1+∣ϵ∣)(1+u)−1)|x - q| \le 1/2 + |x| \left((1 + |\epsilon|)(1 + u) - 1\right).

Proof. In the first case ∣x−q∣≤∣x−y∣+∣y−q∣≤∣ϵx∣+1/2|x - q| \le |x - y| + |y - q| \le |\epsilon x| + 1/2. The second case is the first with fl(y)=x(1+ϵ)(1+δ)\mathrm{fl}(y) = x(1 + \epsilon)(1 + \delta), ∣δ∣≤u|\delta| \le u.

Reduction#

Algorithm 1: Reduction

Input: an integer aa with ∣a∣<253|a| < 2^{53}, an integer n≥4n \ge 4 and ninv=fl(1/n)\mathit{ninv} = \mathrm{fl}(1/n).

Output: an integer r≡a(modn)r \equiv a \pmod n with ∣r∣<n/2+2|r| < n/2 + 2.

  1. q←q \gets the rounded value of a⋅ninva \cdot \mathit{ninv}, by either method.
  2. Return r=fma(−q,n,a)r = \mathrm{fma}(-q, n, a).

This is vec4d_reduce_to_pm1n (and the identical vec4d_reduce_to_pm1no). Following it with "add nn if negative" gives vec4d_reduce_to_0n.

Theorem 3. Algorithm 1 is correct. More precisely, ∣r∣≤n/2+u∣a∣<n/2+1|r| \le n/2 + u|a| < n/2 + 1 with the magic constant and ∣r∣≤n/2+(2u+u2)∣a∣<n/2+2|r| \le n/2 + (2u + u^2)|a| < n/2 + 2 with native rounding.

Proof. With the magic constant, the fused multiply-add rounds the exact value a⋅ninv+Ra \cdot \mathit{ninv} + R once, and ∣a⋅ninv∣≤(253−1)(1+u)/4<251|a \cdot \mathit{ninv}| \le (2^{53} - 1)(1 + u)/4 < 2^{51} because n≥4n \ge 4. So qq is the integer nearest to y=a⋅ninvy = a \cdot \mathit{ninv} by Lemma 1, and Lemma 2 with x=a/nx = a/n and ϵ=δ0\epsilon = \delta_0 gives ∣a/n−q∣≤1/2+u∣a∣/n|a/n - q| \le 1/2 + u|a|/n. With native rounding, the product is rounded once more, and the second case of Lemma 2 gives ∣a/n−q∣≤1/2+(2u+u2)∣a∣/n|a/n - q| \le 1/2 + (2u + u^2)|a|/n. Multiplying by nn gives the bounds on a−qna - qn, and (2u+u2)∣a∣≤(2u+u2)(253−1)<2(2u + u^2)|a| \le (2u + u^2)(2^{53} - 1) < 2. The integer a−qna - qn is thus smaller than n/2+2<253n/2 + 2 < 2^{53} in absolute value, so it is a double and the fused multiply-add returns it exactly.

The condition n≥4n \ge 4 is needed by the magic constant: for n=2n = 2 or 33 and ∣a∣|a| close to 2532^{53} the quotient exceeds 2512^{51} and the result is wrong. It also gives ∣r∣<n/2+2≤n|r| < n/2 + 2 \le n, so the result lies in (−n,n)(-n, n), inside the range [−n,n][-n, n] promised by the name pm1n.

Multiplication#

Algorithm 2: Modular multiplication

Input: integers a,ba, b stored as doubles with ∣ab∣<2103|ab| < 2^{103}, an integer 1≤n<2501 \le n < 2^{50} and ninv=fl(1/n)\mathit{ninv} = \mathrm{fl}(1/n).

Output: an integer r≡ab(modn)r \equiv ab \pmod n.

  1. h←fl(ab)h \gets \mathrm{fl}(ab).
  2. q←q \gets the rounded value of h⋅ninvh \cdot \mathit{ninv}.
  3. ℓ←fma(a,b,−h)\ell \gets \mathrm{fma}(a, b, -h), so that h+ℓ=abh + \ell = ab exactly.
  4. Return r=fl(fma(−q,n,h)+ℓ)r = \mathrm{fl}(\mathrm{fma}(-q, n, h) + \ell).

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 −r-r the same way: they set ℓ=fma(−a,b,h)=h−ab\ell = \mathrm{fma}(-a, b, h) = h - ab and return ℓ−fma(−q,n,h)\ell - \mathrm{fma}(-q, n, h).

Theorem 4. With native rounding, Algorithm 2 returns an integer r≡ab(modn)r \equiv ab \pmod n with

∣r∣≤n2+η∣ab∣,η=(1+u)3−1<3.0001 u.|r| \le \frac{n}{2} + \eta |ab|, \qquad \eta = (1 + u)^3 - 1 < 3.0001\,u.

With the magic constant, if moreover ∣ab∣≤(251−1)n|ab| \le (2^{51} - 1) n, the same holds with η=(1+u)2−1<2.0001 u\eta = (1 + u)^2 - 1 < 2.0001\,u.

Proof. Write x=ab/nx = ab/n. With native rounding, h=ab(1+δ1)h = ab(1 + \delta_1) and fl(h⋅ninv)=x(1+δ1)(1+δ0)(1+δ2)\mathrm{fl}(h \cdot \mathit{ninv}) = x(1 + \delta_1)(1 + \delta_0)(1 + \delta_2) with all ∣δi∣≤u|\delta_i| \le u, so the second case of Lemma 2 gives ∣x−q∣≤1/2+η∣x∣|x - q| \le 1/2 + \eta |x|, that is, ∣ab−qn∣≤n/2+η∣ab∣|ab - qn| \le n/2 + \eta|ab|. With the magic constant, the fused multiply-add rounds the exact value y+Ry + R where y=h⋅ninv=x(1+δ1)(1+δ0)y = h \cdot \mathit{ninv} = x(1 + \delta_1)(1 + \delta_0). By the hypothesis, ∣y∣≤(251−1)(1+u)2<251|y| \le (2^{51} - 1)(1 + u)^2 < 2^{51}, so qq is the integer nearest to yy by Lemma 1, and the first case of Lemma 2 gives the bound with the smaller η\eta.

It remains to see that rr is computed exactly. The double hh is an integer, because abab is an integer and every double of absolute value at least 2532^{53} is an integer. So ℓ=ab−h\ell = ab - h and h−qnh - qn are integers. From ∣ab∣<2103|ab| < 2^{103} we get ∣ℓ∣≤u∣ab∣<250|\ell| \le u|ab| < 2^{50} and ∣ab−qn∣≤249+3.0001⋅250|ab - qn| \le 2^{49} + 3.0001 \cdot 2^{50}, so ∣h−qn∣≤∣ab−qn∣+∣ℓ∣<5⋅250<253|h - qn| \le |ab - qn| + |\ell| < 5 \cdot 2^{50} < 2^{53}. Hence the fused multiply-add in step 4 returns h−qnh - qn exactly, and the final addition returns the integer (h−qn)+ℓ=ab−qn(h - qn) + \ell = ab - qn exactly.

The theorem holds for any n<250n < 2^{50}, prime or not, odd or even. The ranges used by the transforms follow from it.

Corollary 5. Let n<249n < 2^{49}. With either rounding method, ∣ab∣<2n2|ab| < 2n^2 implies ∣r∣<n|r| < n, and with native rounding ∣ab∣<4n2|ab| < 4n^2 implies ∣r∣<3n/2|r| < 3n/2.

Proof. With ∣ab∣<2n2|ab| < 2n^2 and n<249n < 2^{49}, η∣ab∣<3.0001⋅2−53⋅2⋅249n<0.376 n\eta|ab| < 3.0001 \cdot 2^{-53} \cdot 2 \cdot 2^{49} n < 0.376\,n, so ∣r∣<0.876 n|r| < 0.876\,n. With ∣ab∣<4n2|ab| < 4n^2 the same computation gives ∣r∣<1.251 n|r| < 1.251\,n. The magic constant applies to the first case because ∣ab∣<2n2<(251−1)n|ab| < 2n^2 < (2^{51} - 1) n.

For 249≤n<2502^{49} \le n < 2^{50} the theorem gives only ∣r∣<1.25 n|r| < 1.25\,n for ∣ab∣<2n2|ab| < 2n^2, and a finer analysis of the rounding in step 2, in the comments of fft_small/mulmod_satisfies_bounds.c, gives 9n/89n/8. FLINT therefore checks each 50-bit prime individually with fft_small_mulmod_satisfies_bounds, which evaluates that finer bound with the actual error ∣n⋅ninv−1∣|n \cdot \mathit{ninv} - 1| in place of its maximum uu. A prime passes if the bound gives ∣r∣<n|r| < n for ∣ab∣<2n2|ab| < 2n^2 and ∣r∣<3n/2|r| < 3n/2 for ∣ab∣<4n2|ab| < 4n^2, 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 2532^{53}:

FunctionInputOutputMethod
reduce_pm1no_to_0n(−n,n)(-n, n)[0,n)[0, n)add nn if negative
reduce_2n_to_n[0,2n)[0, 2n)[0,n)[0, n)subtract nn if not negative afterwards
reduce_0n_to_pmhn[0,n][0, n](−n/2,n/2](-n/2, n/2]subtract nn if above n/2n/2
reduce_pm1n_to_pmhn[−3n/2,3n/2][-3n/2, 3n/2][−n/2,n/2][-n/2, n/2]add or subtract nn if ∣a∣>n/2\lvert a \rvert > n/2

The vectorised versions use blendv on a sign bit or a comparison mask instead of branches. Testing the sign bit treats −0.0-0.0 as negative, so vec4d_reduce_pm1no_to_0n on AVX2 maps −0.0-0.0 to nn, outside the stated output range; the result is still congruent to the input.

Use in FLINT#

  • Small-prime FFT. fft_small computes number-theoretic transforms modulo primes p<250p < 2^{50} with p≡1(mod2k)p \equiv 1 \pmod{2^k}, with all arithmetic in vec8d and vec4d. Twiddle factors are stored in (−p/2,p/2)(-p/2, p/2), the butterflies use mulmod_fast and reduce_to_pm1n, and the bound analysis in fft_small/mulmod_satisfies_bounds.c shows that the values stay within a few multiples of pp, 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_0n and reduce_0n_to_pmhn. Through fft_small, this arithmetic underlies the multiplication of large integers (flint_mpn_mul) and of large polynomials in nmod_poly and fmpz_poly.
  • Precomputed transforms. mpn_mod and the gr implementations of transformed polynomials multiply by fixed scalars with vec8d_mulmod.
  • Matrices. nmod_mat_mul_u8 reduces its results with vec4d_reduce_to_0n on x86. The floating-point kernel nmod_mat_mul_fp50 uses its own versions of round_mul, mulmod_fast and reduce_to_pm1n with 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
Cite this page

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).