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}