Skip to content

Commit a5cf37c

Browse files
authored
Add Cipolla algorithm for Quadratic Residue problem (TheAlgorithms#303)
1 parent c790d79 commit a5cf37c

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
* [Miller Rabin](https://github.com/TheAlgorithms/Rust/blob/master/src/math/miller_rabin.rs)
7171
* [Linear Sieve](https://github.com/TheAlgorithms/Rust/blob/master/src/math/linear_sieve.rs)
7272
* [Pollard's Rho algorithm](https://github.com/TheAlgorithms/Rust/blob/master/src/math/pollard_rho.rs)
73+
* [Quadratic Residue](https://github.com/TheAlgorithms/Rust/blob/master/src/math/quadratic_residue.rs)
7374
* [Simpson's Rule](https://github.com/TheAlgorithms/Rust/blob/master/src/math/simpson_integration.rs)
7475
* [Fast Fourier Transform](https://github.com/TheAlgorithms/Rust/blob/master/src/math/fast_fourier_transform.rs)
7576
* [Armstrong Number](https://github.com/TheAlgorithms/Rust/blob/master/src/math/armstrong_number.rs)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ These are for demonstration purposes only.
5757
- [X] [Prime number](./src/math/prime_numbers.rs)
5858
- [x] [Linear Sieve](./src/math/linear_sieve.rs)
5959
- [x] [Pollard's Rho algorithm](./src/math/pollard_rho.rs)
60+
- [x] [Quadratic Residue](./src/math/quadratic_residue.rs)
6061
- [x] [Simpson's Rule for Integration](./src/math/simpson_integration.rs)
6162
- [x] [Fast Fourier Transform](./src/math/fast_fourier_transform.rs)
6263
- [x] [Armstrong Number](./src/math/armstrong_number.rs)

src/math/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod perfect_numbers;
1515
mod pollard_rho;
1616
mod prime_check;
1717
mod prime_numbers;
18+
mod quadratic_residue;
1819
mod random;
1920
mod sieve_of_eratosthenes;
2021
mod simpson_integration;
@@ -44,6 +45,7 @@ pub use self::perfect_numbers::perfect_numbers;
4445
pub use self::pollard_rho::{pollard_rho_factorize, pollard_rho_get_one_factor};
4546
pub use self::prime_check::prime_check;
4647
pub use self::prime_numbers::prime_numbers;
48+
pub use self::quadratic_residue::cipolla;
4749
pub use self::random::PCG32;
4850
pub use self::sieve_of_eratosthenes::sieve_of_eratosthenes;
4951
pub use self::simpson_integration::simpson_integration;

src/math/quadratic_residue.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/// Cipolla algorithm
2+
///
3+
/// Solving quadratic residue problem:
4+
/// x^2 = a (mod p) , p is an odd prime
5+
/// with O(M*log(n)) time complexity, M depends on the complexity of complex numbers multiplication.
6+
///
7+
/// Wikipedia reference: https://en.wikipedia.org/wiki/Cipolla%27s_algorithm
8+
/// When a is the primitive root modulo n, the answer is unique.
9+
/// Otherwise it will return the smallest positive solution
10+
use std::rc::Rc;
11+
use std::time::{SystemTime, UNIX_EPOCH};
12+
13+
use super::{fast_power, PCG32};
14+
15+
#[derive(Debug)]
16+
struct CustomFiniteFiled {
17+
modulus: u64,
18+
i_square: u64,
19+
}
20+
21+
impl CustomFiniteFiled {
22+
pub fn new(modulus: u64, i_square: u64) -> Self {
23+
Self { modulus, i_square }
24+
}
25+
}
26+
27+
#[derive(Clone, Debug)]
28+
struct CustomComplexNumber {
29+
real: u64,
30+
imag: u64,
31+
f: Rc<CustomFiniteFiled>,
32+
}
33+
34+
impl CustomComplexNumber {
35+
pub fn new(real: u64, imag: u64, f: Rc<CustomFiniteFiled>) -> Self {
36+
Self { real, imag, f }
37+
}
38+
39+
pub fn mult_other(&mut self, rhs: &Self) {
40+
let tmp = (self.imag * rhs.real + self.real * rhs.imag) % self.f.modulus;
41+
self.real = (self.real * rhs.real
42+
+ ((self.imag * rhs.imag) % self.f.modulus) * self.f.i_square)
43+
% self.f.modulus;
44+
self.imag = tmp;
45+
}
46+
47+
pub fn mult_self(&mut self) {
48+
let tmp = (self.imag * self.real + self.real * self.imag) % self.f.modulus;
49+
self.real = (self.real * self.real
50+
+ ((self.imag * self.imag) % self.f.modulus) * self.f.i_square)
51+
% self.f.modulus;
52+
self.imag = tmp;
53+
}
54+
55+
pub fn fast_power(mut base: Self, mut power: u64) -> Self {
56+
let mut result = CustomComplexNumber::new(1, 0, base.f.clone());
57+
while power != 0 {
58+
if (power & 1) != 0 {
59+
result.mult_other(&base); // result *= base;
60+
}
61+
base.mult_self(); // base *= base;
62+
power >>= 1;
63+
}
64+
result
65+
}
66+
}
67+
68+
fn is_residue(x: u64, modulus: u64) -> bool {
69+
let power = (modulus - 1) >> 1;
70+
x != 0 && fast_power(x as usize, power as usize, modulus as usize) == 1
71+
}
72+
73+
// return two solutions (x1, x2) for Quadratic Residue problem x^2 = a (mod p), where p is an odd prime
74+
// if a is Quadratic Nonresidues, return None
75+
pub fn cipolla(a: u32, p: u32, seed: Option<u64>) -> Option<(u32, u32)> {
76+
// The params should be kept in u32 range for multiplication overflow issue
77+
// But inside we use u64 for convenience
78+
let a = a as u64;
79+
let p = p as u64;
80+
if a == 0 {
81+
return Some((0, 0));
82+
}
83+
if !is_residue(a, p) {
84+
return None;
85+
}
86+
let seed = match seed {
87+
Some(seed) => seed,
88+
None => SystemTime::now()
89+
.duration_since(UNIX_EPOCH)
90+
.unwrap()
91+
.as_secs(),
92+
};
93+
let mut rng = PCG32::new_default(seed);
94+
let r = loop {
95+
let r = rng.get_u64() % p;
96+
if r == 0 || !is_residue((p + r * r - a) % p, p) {
97+
break r;
98+
}
99+
};
100+
let filed = Rc::new(CustomFiniteFiled::new(p, (p + r * r - a) % p));
101+
let comp = CustomComplexNumber::new(r, 1, filed);
102+
let power = (p + 1) >> 1;
103+
let x0 = CustomComplexNumber::fast_power(comp, power).real as u32;
104+
let x1 = p as u32 - x0 as u32;
105+
if x0 < x1 {
106+
Some((x0, x1))
107+
} else {
108+
Some((x1, x0))
109+
}
110+
}
111+
112+
#[cfg(test)]
113+
mod tests {
114+
use super::*;
115+
116+
#[test]
117+
fn small_numbers() {
118+
assert_eq!(cipolla(1, 43, None), Some((1, 42)));
119+
assert_eq!(cipolla(2, 23, None), Some((5, 18)));
120+
assert_eq!(cipolla(17, 83, Some(42)), Some((10, 73)));
121+
}
122+
123+
#[test]
124+
fn random_numbers() {
125+
assert_eq!(cipolla(392203, 852167, None), Some((413252, 438915)));
126+
assert_eq!(
127+
cipolla(379606557, 425172197, None),
128+
Some((143417827, 281754370))
129+
);
130+
assert_eq!(
131+
cipolla(585251669, 892950901, None),
132+
Some((192354555, 700596346))
133+
);
134+
assert_eq!(
135+
cipolla(404690348, 430183399, Some(19260817)),
136+
Some((57227138, 372956261))
137+
);
138+
assert_eq!(
139+
cipolla(210205747, 625380647, Some(998244353)),
140+
Some((76810367, 548570280))
141+
);
142+
}
143+
144+
#[test]
145+
fn no_answer() {
146+
assert_eq!(cipolla(650927, 852167, None), None);
147+
}
148+
}

0 commit comments

Comments
 (0)