quadratic/
lib.rs

1//! Helper functions related to [quadratic reciprocity](https://en.wikipedia.org/wiki/Quadratic_reciprocity).
2//!
3//! This library calculates the [Legendre](http://mathworld.wolfram.com/LegendreSymbol.html)
4//! and [Jacobi symbol](http://mathworld.wolfram.com/JacobiSymbol.html) using the
5//! [standard algorithm](https://en.wikipedia.org/wiki/Jacobi_symbol#Calculating_the_Jacobi_symbol).
6//!
7//! Since the Jacobi symbol is a generalization of the Legendre symbol,
8//! only a `jacobi` function is provided.
9//!
10
11extern crate num;
12use num::Integer;
13use num::traits::ToPrimitive;
14
15
16/// Supplementary law
17fn two_over(n: usize) -> (i8) {
18    if n % 8 == 1 || n % 8 == 7 { 1 } else { -1 }
19}
20
21/// Legendre's version of quadratic reciprocity law
22/// Returns the sign change needed to keep track of if flipping
23fn reciprocity(num: usize, den: usize) -> (i8) {
24    if num % 4 == 3 && den % 4 == 3 { -1 } else { 1 }
25}
26
27/// Returns the value of the Jacobi symbol for `(a \ n)`
28/// Where `a` is an integer and `n` is an odd positive integer
29///
30/// If the intergers passed in do not match these assumptions,
31/// the return value will be `None`. Otherwise the algorithm can be assumed to
32/// always succeed.
33///
34/// # Example
35///
36/// ```rust
37/// # extern crate quadratic;
38/// // compute the Jacobi symbol of (2 \ 5)
39/// let symb = quadratic::jacobi(2, 5);
40/// assert_eq!(Some(-1), symb);
41/// ```
42
43pub fn jacobi(a: isize, n: isize) -> Option<i8> {
44    // jacobi symbol is only defined for odd positive moduli
45    if n.is_even() || n <= 0 {
46        return None;
47    }
48
49    // Raise a mod n, then start the unsigned algorithm
50    let mut acc = 1;
51    let mut num = a.mod_floor(&n).to_usize().unwrap();
52    let mut den = n as usize;
53    loop {
54        // reduce numerator
55        num = num % den;
56        if num == 0 {
57            return Some(0);
58        }
59
60        // extract factors of two from numerator
61        while num.is_even() {
62            acc *= two_over(den);
63            num /= 2;
64        }
65        // if numerator is 1 => this sub-symbol is 1
66        if num == 1 {
67            return Some(acc);
68        }
69        // shared factors => one sub-symbol is zero
70        if num.gcd(&den) > 1 {
71            return Some(0);
72        }
73        // num and den are now odd co-prime, use reciprocity law:
74        acc *= reciprocity(num, den);
75        let tmp = num;
76        num = den;
77        den = tmp;
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use jacobi;
84
85    // legendre tests
86    #[test]
87    fn minus_one_over_p() {
88        // 1 mod 4 => 1
89        assert_eq!(Some(1), jacobi(-1, 5));
90        assert_eq!(Some(1), jacobi(-1, 13));
91        // 3 mod 4 => -1
92        assert_eq!(Some(-1), jacobi(-1, 3));
93        assert_eq!(Some(-1), jacobi(-1, 7));
94    }
95    #[test]
96    fn two_over_p() {
97        assert_eq!(Some(-1), jacobi(2, 3));
98        assert_eq!(Some(-1), jacobi(2, 5));
99        assert_eq!(Some(1), jacobi(2, 7));
100        assert_eq!(Some(1), jacobi(2, 17)); // 17 = 1 mod 8
101    }
102    #[test]
103    fn three_over_p() {
104        assert_eq!(Some(0), jacobi(3, 3));
105        assert_eq!(Some(-1), jacobi(3, 5));
106        assert_eq!(Some(-1), jacobi(3, 7));
107    }
108    #[test]
109    fn periodicity() {
110        assert_eq!(jacobi(3,5), jacobi(-2,5));
111        assert_eq!(jacobi(-1,5), jacobi(4,5));
112        assert_eq!(jacobi(11,7), jacobi(4,7));
113        assert_eq!(jacobi(-3,7), jacobi(4,7));
114        assert_eq!(jacobi(10,7), jacobi(3,7));
115    }
116
117    #[test]
118    fn jacobi_simple() { // 45 = 3*3*5
119        assert_eq!(Some(-1), jacobi(2, 45)); // -1 * -1 * -1
120        assert_eq!(Some(0), jacobi(3, 45)); // 0 * 0 * -1
121        assert_eq!(Some(-1), jacobi(7, 45)); // -1 * -1 * -1
122        assert_eq!(Some(1), jacobi(2, 15)); // (2\5) * (2\3)
123        assert_eq!(Some(-1), jacobi(1001, 9907)); // wikepedia example
124    }
125
126    #[test]
127    fn even_moduli_fails() {
128        assert_eq!(None, jacobi(2, 4));
129    }
130    #[test]
131    fn negative_moduli_fails() {
132        assert_eq!(None, jacobi(2, -3));
133    }
134}