use num_modular::ModularSymbols;
use crate::{fast_div::ConstDivisor, ubig::UBig};
use dashu_base::BitTest;
impl ModularSymbols<&UBig> for UBig {
#[inline]
fn checked_legendre(&self, n: &UBig) -> Option<i8> {
let ring = ConstDivisor::new(n.clone());
let r = ring
.reduce(self.clone())
.pow(&((n - UBig::ONE) >> 1))
.residue();
if r.is_zero() {
Some(0)
} else if r == UBig::ONE {
Some(1)
} else if r + UBig::ONE == *n {
Some(-1)
} else {
None
}
}
fn checked_jacobi(&self, n: &UBig) -> Option<i8> {
if !n.bit(0) {
return None;
}
if self.is_zero() {
return Some(if n == &UBig::ONE { 1 } else { 0 });
}
if self == &UBig::ONE {
return Some(1);
}
let mut a = self % n;
let mut n = n.clone();
let mut t: i8 = 1;
while !a.is_zero() {
let s = a.trailing_zeros().unwrap_or(0);
if s > 0 {
if s & 1 == 1 && n.bit(1) != n.bit(2) {
t = -t;
}
a = &a >> s;
}
core::mem::swap(&mut a, &mut n);
if a.bit(0) && a.bit(1) && n.bit(0) && n.bit(1) {
t = -t;
}
a %= &n;
}
Some(if n == UBig::ONE { t } else { 0 })
}
fn kronecker(&self, n: &UBig) -> i8 {
if n.is_zero() {
return if self == &UBig::ONE { 1 } else { 0 };
}
if n == &UBig::ONE {
return 1;
}
if self.is_zero() {
return 0;
}
if *n == UBig::from(2u8) {
return if !self.bit(0) {
0
} else if self.bit(1) == self.bit(2) {
1
} else {
-1
};
}
let s = n.trailing_zeros().unwrap_or(0);
if s == 0 {
return self.checked_jacobi(n).unwrap_or(0);
}
let n_odd = n >> s;
let t1 = self.kronecker(&UBig::from(2u8));
if t1 == 0 {
return 0;
}
let t2 = self.checked_jacobi(&n_odd).unwrap_or(0);
if s & 1 == 0 {
t2
} else {
t1 * t2
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn u(v: u8) -> UBig {
UBig::from(v)
}
#[test]
fn jacobi_symbol() {
assert_eq!(u(2).checked_jacobi(&u(5)), Some(-1));
assert_eq!(u(2).checked_jacobi(&u(7)), Some(1));
assert_eq!(u(2).checked_jacobi(&u(9)), Some(1)); assert_eq!(u(3).checked_jacobi(&u(5)), Some(-1));
assert_eq!(u(4).checked_jacobi(&u(5)), Some(1));
assert_eq!(u(5).checked_jacobi(&u(5)), Some(0));
assert_eq!(u(6).checked_jacobi(&u(7)), Some(-1)); assert_eq!(u(5).checked_jacobi(&u(7)), Some(-1));
assert_eq!(u(2).checked_jacobi(&u(15)), Some(1)); assert_eq!(u(14).checked_jacobi(&u(15)), Some(-1));
assert_eq!(u(0).checked_jacobi(&u(9)), Some(0));
assert_eq!(u(0).checked_jacobi(&u(1)), Some(1));
assert_eq!(u(1).checked_jacobi(&u(9)), Some(1));
assert_eq!(u(3).checked_jacobi(&u(8)), None);
assert_eq!(u(5).checked_jacobi(&u(21)), u(21).checked_jacobi(&u(5)));
assert_eq!(u(11).checked_jacobi(&u(13)), u(13).checked_jacobi(&u(11)));
assert_eq!(u(13).checked_jacobi(&u(17)), u(17).checked_jacobi(&u(13)));
assert_eq!(u(7).checked_jacobi(&u(15)), Some(-1)); assert_eq!(u(15).checked_jacobi(&u(7)), Some(1));
assert_eq!(
u(6).checked_jacobi(&u(35)),
Some(u(2).checked_jacobi(&u(35)).unwrap() * u(3).checked_jacobi(&u(35)).unwrap())
);
}
#[test]
fn legendre_symbol() {
assert_eq!(u(2).checked_legendre(&u(7)), Some(1));
assert_eq!(u(3).checked_legendre(&u(7)), Some(-1));
assert_eq!(u(7).checked_legendre(&u(7)), Some(0));
for a in [1u8, 3, 4, 5, 9] {
assert_eq!(u(a).checked_legendre(&u(11)), Some(1), "a={a}");
}
for a in [2u8, 6, 7, 8, 10] {
assert_eq!(u(a).checked_legendre(&u(11)), Some(-1), "a={a}");
}
}
#[test]
fn kronecker_symbol() {
assert_eq!(u(1).kronecker(&u(0)), 1);
assert_eq!(u(5).kronecker(&u(0)), 0);
assert_eq!(u(5).kronecker(&u(1)), 1);
assert_eq!(u(3).kronecker(&u(2)), -1);
assert_eq!(u(7).kronecker(&u(2)), 1);
assert_eq!(u(1).kronecker(&u(2)), 1);
assert_eq!(u(5).kronecker(&u(2)), -1);
assert_eq!(u(4).kronecker(&u(2)), 0); assert_eq!(u(3).kronecker(&u(8)), -1); assert_eq!(u(5).kronecker(&u(8)), -1); assert_eq!(u(4).kronecker(&u(8)), 0);
assert_eq!(u(6).kronecker(&u(35)), u(6).checked_jacobi(&u(35)).unwrap());
}
}