use super::lucas::is_odd;
use super::mod_arith::{mod_mul, mod_pow};
use super::uint::BigUint;
use oxinum_core::{OxiNumError, OxiNumResult};
fn factor_out_two(n_minus_1: &BigUint) -> (BigUint, u64) {
let s = n_minus_1.trailing_zeros();
let d = n_minus_1.shr_bits(s);
(d, s)
}
fn miller_rabin_witness(n: &BigUint, d: &BigUint, s: u64, a: u64) -> OxiNumResult<bool> {
let a_big = BigUint::from(a);
let n_minus_1 = n
.checked_sub(&BigUint::one())
.ok_or_else(|| OxiNumError::Domain("miller_rabin_witness: n must be > 1".into()))?;
let mut x = mod_pow(&a_big, d, n)?;
if x == BigUint::one() || x == n_minus_1 {
return Ok(true); }
for _ in 0..s.saturating_sub(1) {
x = mod_pow(&x, &BigUint::from(2u64), n)?;
if x == n_minus_1 {
return Ok(true); }
}
Ok(false) }
fn biguint_to_u128(n: &BigUint) -> Option<u128> {
let limbs = n.as_limbs();
match limbs.len() {
0 => Some(0),
1 => Some(limbs[0] as u128),
2 => Some(((limbs[1] as u128) << 64) | (limbs[0] as u128)),
_ => None, }
}
static WITNESSES_2: &[u64] = &[2];
static WITNESSES_3: &[u64] = &[2, 3];
static WITNESSES_4: &[u64] = &[2, 3, 5];
static WITNESSES_5: &[u64] = &[2, 3, 5, 7];
static WITNESSES_6: &[u64] = &[2, 3, 5, 7, 11];
static WITNESSES_7: &[u64] = &[2, 3, 5, 7, 11, 13];
static WITNESSES_8: &[u64] = &[2, 3, 5, 7, 11, 13, 17];
static WITNESSES_9: &[u64] = &[2, 3, 5, 7, 11, 13, 17, 19, 23];
static WITNESSES_12: &[u64] = &[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
fn select_witnesses(n128: u128) -> Option<&'static [u64]> {
if n128 < 2_047 {
Some(WITNESSES_2)
} else if n128 < 1_373_653 {
Some(WITNESSES_3)
} else if n128 < 25_326_001 {
Some(WITNESSES_4)
} else if n128 < 3_215_031_751 {
Some(WITNESSES_5)
} else if n128 < 2_152_302_898_747 {
Some(WITNESSES_6)
} else if n128 < 3_474_749_660_383 {
Some(WITNESSES_7)
} else if n128 < 341_550_071_728_321 {
Some(WITNESSES_8)
} else if n128 < 3_825_123_056_546_413_051 {
Some(WITNESSES_9)
} else if n128 < 318_665_857_834_031_151_167_461 {
Some(WITNESSES_12)
} else if n128 < 3_317_044_064_679_887_385_961_981 {
Some(&[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41])
} else {
None
}
}
pub(crate) fn jacobi(a: i64, n: &BigUint) -> i32 {
if n.is_zero() || n == &BigUint::one() {
return 0;
}
if !is_odd(n) {
return 0;
}
let mut result: i32 = 1;
let mut a = a;
if a < 0 {
a = -a;
let n_mod4 = n.as_limbs().first().copied().unwrap_or(0) & 3;
if n_mod4 == 3 {
result = -result;
}
}
if a == 0 {
return 0;
}
let n_limbs = n.as_limbs();
let mut a_reduced: u64 = if n_limbs.len() == 1 {
a as u64 % n_limbs[0]
} else {
a as u64
};
let mut n_big = n.clone();
if a_reduced == 0 {
return 0;
}
let s2 = a_reduced.trailing_zeros();
a_reduced >>= s2;
if s2 > 0 {
let n_mod8 = n_big.as_limbs().first().copied().unwrap_or(0) & 7;
let sym2: i32 = if n_mod8 == 1 || n_mod8 == 7 { 1 } else { -1 };
if sym2 == -1 && s2 % 2 == 1 {
result = -result;
}
}
if a_reduced == 1 {
return result;
}
loop {
let a_mod4 = a_reduced & 3;
let n_mod4 = n_big.as_limbs().first().copied().unwrap_or(0) & 3;
if a_mod4 == 3 && n_mod4 == 3 {
result = -result;
}
let a_bu = BigUint::from(a_reduced);
let (_, rem) = super::div::divrem(&n_big, &a_bu);
let new_a_raw = rem.as_limbs().first().copied().unwrap_or(0);
n_big = a_bu; a_reduced = new_a_raw;
if a_reduced == 0 {
return 0;
}
let s2 = a_reduced.trailing_zeros();
a_reduced >>= s2;
if s2 > 0 {
let nb_mod8 = n_big.as_limbs().first().copied().unwrap_or(0) & 7;
let sym2: i32 = if nb_mod8 == 1 || nb_mod8 == 7 { 1 } else { -1 };
if sym2 == -1 && s2 % 2 == 1 {
result = -result;
}
}
if a_reduced == 1 {
return result;
}
}
}
pub(crate) fn selfridge_params(n: &BigUint) -> Option<(i64, i64, i64)> {
let sqrt_n = n.sqrt();
let sq = &sqrt_n * &sqrt_n;
if &sq == n {
return None; }
let mut step: i64 = 0;
loop {
let d_abs: i64 = 5 + 2 * step;
let d_val: i64 = if step % 2 == 0 { d_abs } else { -d_abs };
let j = jacobi(d_val, n);
if j == -1 {
let q: i64 = (1 - d_val) / 4;
return Some((d_val, 1, q));
}
if j == 0 {
let d_abs = d_val.unsigned_abs();
let n_limbs = n.as_limbs();
let n_divides_d = n_limbs.len() == 1 && d_abs % n_limbs[0] == 0;
if !n_divides_d {
return None;
}
}
step += 1;
if step > 500 {
return None;
}
}
}
fn strong_lucas_probable_prime(n: &BigUint, p: i64, q: i64) -> bool {
let n_plus_1 = BigUint::add_ref(n, &BigUint::one());
let (d, s) = factor_out_two(&n_plus_1);
let uv = match super::lucas::lucas_uv(&d, p, q, n) {
Ok(v) => v,
Err(_) => return false,
};
let (u_d, mut v_r) = uv;
if u_d.is_zero() {
return true;
}
let q_reduced = if q >= 0 {
let bq = BigUint::from(q as u64);
let (_, rem) = super::div::divrem(&bq, n);
rem
} else {
let bq = BigUint::from(q.unsigned_abs());
let (_, rem) = super::div::divrem(&bq, n);
if rem.is_zero() {
BigUint::zero()
} else {
n.checked_sub(&rem).unwrap_or_else(BigUint::zero)
}
};
let mut qk = match mod_pow(&q_reduced, &d, n) {
Ok(v) => v,
Err(_) => return false,
};
for _r in 0..s {
if v_r.is_zero() {
return true;
}
if _r == s - 1 {
break; }
let v_sq = match mod_mul(&v_r, &v_r, n) {
Ok(v) => v,
Err(_) => return false,
};
let two_qk = match mod_mul(&BigUint::from(2u64), &qk, n) {
Ok(v) => v,
Err(_) => return false,
};
v_r = if v_sq >= two_qk {
v_sq.checked_sub(&two_qk).unwrap_or_else(BigUint::zero)
} else {
let sum = BigUint::add_ref(&v_sq, n);
sum.checked_sub(&two_qk).unwrap_or_else(BigUint::zero)
};
qk = match mod_mul(&qk, &qk, n) {
Ok(v) => v,
Err(_) => return false,
};
}
false }
pub(crate) fn bpsw_probable_prime(n: &BigUint) -> bool {
let n_minus_1 = match n.checked_sub(&BigUint::one()) {
Some(v) => v,
None => return false,
};
let (d, s) = factor_out_two(&n_minus_1);
match miller_rabin_witness(n, &d, s, 2) {
Ok(true) => {} Ok(false) => return false,
Err(_) => return false,
}
match selfridge_params(n) {
None => false, Some((_d_val, p, q)) => strong_lucas_probable_prime(n, p, q),
}
}
pub fn is_probably_prime(n: &BigUint) -> bool {
if n < &BigUint::from(2u64) {
return false;
}
if n == &BigUint::from(2u64) || n == &BigUint::from(3u64) {
return true;
}
if !is_odd(n) {
return false;
}
match biguint_to_u128(n) {
Some(n128) => match select_witnesses(n128) {
Some(witnesses) => {
let n_minus_1 = match n.checked_sub(&BigUint::one()) {
Some(v) => v,
None => return false,
};
let (d, s) = factor_out_two(&n_minus_1);
for &a in witnesses {
match miller_rabin_witness(n, &d, s, a) {
Ok(true) => continue,
Ok(false) => return false,
Err(_) => return false,
}
}
true
}
None => {
bpsw_probable_prime(n)
}
},
None => {
bpsw_probable_prime(n)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::native::mod_arith::mod_pow;
use crate::native::sieve::prime_sieve;
fn bu(n: u64) -> BigUint {
BigUint::from(n)
}
#[test]
fn jacobi_corner_cases() {
assert_eq!(jacobi(0, &bu(7)), 0);
assert_eq!(jacobi(0, &bu(15)), 0);
assert_eq!(jacobi(1, &bu(5)), 1);
assert_eq!(jacobi(1, &bu(7)), 1);
assert_eq!(jacobi(1, &bu(99)), 1);
}
#[test]
fn jacobi_minus1() {
assert_eq!(jacobi(-1, &bu(5)), 1); assert_eq!(jacobi(-1, &bu(13)), 1); assert_eq!(jacobi(-1, &bu(7)), -1); assert_eq!(jacobi(-1, &bu(11)), -1); }
#[test]
fn jacobi_matches_legendre_for_primes() {
for &p in &[5u64, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] {
for a in 1u64..p {
let exp = (p - 1) / 2;
let leg_raw = mod_pow(&bu(a), &bu(exp), &bu(p)).expect("mod_pow");
let expected: i32 = if leg_raw == bu(1) { 1 } else { -1 };
let got = jacobi(a as i64, &bu(p));
assert_eq!(
got, expected,
"jacobi({a}, {p}) = {got} but Legendre = {expected}"
);
}
}
}
#[test]
fn jacobi_multiplicativity() {
let cases = [(3i64, 5u64, 7u64), (5, 9, 11), (7, 13, 15), (11, 21, 25)];
for (a, m, n) in cases {
let mn = bu(m * n);
if !is_odd(&mn) {
continue;
}
let j_mn = jacobi(a, &mn);
let j_m = jacobi(a, &bu(m));
let j_n = jacobi(a, &bu(n));
if is_odd(&bu(m)) && is_odd(&bu(n)) && m > 1 && n > 1 {
assert_eq!(
j_mn,
j_m * j_n,
"jacobi({a}, {}) = {j_mn} but jacobi({a},{m})*jacobi({a},{n}) = {}",
m * n,
j_m * j_n
);
}
}
}
#[test]
fn jacobi_known_values() {
assert_eq!(jacobi(2, &bu(7)), 1); assert_eq!(jacobi(2, &bu(5)), -1); assert_eq!(jacobi(2, &bu(17)), 1); assert_eq!(jacobi(2, &bu(11)), -1); assert_eq!(jacobi(3, &bu(5)), -1); assert_eq!(jacobi(3, &bu(7)), -1); assert_eq!(jacobi(4, &bu(7)), 1); assert_eq!(jacobi(9, &bu(35)), 1); assert_eq!(jacobi(6, &bu(9)), 0);
}
#[test]
fn selfridge_perfect_squares_return_none() {
let squares = [
1009u64 * 1009,
65537u64 * 65537,
999983u64 * 999983,
4u64, 9u64, 49u64, ];
for &sq in &squares {
let n = bu(sq);
if is_odd(&n) && n > bu(3) {
assert!(
selfridge_params(&n).is_none(),
"selfridge_params({sq}) should be None (perfect square)"
);
}
}
}
#[test]
fn selfridge_primes_return_some() {
for &p in &[5u64, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] {
let (d_val, p_val, q_val) = selfridge_params(&bu(p))
.unwrap_or_else(|| panic!("selfridge_params({p}) returned None"));
assert_eq!(p_val, 1, "P must be 1 for prime {p}");
assert_eq!(q_val, (1 - d_val) / 4, "Q mismatch for prime {p}");
assert_eq!(
jacobi(d_val, &bu(p)),
-1,
"jacobi(D={d_val}, {p}) must be -1"
);
}
}
#[test]
fn bpsw_matches_sieve_for_odd_n() {
let sieve_primes = prime_sieve(10_000);
for n in (5u64..10_000).step_by(2) {
let expected = sieve_primes.binary_search(&n).is_ok();
let got = bpsw_probable_prime(&bu(n));
assert_eq!(
got, expected,
"bpsw_probable_prime({n}) = {got} but expected {expected}"
);
}
}
#[test]
fn bpsw_base2_strong_pseudoprimes_are_composite() {
for &n in &[2047u64, 3277, 4033, 4681, 8321, 15841, 29341, 42799, 49141] {
let result = bpsw_probable_prime(&bu(n));
assert!(
!result,
"base-2 strong pseudoprime {n} should fail BPSW (Lucas should catch it)"
);
}
}
#[test]
fn bpsw_strong_lucas_pseudoprimes_are_composite() {
for &n in &[5459u64, 5777, 10877, 16109, 18971] {
assert!(
!bpsw_probable_prime(&bu(n)),
"strong Lucas pseudoprime {n} should fail BPSW (MR-base-2 catches it)"
);
}
}
#[test]
fn primality_trivial() {
assert!(!is_probably_prime(&bu(0)));
assert!(!is_probably_prime(&bu(1)));
assert!(is_probably_prime(&bu(2)));
assert!(is_probably_prime(&bu(3)));
assert!(!is_probably_prime(&bu(4)));
assert!(is_probably_prime(&bu(5)));
}
#[test]
fn primality_matches_sieve_up_to_10000() {
let sieve_primes = prime_sieve(10_000);
for n in 2u64..10_000 {
let expected = sieve_primes.binary_search(&n).is_ok();
let got = is_probably_prime(&bu(n));
assert_eq!(got, expected, "primality mismatch at n={}", n);
}
}
#[test]
fn carmichael_numbers_are_composite() {
for &n in &[561u64, 1105, 1729, 2465, 2821, 6601, 8911, 10585] {
assert!(
!is_probably_prime(&bu(n)),
"Carmichael {} was incorrectly identified as prime",
n
);
}
}
#[test]
fn mersenne_primes() {
for p in [7u32, 13, 17, 19, 31] {
let m = BigUint::from(2u64)
.pow(p)
.checked_sub(&BigUint::one())
.expect("2^p > 1");
assert!(is_probably_prime(&m), "2^{}-1 should be prime", p);
}
}
#[test]
fn mersenne_composite() {
let m2047 = bu(2047);
assert!(!is_probably_prime(&m2047));
}
#[test]
fn large_known_prime() {
let m31 = BigUint::from(2u64)
.pow(31)
.checked_sub(&BigUint::one())
.expect("2^31 > 1");
assert!(is_probably_prime(&m31));
}
#[test]
fn bpsw_large_mersenne_primes() {
let m61 = BigUint::from(2u64)
.pow(61)
.checked_sub(&BigUint::one())
.expect("2^61 > 1");
assert!(is_probably_prime(&m61), "M_61 should be prime");
}
#[test]
fn bpsw_perfect_square_is_composite() {
let n1 = bu(1009) * bu(1009); let n2 = bu(65537) * bu(65537);
let n3 = bu(999983) * bu(999983); assert!(!is_probably_prime(&n1), "1009^2 should be composite");
assert!(!is_probably_prime(&n2), "65537^2 should be composite");
assert!(!is_probably_prime(&n3), "999983^2 should be composite");
}
}