use super::{is_prime, jacobi, mr_witness};
use crate::Integer;
fn half_mod(x: &Integer, n: &Integer) -> Integer {
let two = Integer::from(2);
let x = x.mod_floor(n);
if x.is_even() {
x / &two
} else {
(&x + n) / &two
}
}
pub(crate) fn lucas_uv_mod(
p: &Integer,
q: &Integer,
k: &Integer,
n: &Integer,
) -> (Integer, Integer, Integer) {
let two = Integer::from(2);
if k.is_zero() {
return (
Integer::from(0),
two.mod_floor(n),
Integer::from(1).mod_floor(n),
);
}
let d = (p * p) - &(q * &Integer::from(4));
let mut bits = Vec::new();
let mut kk = k.clone();
while !kk.is_zero() {
let (quot, rem) = kk.div_rem(&two);
bits.push(rem.is_one());
kk = quot;
}
let mut u = Integer::from(0);
let mut v = two.mod_floor(n);
let mut qk = Integer::from(1).mod_floor(n);
for &bit in bits.iter().rev() {
u = (&u * &v).mod_floor(n);
v = (&v * &v - &(&qk * &two)).mod_floor(n);
qk = (&qk * &qk).mod_floor(n);
if bit {
let u_new = half_mod(&(p * &u + &v), n);
let v_new = half_mod(&(&d * &u + p * &v), n);
u = u_new;
v = v_new;
qk = (&qk * q).mod_floor(n);
}
}
(u, v, qk)
}
fn strong_lucas_prp(n: &Integer) -> bool {
let mut d_abs: i64 = 5;
let mut sign: i64 = 1;
let q = loop {
let d = Integer::from(sign * d_abs);
match jacobi(&d, n) {
-1 => break Integer::from((1 - sign * d_abs) / 4),
0 => return Integer::from(d_abs) == *n,
_ => {
d_abs += 2;
sign = -sign;
}
}
};
let p = Integer::from(1);
let one = Integer::from(1);
let mut d = n + &one;
let mut r = 0u64;
while d.is_even() {
d >>= 1;
r += 1;
}
let (u, mut v, mut qk) = lucas_uv_mod(&p, &q, &d, n);
if u.is_zero() || v.is_zero() {
return true;
}
for _ in 1..r {
v = (&v * &v - &(&qk * &Integer::from(2))).mod_floor(n);
qk = (&qk * &qk).mod_floor(n);
if v.is_zero() {
return true;
}
}
false
}
pub fn is_prime_bpsw(n: &Integer) -> bool {
let two = Integer::from(2);
if n < &two {
return false;
}
for &p in &[2u64, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] {
let pb = Integer::from(p as i64);
if *n == pb {
return true;
}
if n.mod_floor(&pb).is_zero() {
return false;
}
}
let s = n.sqrt();
if &s * &s == *n {
return false;
}
let one = Integer::from(1);
let mut d = n - &one;
let mut r = 0u64;
while d.is_even() {
d >>= 1;
r += 1;
}
if !mr_witness(n, &d, r, &two) {
return false;
}
strong_lucas_prp(n)
}
pub fn is_prime_u64(n: u64) -> bool {
is_prime(&Integer::new(n))
}
#[cfg(test)]
mod tests {
use super::*;
fn b(n: i64) -> Integer {
Integer::from(n)
}
#[test]
fn lucas_fibonacci_identity() {
let n = b(1009);
let (u, v, qk) = lucas_uv_mod(&b(1), &b(-1), &b(10), &n);
assert_eq!(u, b(55));
assert_eq!(v, b(123));
assert_eq!(qk, b(1));
}
#[test]
fn lucas_matches_recurrence() {
let n = b(1_000_003);
let p = b(3);
let q = b(-2);
let mut u_prev = b(0); let mut u_cur = b(1); let mut v_prev = b(2); let mut v_cur = p.clone(); for k in 1..60 {
let kb = b(k);
let (u, v, _) = lucas_uv_mod(&p, &q, &kb, &n);
assert_eq!(u, u_cur.mod_floor(&n), "U_{k} mismatch");
assert_eq!(v, v_cur.mod_floor(&n), "V_{k} mismatch");
let u_next = (&p * &u_cur - &q * &u_prev).mod_floor(&n);
let v_next = (&p * &v_cur - &q * &v_prev).mod_floor(&n);
u_prev = u_cur;
u_cur = u_next;
v_prev = v_cur;
v_cur = v_next;
}
}
#[test]
fn bpsw_accepts_primes() {
let primes = [
"97",
"7919",
"1000003",
"2147483647", "2305843009213693951", "1000000000000000009", ];
for s in primes {
let n: Integer = s.parse::<num_bigint::BigInt>().unwrap().into();
assert!(is_prime_bpsw(&n), "{s} should pass BPSW");
}
}
#[test]
fn bpsw_rejects_composites() {
let composites = [
561i64, 1105, 1729, 2465, 2821, 6601, 8911, 41041, 825265, 2047, 3277, 4033, 4681,
8321, 15841, 29341, 42799, 49141, 52633, 65281, 74665, 80581, 85489, 88357, 90751,
];
for c in composites {
assert!(!is_prime_bpsw(&b(c)), "{c} must be rejected by BPSW");
}
for p in [41i64, 101, 1009] {
assert!(!is_prime_bpsw(&b(p * p)));
}
let n = b(1_000_003) * b(1_000_033);
assert!(!is_prime_bpsw(&n));
}
#[test]
fn is_prime_u64_boundary() {
assert!(is_prime_u64(2));
assert!(is_prime_u64(u64::MAX - 58)); assert!(!is_prime_u64(u64::MAX)); assert!(!is_prime_u64(0));
assert!(!is_prime_u64(1));
}
#[cfg(test)]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn primality_tests_agree(x in any::<u64>()) {
let n = Integer::new(x);
assert_eq!(is_prime(&n), is_prime_bpsw(&n), "is_prime vs BPSW at {x}");
assert_eq!(is_prime(&n), is_prime_u64(x), "is_prime vs u64 at {x}");
}
}
}
}