Skip to main content

malachite_base/num/arithmetic/
mod_sqrt.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// Uses code adopted from the FLINT Library.
4//
5//      Copyright © 2009 William Hart
6//
7//      Copyright © 2011 Sebastian Pancratz
8//
9// This file is part of Malachite.
10//
11// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
12// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
13// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
14
15use crate::num::arithmetic::traits::{CheckedSqrt, JacobiSymbol, ModMulPrecomputed, ModSqrt};
16use crate::num::basic::integers::USIZE_IS_U32;
17use crate::num::basic::unsigneds::PrimitiveUnsigned;
18use crate::num::conversion::traits::WrappingFrom;
19
20// Modular exponentiation by binary exponentiation, with an exponent of the full width of `T`. The
21// standard `ModPow` implementations take a `u64` exponent, which is too narrow for `u128` moduli.
22// The multiplications share the caller's precomputed data.
23fn mod_pow_full_width<T: PrimitiveUnsigned>(
24    x: T,
25    exp: T,
26    m: T,
27    data: &<T as ModMulPrecomputed<T, T>>::Data,
28) -> T {
29    let mut result = T::ONE.mod_op(m);
30    let mut base = x;
31    let mut exp = exp;
32    while exp != T::ZERO {
33        if exp.odd() {
34            result.mod_mul_precomputed_assign(base, m, data);
35        }
36        exp >>= 1;
37        if exp != T::ZERO {
38            base.mod_mul_precomputed_assign(base, m, data);
39        }
40    }
41    result
42}
43
44// Computes a square root of `x` modulo `m`, where `x` must be reduced modulo `m`.
45//
46// This has the same behavior as `n_sqrtmod` from `ulong_extras/sqrtmod.c`, FLINT 3.6.0, except
47// that:
48// - for even moduli between 50 and 600, FLINT consults a Jacobi-symbol routine whose behavior for
49//   even moduli is undefined, and this function does not; and
50// - for the moduli `T::MAX` and `T::MAX - 2`, FLINT's `(p + 1) / 4` and `(p + 3) / 8` wrap around,
51//   while this function computes them exactly as `(p >> 2) + 1` and `(p >> 3) + 1`, agreeing with
52//   what `fmpz_sqrtmod` computes for such moduli. (Both moduli are composite at every width, so
53//   both behaviors are anyway outside the odd-prime domain.)
54//
55// See `mod_sqrt_ref_ref` in `malachite-nz` for the structure; this is the same algorithm.
56//
57// # Worst-case complexity
58// $T(n) = O(n^2)$
59//
60// $M(n) = O(1)$
61//
62// where $T$ is time, $M$ is additional memory, and $n$ is `m.significant_bits()`: the fast cases
63// are a single $O(n)$ modular powering, and the general Tonelli-Shanks loop runs at most $n$ times
64// with an inner adjustment chain of at most $n$ squarings, all on words. The bound assumes that the
65// quadratic-nonresidue search does not dominate; under the extended Riemann hypothesis the search
66// inspects $O((\log m)^2)$ candidates.
67private_test_fn! {mod_sqrt_unsigned<
68    T: CheckedSqrt<Output = T> + JacobiSymbol<T> + PrimitiveUnsigned,
69>(
70    x: T,
71    m: T,
72) -> Option<T> {
73    assert!(x < m, "x must be reduced mod m, but {x} >= {m}");
74    if x <= T::ONE {
75        return Some(x);
76    }
77    // Here x >= 2, so m >= 4.
78    if m < T::saturating_from(600u16) {
79        if m > T::saturating_from(50u8) && m.odd() && x.jacobi_symbol(m) == -1 {
80            return None;
81        }
82        let limit = (m - T::ONE) >> 1;
83        let mut t = T::ZERO;
84        let mut t_squared = T::ZERO;
85        while t < limit {
86            // (t + 1) ^ 2 = t ^ 2 + 2t + 1; 2t + 1 < m since t < (m - 1) / 2
87            t_squared.mod_add_assign((t << 1) | T::ONE, m);
88            t += T::ONE;
89            if t_squared == x {
90                return Some(t);
91            }
92        }
93        return None;
94    }
95    // The evenness test must come first, since the Jacobi symbol requires an odd modulus; the
96    // perfect-square test keeps the quadratic-nonresidue search below terminating.
97    if m.even() || m.checked_sqrt().is_some() || x.jacobi_symbol(m) == -1 {
98        return None;
99    }
100    let data = T::precompute_mod_mul_data(&m);
101    if m.mod_power_of_2(2) == T::from(3u8) {
102        // (m + 1) / 4, written without overflow
103        return Some(mod_pow_full_width(x, (m >> 2) + T::ONE, m, &data));
104    }
105    if m.mod_power_of_2(3) == T::from(5u8) {
106        // (m + 3) / 8, written without overflow
107        let root = mod_pow_full_width(x, (m >> 3) + T::ONE, m, &data);
108        if root.mod_mul_precomputed(root, m, &data) == x {
109            return Some(root);
110        }
111        let g = mod_pow_full_width(T::TWO, (m - T::ONE) >> 2, m, &data);
112        return Some(g.mod_mul_precomputed(root, m, &data));
113    }
114    // Tonelli-Shanks. Here m == 1 mod 8, so if m is prime, 2 is a quadratic residue and the
115    // smallest nonresidue is odd.
116    let mut r = 0u64;
117    let mut p1 = m - T::ONE;
118    loop {
119        p1 >>= 1;
120        r += 1;
121        if p1.odd() {
122            break;
123        }
124    }
125    let mut b = mod_pow_full_width(x, p1, m, &data);
126    let mut k = T::from(3u8);
127    while k.jacobi_symbol(m) != -1 {
128        k += T::TWO;
129    }
130    let mut g = mod_pow_full_width(k, p1, m, &data);
131    let mut root = mod_pow_full_width(x, (p1 >> 1) + T::ONE, m, &data);
132    // the maximum number of iterations if m is prime
133    let mut iter = r - 1;
134    while b != T::ONE {
135        let mut b_pow = b;
136        let mut new_r = 0;
137        loop {
138            b_pow.mod_mul_precomputed_assign(b_pow, m, &data);
139            new_r += 1;
140            if new_r >= r || b_pow == T::ONE {
141                break;
142            }
143        }
144        let mut g_pow = g;
145        for _ in 1..r - new_r {
146            g_pow.mod_mul_precomputed_assign(g_pow, m, &data);
147        }
148        root.mod_mul_precomputed_assign(g_pow, m, &data);
149        g = g_pow.mod_mul_precomputed(g_pow, m, &data);
150        b.mod_mul_precomputed_assign(g, m, &data);
151        r = new_r;
152        if iter == 0 {
153            // too many iterations; m is not prime
154            root = T::ZERO;
155            break;
156        }
157        iter -= 1;
158    }
159    if root == T::ZERO { None } else { Some(root) }
160}}
161
162macro_rules! impl_mod_sqrt {
163    ($t:ident) => {
164        impl ModSqrt<$t> for $t {
165            type Output = $t;
166
167            /// Computes a square root of a number modulo another number $m$: a $y$ with $y^2 \equiv
168            /// x \pmod m$. The input must be already reduced modulo $m$.
169            ///
170            /// If $m$ is an odd prime, a root is returned whenever one exists, and `None` is
171            /// returned exactly when $x$ is a quadratic nonresidue. For other moduli the function
172            /// still terminates and is deterministic, but it may return `None` even though a root
173            /// exists, and it may return a value that is not a root, so if $m$ is not known to be
174            /// prime, a returned root should be verified by squaring. The behavior for such moduli
175            /// matches FLINT's, with two exceptions, both involving only composite moduli: for even
176            /// moduli between 50 and 600 FLINT consults a Jacobi-symbol routine whose behavior for
177            /// even moduli is undefined, and for the two largest odd moduli of a width FLINT's
178            /// exponent computations wrap, while this function computes them exactly, as FLINT's
179            /// own multiprecision path does.
180            ///
181            /// $f(x, m) = y$, where $x, y < m$ and $y^2 \equiv x \mod m$, if such a $y$ is found.
182            ///
183            /// # Worst-case complexity
184            /// $T(n) = O(n^2)$
185            ///
186            /// $M(n) = O(1)$
187            ///
188            /// where $T$ is time, $M$ is additional memory, and $n$ is `m.significant_bits()`. The
189            /// bound assumes that the quadratic-nonresidue search does not dominate; under the
190            /// extended Riemann hypothesis the search inspects $O((\log m)^2)$ candidates.
191            ///
192            /// # Panics
193            /// Panics if `self` is greater than or equal to `m`.
194            ///
195            /// # Examples
196            /// See [here](super::mod_sqrt#mod_sqrt).
197            ///
198            /// This is equivalent to `n_sqrtmod` from `ulong_extras/sqrtmod.c`, FLINT 3.6.0,
199            /// returning an `Option` where FLINT returns 0 for both a failure and a root of 0.
200            #[inline]
201            fn mod_sqrt(self, m: $t) -> Option<$t> {
202                mod_sqrt_unsigned(self, m)
203            }
204        }
205    };
206}
207impl_mod_sqrt!(u32);
208impl_mod_sqrt!(u64);
209impl_mod_sqrt!(u128);
210
211macro_rules! impl_mod_sqrt_promoted {
212    ($t:ident) => {
213        impl ModSqrt<$t> for $t {
214            type Output = $t;
215
216            /// Computes a square root of a number modulo another number $m$: a $y$ with $y^2 \equiv
217            /// x \pmod m$. The input must be already reduced modulo $m$.
218            ///
219            /// If $m$ is an odd prime, a root is returned whenever one exists, and `None` is
220            /// returned exactly when $x$ is a quadratic nonresidue. For other moduli the function
221            /// still terminates and is deterministic, but it may return `None` even though a root
222            /// exists, and it may return a value that is not a root, so if $m$ is not known to be
223            /// prime, a returned root should be verified by squaring. The behavior for such moduli
224            /// matches FLINT's, with two exceptions, both involving only composite moduli: for even
225            /// moduli between 50 and 600 FLINT consults a Jacobi-symbol routine whose behavior for
226            /// even moduli is undefined, and for the two largest odd moduli of a width FLINT's
227            /// exponent computations wrap, while this function computes them exactly, as FLINT's
228            /// own multiprecision path does.
229            ///
230            /// $f(x, m) = y$, where $x, y < m$ and $y^2 \equiv x \mod m$, if such a $y$ is found.
231            ///
232            /// # Worst-case complexity
233            /// $T(n) = O(n^2)$
234            ///
235            /// $M(n) = O(1)$
236            ///
237            /// where $T$ is time, $M$ is additional memory, and $n$ is `m.significant_bits()`. The
238            /// bound assumes that the quadratic-nonresidue search does not dominate; under the
239            /// extended Riemann hypothesis the search inspects $O((\log m)^2)$ candidates.
240            ///
241            /// # Panics
242            /// Panics if `self` is greater than or equal to `m`.
243            ///
244            /// # Examples
245            /// See [here](super::mod_sqrt#mod_sqrt).
246            ///
247            /// This is equivalent to `n_sqrtmod` from `ulong_extras/sqrtmod.c`, FLINT 3.6.0,
248            /// returning an `Option` where FLINT returns 0 for both a failure and a root of 0.
249            #[inline]
250            fn mod_sqrt(self, m: $t) -> Option<$t> {
251                u32::from(self)
252                    .mod_sqrt(u32::from(m))
253                    .map($t::wrapping_from)
254            }
255        }
256    };
257}
258impl_mod_sqrt_promoted!(u8);
259impl_mod_sqrt_promoted!(u16);
260
261impl ModSqrt<Self> for usize {
262    type Output = Self;
263
264    /// Computes a square root of a number modulo another number $m$: a $y$ with $y^2 \equiv x \pmod
265    /// m$. The input must be already reduced modulo $m$.
266    ///
267    /// If $m$ is an odd prime, a root is returned whenever one exists, and `None` is returned
268    /// exactly when $x$ is a quadratic nonresidue. For other moduli the function still terminates
269    /// and is deterministic, but it may return `None` even though a root exists, and it may return
270    /// a value that is not a root, so if $m$ is not known to be prime, a returned root should be
271    /// verified by squaring. The behavior for such moduli matches FLINT's, with two exceptions,
272    /// both involving only composite moduli: for even moduli between 50 and 600 FLINT consults a
273    /// Jacobi-symbol routine whose behavior for even moduli is undefined, and for the two largest
274    /// odd moduli of a width FLINT's exponent computations wrap, while this function computes them
275    /// exactly, as FLINT's own multiprecision path does.
276    ///
277    /// $f(x, m) = y$, where $x, y < m$ and $y^2 \equiv x \mod m$, if such a $y$ is found.
278    ///
279    /// # Worst-case complexity
280    /// $T(n) = O(n^2)$
281    ///
282    /// $M(n) = O(1)$
283    ///
284    /// where $T$ is time, $M$ is additional memory, and $n$ is `m.significant_bits()`. The bound
285    /// assumes that the quadratic-nonresidue search does not dominate; under the extended Riemann
286    /// hypothesis the search inspects $O((\log m)^2)$ candidates.
287    ///
288    /// # Panics
289    /// Panics if `self` is greater than or equal to `m`.
290    ///
291    /// # Examples
292    /// See [here](super::mod_sqrt#mod_sqrt).
293    ///
294    /// This is equivalent to `n_sqrtmod` from `ulong_extras/sqrtmod.c`, FLINT 3.6.0, returning an
295    /// `Option` where FLINT returns 0 for both a failure and a root of 0.
296    #[inline]
297    fn mod_sqrt(self, m: Self) -> Option<Self> {
298        if USIZE_IS_U32 {
299            u32::wrapping_from(self)
300                .mod_sqrt(u32::wrapping_from(m))
301                .map(Self::wrapping_from)
302        } else {
303            u64::wrapping_from(self)
304                .mod_sqrt(u64::wrapping_from(m))
305                .map(Self::wrapping_from)
306        }
307    }
308}