use crate::exact::bigint::BigInt;
use crate::monte_carlo::Rng;
#[must_use]
pub fn sieve_eratosthenes(n: usize) -> Vec<usize> {
if n < 2 {
return Vec::new();
}
let mut is_p = vec![true; n + 1];
is_p[0] = false;
is_p[1] = false;
let mut i = 2usize;
while i * i <= n {
if is_p[i] {
let mut j = i * i;
while j <= n {
is_p[j] = false;
j += i;
}
}
i += 1;
}
(2..=n).filter(|&k| is_p[k]).collect()
}
#[must_use]
pub fn sieve_segmented(lo: u64, hi: u64) -> Vec<u64> {
if hi <= 2 || hi <= lo {
return Vec::new();
}
let lo = lo.max(2);
let root = (hi as f64).sqrt() as usize + 1;
let base = sieve_eratosthenes(root);
let len = (hi - lo) as usize;
let mut is_p = vec![true; len];
for p in base {
let p = p as u64;
let Some(square) = p.checked_mul(p) else { break };
if square >= hi {
break;
}
let rem = lo % p;
let first = if rem == 0 {
Some(lo)
} else {
lo.checked_add(p - rem)
};
let Some(first) = first else { continue };
let mut m = first.max(square);
while m < hi {
is_p[(m - lo) as usize] = false;
m += p;
}
}
(0..len)
.filter(|&i| is_p[i])
.map(|i| lo + i as u64)
.collect()
}
#[must_use]
pub fn sieve_linear(n: usize) -> (Vec<usize>, Vec<usize>) {
let mut spf = vec![0usize; n + 1];
let mut primes = Vec::new();
for i in 2..=n {
if spf[i] == 0 {
spf[i] = i;
primes.push(i);
}
for &p in &primes {
if p > spf[i] || i * p > n {
break;
}
spf[i * p] = p;
}
}
(primes, spf)
}
fn mul_mod(a: u64, b: u64, m: u64) -> u64 {
((u128::from(a) * u128::from(b)) % u128::from(m)) as u64
}
#[must_use]
pub fn mod_pow_u64(mut base: u64, mut exp: u64, m: u64) -> u64 {
if m == 1 {
return 0;
}
let mut acc = 1u64;
base %= m;
while exp > 0 {
if exp & 1 == 1 {
acc = mul_mod(acc, base, m);
}
base = mul_mod(base, base, m);
exp >>= 1;
}
acc
}
#[must_use]
pub fn is_prime_u64(n: u64) -> bool {
if n < 2 {
return false;
}
for p in [2u64, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] {
if n.is_multiple_of(p) {
return n == p;
}
}
let mut d = n - 1;
let mut r = 0u32;
while d.is_multiple_of(2) {
d /= 2;
r += 1;
}
'base: for a in [2u64, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] {
let mut x = mod_pow_u64(a, d, n);
if x == 1 || x == n - 1 {
continue;
}
for _ in 1..r {
x = mul_mod(x, x, n);
if x == n - 1 {
continue 'base;
}
}
return false;
}
true
}
fn mr_round(n: &BigInt, d: &BigInt, r: u32, a: &BigInt) -> bool {
let n_minus_1 = n.sub(&BigInt::one());
let mut x = a.mod_pow(d, n);
if x == BigInt::one() || x == n_minus_1 {
return true;
}
for _ in 1..r {
x = x.mul(&x).rem_euclid(n);
if x == n_minus_1 {
return true;
}
}
false
}
#[must_use]
pub fn is_prime_bigint(n: &BigInt, rounds: usize, rng: &mut Rng) -> bool {
assert!(!n.is_negative(), "primality is defined for non-negative integers");
if let Some(small) = n.to_i64() {
if (0..(1 << 62)).contains(&small) {
return is_prime_u64(small as u64);
}
}
if n.is_even() {
return false;
}
let one = BigInt::one();
let two = BigInt::from_u64(2);
let n_minus_1 = n.sub(&one);
let mut d = n_minus_1.clone();
let mut r = 0u32;
while d.is_even() {
d = d.shr(1);
r += 1;
}
if !mr_round(n, &d, r, &two) {
return false;
}
for _ in 0..rounds {
let a = BigInt::random_below(&n.sub(&BigInt::from_u64(3)), rng).add(&two);
if !mr_round(n, &d, r, &a) {
return false;
}
}
strong_lucas_probable_prime(n)
}
fn strong_lucas_probable_prime(n: &BigInt) -> bool {
if n.is_perfect_square() {
return false;
}
let mut d_val: i64 = 5;
loop {
let j = jacobi_bigint(d_val, n);
if j == -1 {
break;
}
if j == 0 && n.abs() != BigInt::from_u64(d_val.unsigned_abs()) {
return false;
}
d_val = if d_val > 0 { -(d_val + 2) } else { -(d_val - 2) };
if d_val.abs() > 1_000_000 {
return false;
}
}
let p = BigInt::one();
let q_val = (1 - d_val) / 4;
let q = BigInt::from_i64(q_val);
let mut dd = n.add(&BigInt::one());
let mut s = 0u32;
while dd.is_even() {
dd = dd.shr(1);
s += 1;
}
let (mut u, mut v) = (BigInt::one(), p.clone());
let mut q_k = q.clone();
let bits = dd.bits();
for i in (0..bits.saturating_sub(1)).rev() {
u = u.mul(&v).rem_euclid(n);
v = v.mul(&v).sub(&q_k.mul(&BigInt::from_u64(2))).rem_euclid(n);
q_k = q_k.mul(&q_k).rem_euclid(n);
if dd.bit(i) {
let u_next = u.add(&v);
let v_next = v.add(&u.mul(&BigInt::from_i64(d_val)));
u = half_mod(&u_next, n);
v = half_mod(&v_next, n);
q_k = q_k.mul(&q).rem_euclid(n);
}
}
if u.is_zero() || v.is_zero() {
return true;
}
for _ in 1..s {
v = v.mul(&v).sub(&q_k.mul(&BigInt::from_u64(2))).rem_euclid(n);
if v.is_zero() {
return true;
}
q_k = q_k.mul(&q_k).rem_euclid(n);
}
false
}
fn half_mod(x: &BigInt, n: &BigInt) -> BigInt {
let v = x.rem_euclid(n);
if v.is_even() {
v.shr(1)
} else {
v.add(n).shr(1)
}
}
fn jacobi_bigint(a: i64, n: &BigInt) -> i8 {
let mut a_big = BigInt::from_i64(a).rem_euclid(n);
let mut n_big = n.clone();
let mut result = 1i8;
while !a_big.is_zero() {
while a_big.is_even() {
a_big = a_big.shr(1);
let r = n_big.rem_euclid(&BigInt::from_u64(8)).to_i64().unwrap_or(0);
if r == 3 || r == 5 {
result = -result;
}
}
std::mem::swap(&mut a_big, &mut n_big);
let ra = a_big.rem_euclid(&BigInt::from_u64(4)).to_i64().unwrap_or(0);
let rn = n_big.rem_euclid(&BigInt::from_u64(4)).to_i64().unwrap_or(0);
if ra == 3 && rn == 3 {
result = -result;
}
a_big = a_big.rem_euclid(&n_big);
}
if n_big == BigInt::one() {
result
} else {
0
}
}
#[must_use]
pub fn next_prime(n: u64) -> u64 {
if n < 2 {
return 2;
}
let mut c = n + 1;
while !is_prime_u64(c) {
c = c.checked_add(1).expect("no further u64 prime");
}
c
}
#[must_use]
pub fn prev_prime(n: u64) -> Option<u64> {
if n <= 2 {
return None;
}
let mut c = n - 1;
loop {
if is_prime_u64(c) {
return Some(c);
}
c -= 1;
}
}
#[must_use]
pub fn random_prime(bits: usize, rng: &mut Rng) -> BigInt {
assert!(bits >= 2, "need at least two bits");
loop {
let mut c = BigInt::random_bits(bits, rng);
if c.is_even() {
c = c.add(&BigInt::one());
}
if c.bits() != bits {
continue;
}
if is_prime_bigint(&c, 8, rng) {
return c;
}
}
}
#[must_use]
pub fn pollard_rho(n: u64) -> Option<u64> {
if n.is_multiple_of(2) {
return Some(2);
}
if n < 4 || is_prime_u64(n) {
return None;
}
for c in 1..64u64 {
let f = |x: u64| (mul_mod(x, x, n) + c) % n;
let (mut x, mut y, mut d) = (2u64, 2u64, 1u64);
while d == 1 {
x = f(x);
y = f(f(y));
d = gcd(x.abs_diff(y), n);
}
if d != n {
return Some(d);
}
}
None
}
fn smallest_factor_by_trial(n: u64) -> Option<u64> {
if n < 4 {
return None;
}
let mut d = 2u64;
while d.checked_mul(d).is_some_and(|dd| dd <= n) {
if n.is_multiple_of(d) {
return Some(d);
}
d += 1;
}
None
}
fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
let t = a % b;
a = b;
b = t;
}
a
}
#[must_use]
pub fn pollard_rho_bigint(n: &BigInt, rng: &mut Rng) -> Option<BigInt> {
if n.is_even() {
return Some(BigInt::from_u64(2));
}
let one = BigInt::one();
for _ in 0..16 {
let c = BigInt::random_below(n, rng).add(&one);
let mut x = BigInt::random_below(n, rng);
let mut y = x.clone();
let mut d = one.clone();
let f = |v: &BigInt| v.mul(v).add(&c).rem_euclid(n);
let mut steps = 0u32;
while d == one && steps < 200_000 {
x = f(&x);
y = f(&f(&y));
let diff = x.sub(&y).abs();
if diff.is_zero() {
break;
}
d = diff.gcd(n);
steps += 1;
}
if d != one && d != *n {
return Some(d);
}
}
None
}
#[must_use]
pub fn pollard_p_minus_1(n: u64, bound: u64) -> Option<u64> {
if n.is_multiple_of(2) {
return Some(2);
}
let mut a = 2u64;
for q in sieve_eratosthenes(bound as usize) {
let q = q as u64;
let mut e = q;
while e <= bound {
a = mod_pow_u64(a, q, n);
e = e.saturating_mul(q);
}
let d = gcd(a.wrapping_sub(1), n);
if d > 1 && d < n {
return Some(d);
}
}
None
}
#[must_use]
pub fn trial_division(mut n: u64, limit: u64) -> (Vec<(u64, u32)>, u64) {
let mut out = Vec::new();
let mut p = 2u64;
while p <= limit && p.saturating_mul(p) <= n {
if n.is_multiple_of(p) {
let mut e = 0u32;
while n.is_multiple_of(p) {
n /= p;
e += 1;
}
out.push((p, e));
}
p += if p == 2 { 1 } else { 2 };
}
(out, n)
}
#[must_use]
pub fn fermat_factor(n: u64) -> Option<(u64, u64)> {
if n.is_multiple_of(2) {
return Some((2, n / 2));
}
let start = (n as f64).sqrt().ceil() as u64;
for a in start..start.saturating_add(1_000_000) {
let b2 = a.checked_mul(a)?.checked_sub(n)?;
let b = (b2 as f64).sqrt().round() as u64;
if b * b == b2 {
return Some((a - b, a + b));
}
}
None
}
#[must_use]
pub fn factorize(n: u64) -> Vec<(u64, u32)> {
if n < 2 {
return Vec::new();
}
let (mut out, rest) = trial_division(n, 100_000);
if rest > 1 {
let mut stack = vec![rest];
let mut found: Vec<u64> = Vec::new();
while let Some(m) = stack.pop() {
if m == 1 {
continue;
}
if is_prime_u64(m) {
found.push(m);
continue;
}
let d = pollard_rho(m)
.or_else(|| smallest_factor_by_trial(m))
.expect("a composite has a factor at or below its square root");
stack.push(d);
stack.push(m / d);
}
found.sort_unstable();
for f in found {
match out.iter_mut().find(|(p, _)| *p == f) {
Some((_, e)) => *e += 1,
None => out.push((f, 1)),
}
}
}
out.sort_unstable();
out
}
#[must_use]
pub fn factorize_bigint(n: &BigInt, rng: &mut Rng) -> Vec<(BigInt, u32)> {
assert!(!n.is_negative() && !n.is_zero(), "factorization needs a positive integer");
let mut out: Vec<(BigInt, u32)> = Vec::new();
let mut stack = vec![n.clone()];
while let Some(m) = stack.pop() {
if m == BigInt::one() {
continue;
}
if is_prime_bigint(&m, 8, rng) {
match out.iter_mut().find(|(p, _)| *p == m) {
Some((_, e)) => *e += 1,
None => out.push((m, 1)),
}
continue;
}
let split = pollard_rho_bigint(&m, rng)
.or_else(|| pollard_rho_bigint(&m, rng))
.or_else(|| pollard_rho_bigint(&m, rng));
match split {
Some(d) => {
let other = m.div_rem(&d).0;
stack.push(d);
stack.push(other);
}
None => match out.iter_mut().find(|(p, _)| *p == m) {
Some((_, e)) => *e += 1,
None => out.push((m, 1)),
},
}
}
out.sort_by(|a, b| a.0.cmp(&b.0));
out
}
#[must_use]
pub fn prime_count_meissel(n: u64) -> u64 {
if n < 2 {
return 0;
}
let r = (n as f64).sqrt() as u64;
let r = (r + 2).min(n);
let r = (0..=r).rev().find(|&k| k * k <= n).expect("root exists");
let mut small: Vec<u64> = vec![0; (r + 1) as usize]; let mut large: Vec<u64> = vec![0; (r + 1) as usize]; for v in 1..=r {
small[v as usize] = v - 1;
}
for i in 1..=r {
large[i as usize] = n / i - 1;
}
for p in 2..=r {
if small[p as usize] == small[(p - 1) as usize] {
continue; }
let sp = small[(p - 1) as usize];
let p2 = p * p;
let lim = (n / p2).min(r);
for i in 1..=lim {
let d = i * p;
large[i as usize] -= if d <= r {
large[d as usize] - sp
} else {
small[(n / d) as usize] - sp
};
}
let mut v = r;
while v >= p2 {
small[v as usize] -= small[(v / p) as usize] - sp;
v -= 1;
}
}
large[1]
}
#[must_use]
pub fn prime_count_li_approx(x: f64) -> f64 {
if x <= 1.0 {
return 0.0;
}
let l = x.ln();
let gamma = 0.577_215_664_901_532_9_f64;
let mut sum = gamma + l.abs().ln();
let mut term = 1.0f64;
for k in 1..200 {
term *= l / k as f64;
sum += term / k as f64;
if term.abs() < 1e-18 * sum.abs() {
break;
}
}
sum - 1.045_163_780_117_493
}
#[must_use]
pub fn riemann_r(x: f64) -> f64 {
if x <= 1.0 {
return 0.0;
}
let mu = mobius_small(64);
let mut sum = 0.0;
for k in 1..64usize {
if mu[k] == 0 {
continue;
}
let root = x.powf(1.0 / k as f64);
if root < 2.0 {
break;
}
sum += f64::from(mu[k]) / k as f64 * prime_count_li_approx(root);
}
sum
}
fn mobius_small(n: usize) -> Vec<i8> {
let mut mu = vec![1i8; n + 1];
let mut primes = vec![true; n + 1];
for i in 2..=n {
if primes[i] {
let mut j = i;
while j <= n {
if j > i {
primes[j] = false;
}
mu[j] = -mu[j];
j += i;
}
let sq = i * i;
let mut j = sq;
while j <= n {
mu[j] = 0;
j += sq;
}
}
}
mu
}
#[must_use]
pub fn nth_prime(n: usize) -> u64 {
assert!(n > 0, "primes are numbered from one");
if n < 6 {
return [2u64, 3, 5, 7, 11][n - 1];
}
let fl = n as f64;
let limit = (fl * (fl.ln() + fl.ln().ln())).ceil() as usize + 10;
let primes = sieve_eratosthenes(limit);
primes[n - 1] as u64
}
#[must_use]
pub fn prime_gaps(n: usize) -> Vec<u64> {
let p = sieve_eratosthenes(n);
p.windows(2).map(|w| (w[1] - w[0]) as u64).collect()
}
#[must_use]
pub fn twin_primes(n: usize) -> Vec<(u64, u64)> {
let p = sieve_eratosthenes(n);
p.windows(2)
.filter(|w| w[1] - w[0] == 2)
.map(|w| (w[0] as u64, w[1] as u64))
.collect()
}
#[must_use]
pub fn goldbach_partitions(n: u64) -> Vec<(u64, u64)> {
if n < 4 || !n.is_multiple_of(2) {
return Vec::new();
}
sieve_eratosthenes(n as usize / 2)
.into_iter()
.map(|p| p as u64)
.filter(|&p| is_prime_u64(n - p))
.map(|p| (p, n - p))
.collect()
}
#[must_use]
pub fn primes_in_arithmetic_progression(a: u64, d: u64, count: usize) -> Vec<u64> {
assert!(d > 0, "step must be positive");
let mut out = Vec::with_capacity(count);
let mut v = a;
while out.len() < count {
if is_prime_u64(v) {
out.push(v);
}
v = match v.checked_add(d) {
Some(x) => x,
None => break,
};
}
out
}
#[must_use]
pub fn mersenne_lucas_lehmer(p: u32) -> bool {
if p == 2 {
return true;
}
if p < 2 || !is_prime_u64(u64::from(p)) {
return false;
}
let m = BigInt::one().shl(p as usize).sub(&BigInt::one());
let mut s = BigInt::from_u64(4);
let two = BigInt::from_u64(2);
for _ in 0..(p - 2) {
s = s.mul(&s).sub(&two).rem_euclid(&m);
}
s.is_zero()
}
#[must_use]
pub fn wilson_check(p: u64) -> bool {
if p < 2 {
return false;
}
let mut acc = 1u64;
for k in 2..p {
acc = mul_mod(acc, k, p);
}
acc == p - 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sieves_agree_with_each_other() {
let p = sieve_eratosthenes(100);
assert_eq!(p, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
53, 59, 61, 67, 71, 73, 79, 83, 89, 97]);
assert!(sieve_eratosthenes(1).is_empty());
assert_eq!(sieve_eratosthenes(2), [2]);
let n = 20_000usize;
let era = sieve_eratosthenes(n);
let (lin, spf) = sieve_linear(n);
assert_eq!(era, lin, "linear sieve disagrees with Eratosthenes");
let seg: Vec<u64> = sieve_segmented(0, n as u64 + 1);
assert_eq!(seg, era.iter().map(|&x| x as u64).collect::<Vec<_>>());
for k in 0..=n {
assert_eq!(era.binary_search(&k).is_ok(), is_prime_u64(k as u64), "n={k}");
}
for k in 2..=n {
let f = spf[k];
assert!(is_prime_u64(f as u64) && k % f == 0, "spf({k}) = {f}");
assert!((2..f).all(|d| k % d != 0), "spf({k}) is not smallest");
}
let seg = sieve_segmented(1_000_000, 1_000_100);
assert_eq!(seg, [1_000_003, 1_000_033, 1_000_037, 1_000_039, 1_000_081, 1_000_099]);
assert!(seg.iter().all(|&p| is_prime_u64(p)));
assert!(sieve_segmented(10, 10).is_empty());
}
#[test]
fn test_primality_and_navigation() {
for c in [561u64, 1105, 1729, 2465, 2821, 6601, 8911] {
assert!(!is_prime_u64(c), "{c} is a Carmichael number, not a prime");
}
assert!(is_prime_u64(2_147_483_647), "2^31-1 is prime");
assert!(is_prime_u64(18_446_744_073_709_551_557), "largest u64 prime");
assert!(!is_prime_u64(18_446_744_073_709_551_615), "2^64-1 is composite");
assert!(!is_prime_u64(3_215_031_751), "smallest strong pseudoprime to 2,3,5,7");
assert!(!is_prime_u64(1) && !is_prime_u64(0));
assert_eq!(next_prime(0), 2);
assert_eq!(next_prime(7), 11);
assert_eq!(next_prime(89), 97);
assert_eq!(prev_prime(11), Some(7));
assert_eq!(prev_prime(2), None);
for n in 3..2000u64 {
if is_prime_u64(n) {
assert_eq!(prev_prime(next_prime(n)), Some(n), "bracket at {n}");
}
}
assert_eq!(nth_prime(1), 2);
assert_eq!(nth_prime(6), 13);
assert_eq!(nth_prime(10_001), 104_743, "the classic 10001st prime");
for k in 1..500usize {
assert!(is_prime_u64(nth_prime(k)));
assert_eq!(prime_count_meissel(nth_prime(k)), k as u64, "pi(p_k) = k");
}
}
#[test]
fn test_factorization_reconstructs_its_input() {
let mut rng = Rng::new(17);
for _ in 0..2_000 {
let n = rng.next_u64() % 1_000_000_000_000 + 2;
let f = factorize(n);
let mut prod = 1u128;
for &(p, e) in &f {
assert!(is_prime_u64(p), "{p} is not prime in the factorization of {n}");
prod *= u128::from(p).pow(e);
}
assert_eq!(prod, u128::from(n), "factorization of {n} does not multiply back");
assert!(f.windows(2).all(|w| w[0].0 < w[1].0), "factors not ascending");
}
for n in [1_000_003u64 * 1_000_033, 2u64.pow(59), 3u64.pow(37),
999_999_000_001, 1_000_000_007, 4] {
let f = factorize(n);
let prod: u128 = f.iter().map(|&(p, e)| u128::from(p).pow(e)).product();
assert_eq!(prod, u128::from(n), "failed on {n}");
}
assert!(factorize(1).is_empty());
assert_eq!(factorize(2), [(2, 1)]);
assert_eq!(factorize(360), [(2, 3), (3, 2), (5, 1)]);
assert_eq!(pollard_rho(8_051).map(|d| 8_051 % d), Some(0));
assert!(pollard_rho(97).is_none(), "no factor of a prime");
assert_eq!(fermat_factor(5_959), Some((59, 101)));
let p = 1_000_037u64; let q = 1_000_039u64;
if let Some(d) = pollard_p_minus_1(p * q, 200_000) {
assert!(d == p || d == q, "p-1 returned a wrong factor {d}");
}
let (small, rest) = trial_division(2u64.pow(10) * 3 * 1_000_003, 100);
assert_eq!(small, [(2, 10), (3, 1)]);
assert_eq!(rest, 1_000_003);
}
#[test]
fn test_prime_counting() {
assert_eq!(prime_count_meissel(1_000_000_000), 50_847_534);
for (n, want) in [(0u64, 0u64), (1, 0), (2, 1), (10, 4), (100, 25), (1_000, 168),
(10_000, 1_229), (100_000, 9_592), (1_000_000, 78_498),
(10_000_000, 664_579), (100_000_000, 5_761_455)] {
assert_eq!(prime_count_meissel(n), want, "pi({n})");
}
let era = sieve_eratosthenes(5_000);
for n in 0..=5_000u64 {
let direct = era.iter().filter(|&&p| p as u64 <= n).count() as u64;
assert_eq!(prime_count_meissel(n), direct, "pi({n})");
}
let pi9 = 50_847_534.0;
let li_err = (prime_count_li_approx(1e9) - pi9).abs();
let r_err = (riemann_r(1e9) - pi9).abs();
assert!(li_err < 3_000.0, "li(1e9) off by {li_err}");
assert!(r_err < li_err / 5.0, "R should beat li: {r_err} vs {li_err}");
assert_eq!(prime_count_li_approx(1.0), 0.0);
}
#[test]
fn test_prime_patterns() {
assert_eq!(twin_primes(100),
[(3, 5), (5, 7), (11, 13), (17, 19), (29, 31), (41, 43), (59, 61), (71, 73)]);
let gaps = prime_gaps(100);
assert_eq!(gaps[0], 1, "2 to 3");
assert!(gaps[1..].iter().all(|&g| g % 2 == 0), "gaps above 3 are even");
assert_eq!(gaps.iter().sum::<u64>(), 97 - 2, "gaps telescope");
for n in (4..2_000u64).step_by(2) {
let parts = goldbach_partitions(n);
assert!(!parts.is_empty(), "no Goldbach partition for {n}");
for (p, q) in parts {
assert!(p <= q && p + q == n && is_prime_u64(p) && is_prime_u64(q));
}
}
assert!(goldbach_partitions(7).is_empty(), "odd input");
let ap = primes_in_arithmetic_progression(3, 4, 5);
assert_eq!(ap, [3, 7, 11, 19, 23]);
assert!(ap.iter().all(|&p| is_prime_u64(p) && p % 4 == 3));
let known = [2u32, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127];
for p in 2..=127u32 {
let want = known.contains(&p);
assert_eq!(mersenne_lucas_lehmer(p), want, "M_{p}");
}
for p in [2u32, 3, 5, 7, 13, 17, 19, 31] {
let m = 2u64.pow(p) - 1;
assert_eq!(mersenne_lucas_lehmer(p), is_prime_u64(m), "M_{p} = {m}");
}
for n in 2..300u64 {
assert_eq!(wilson_check(n), is_prime_u64(n), "Wilson at {n}");
}
}
#[test]
fn test_bigint_primality_and_factorization() {
let mut rng = Rng::new(29);
for n in 0..3_000u64 {
let b = BigInt::from_u64(n);
assert_eq!(is_prime_bigint(&b, 4, &mut rng), is_prime_u64(n), "BPSW at {n}");
}
for _ in 0..400 {
let n = rng.next_u64() % 10_000_000;
let b = BigInt::from_u64(n);
assert_eq!(is_prime_bigint(&b, 4, &mut rng), is_prime_u64(n), "BPSW at {n}");
}
let m127 = BigInt::one().shl(127).sub(&BigInt::one());
assert!(is_prime_bigint(&m127, 8, &mut rng), "2^127-1 is prime");
let m128 = BigInt::one().shl(128).sub(&BigInt::one());
assert!(!is_prime_bigint(&m128, 8, &mut rng), "2^128-1 is composite");
let sq = BigInt::from_u64(1_000_003).pow(2);
assert!(!is_prime_bigint(&sq, 8, &mut rng));
let lo = 1u64 << 62;
for k in 0..400u64 {
let n = lo + k;
let b = BigInt::from_u64(n);
assert_eq!(is_prime_bigint(&b, 2, &mut rng), is_prime_u64(n),
"BPSW disagrees at {n}");
}
for _ in 0..200 {
let n = lo | (rng.next_u64() >> 2);
let b = BigInt::from_u64(n);
assert_eq!(is_prime_bigint(&b, 2, &mut rng), is_prime_u64(n),
"BPSW disagrees at {n}");
}
for bits in [16usize, 32, 64, 96] {
let p = random_prime(bits, &mut rng);
assert_eq!(p.bits(), bits, "width of a {bits}-bit prime");
assert!(is_prime_bigint(&p, 12, &mut rng));
}
for n in [BigInt::from_u64(1_000_003).mul(&BigInt::from_u64(1_000_033)),
BigInt::from_u64(2).pow(20).mul(&BigInt::from_u64(3).pow(9)),
BigInt::from_u64(999_999_000_001)] {
let f = factorize_bigint(&n, &mut rng);
let mut prod = BigInt::one();
for (p, e) in &f {
assert!(is_prime_bigint(p, 8, &mut rng), "{p} is not prime");
prod = prod.mul(&p.pow(u64::from(*e)));
}
assert_eq!(prod, n, "factorization of {n} does not multiply back");
}
}
}
#[cfg(test)]
mod lucas_tests {
use super::*;
#[test]
fn test_strong_lucas_against_its_pseudoprimes() {
const LUCAS_PSEUDOPRIMES: [u64; 5] = [5459, 5777, 10877, 16109, 18971];
let mut found = Vec::new();
for n in (3..20_000u64).step_by(2) {
let got = strong_lucas_probable_prime(&BigInt::from_u64(n));
if is_prime_u64(n) {
assert!(got, "the strong Lucas test rejected the prime {n}");
} else if got {
found.push(n);
}
}
assert_eq!(found, LUCAS_PSEUDOPRIMES,
"the set of strong Lucas pseudoprimes is wrong");
const SPSP_BASE_2: [u64; 6] = [2047, 3277, 4033, 4681, 8321, 15841];
for n in SPSP_BASE_2 {
assert!(!is_prime_u64(n), "{n} should be composite");
let mut d = n - 1;
let mut r = 0u32;
while d % 2 == 0 {
d /= 2;
r += 1;
}
let mut x = mod_pow_u64(2, d, n);
let mut passes = x == 1 || x == n - 1;
for _ in 1..r {
x = mul_mod(x, x, n);
if x == n - 1 {
passes = true;
}
}
assert!(passes, "{n} is not a base-2 strong pseudoprime");
assert!(!strong_lucas_probable_prime(&BigInt::from_u64(n)),
"Lucas failed to reject the base-2 pseudoprime {n}");
}
for n in LUCAS_PSEUDOPRIMES {
assert!(!SPSP_BASE_2.contains(&n), "{n} would defeat BPSW");
}
}
}
#[cfg(test)]
mod review_regressions {
use super::*;
#[test]
fn first_multiple_never_rounds_past_the_top() {
let first_multiple = |lo: u64, p: u64| -> Option<u64> {
let rem = lo % p;
if rem == 0 { Some(lo) } else { lo.checked_add(p - rem) }
};
for lo in [0u64, 1, 2, 10, 1_000, 1_000_000, 1u64 << 40] {
for p in [2u64, 3, 5, 7, 11, 97, 65_537] {
assert_eq!(first_multiple(lo, p), Some(lo.div_ceil(p) * p));
}
}
for p in [7u64, 11, 13] {
assert!(
u64::MAX.checked_div(p).is_some() && first_multiple(u64::MAX, p).is_none(),
"p = {p} should have no multiple at or above u64::MAX"
);
}
assert_eq!(first_multiple(u64::MAX, 3), Some(u64::MAX));
assert_eq!(first_multiple(u64::MAX, 5), Some(u64::MAX));
for (lo, hi) in [(0u64, 200u64), (100, 300), (1_000, 1_100), (10_000, 10_500)] {
let seg = sieve_segmented(lo, hi);
let want: Vec<u64> = sieve_eratosthenes(hi as usize - 1)
.into_iter()
.map(|p| p as u64)
.filter(|&p| p >= lo)
.collect();
assert_eq!(seg, want, "window [{lo}, {hi})");
}
}
#[test]
fn factorize_reports_only_primes() {
assert_eq!(smallest_factor_by_trial(91), Some(7));
assert_eq!(smallest_factor_by_trial(4), Some(2));
assert_eq!(smallest_factor_by_trial(1_000_003 * 1_000_033), Some(1_000_003));
assert_eq!(smallest_factor_by_trial(97), None);
assert_eq!(smallest_factor_by_trial(2), None);
assert_eq!(smallest_factor_by_trial(1), None);
for n in [
2u64,
4,
2u64.pow(20),
3u64.pow(13),
91,
1_000_003 * 1_000_033,
999_999_000_001,
67_280_421_310_721,
(1u64 << 61) - 1,
] {
let f = factorize(n);
let product = f
.iter()
.fold(1u128, |a, &(p, e)| a * u128::from(p).pow(e));
assert_eq!(product, u128::from(n), "factorization of {n} does not multiply back");
for (p, _) in f {
assert!(is_prime_u64(p), "factorize({n}) reported composite {p}");
}
}
}
}