Skip to content

Commit

Permalink
Added Machine-Prime, simplified nearest_prime
Browse files Browse the repository at this point in the history
Added the low-memory variant of Machine-prime (using a modified BPSW test) as an optional dependency. The Criterion benchmark shows that it is approximately 16x faster than the existing version. 

Simplified the next and previous_prime functions
  • Loading branch information
JASory authored Nov 28, 2024
1 parent b27f313 commit 1b7f642
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 19 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ serde = { version = "1.0", default-features = false, features = ["derive"], opti
serde_arrays = { version = "0.1.0", optional = true }
zerocopy = { version = "0.8", default-features = false, features = ["derive"], optional = true }
rkyv = { version = "0.8", default-features = false, optional = true }
# no-std constant time primality testing, low memory variant
machine-prime = { version="1.3.*", features = ["small"], optional = true}

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
Expand All @@ -33,6 +35,10 @@ zerocopy = ["dep:zerocopy"]
# Derives the `Serialize`, `Deserialize`, and `Archive` traits from the [`rkyv`](https://crates.io/crates/rkyv) crate for the `Primes` struct.
rkyv = ["dep:rkyv"]

# Calls the Machine-Prime library for much faster primality testing
fastprime = ["dep:machine-prime"]


[package.metadata.docs.rs]
# Document all features.
all-features = true
Expand Down
13 changes: 12 additions & 1 deletion src/check.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! This module contains an implementation of a deterministic Miller-Rabin primality test
#[cfg(not(feature="fastprime"))]
use crate::integer_math::{mod_mul, mod_pow};

/// Returns whether `n` is prime.
Expand All @@ -17,6 +17,14 @@ use crate::integer_math::{mod_mul, mod_pow};
/// ```
#[must_use]
pub const fn is_prime(n: u64) -> bool {

#[cfg(feature="fastprime")]
{
machine_prime::is_prime(n)
}

#[cfg(not(feature="fastprime"))]
{
// Since we know the maximum size of the numbers we test against
// we can use the fact that there are known perfect bases
// in order to make the test both fast and deterministic.
Expand Down Expand Up @@ -75,9 +83,12 @@ pub const fn is_prime(n: u64) -> bool {
}

true

} // end conditional compilation block
}

/// Performs a Miller-Rabin test with the witness k.
#[cfg(not(feature="fastprime"))]
const fn miller_test(mut d: u64, n: u64, k: u64) -> bool {
let mut x = mod_pow(k, d, n);
if x == 1 || x == n - 1 {
Expand Down
2 changes: 2 additions & 0 deletions src/integer_math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub const fn isqrt(n: u64) -> u64 {

/// Calculates (`base` ^ `exp`) mod `modulo` without overflow.
#[must_use]
#[cfg(not(feature="fastprime"))]
pub const fn mod_pow(mut base: u64, mut exp: u64, modulo: u64) -> u64 {
let mut res = 1;

Expand All @@ -52,6 +53,7 @@ pub const fn mod_pow(mut base: u64, mut exp: u64, modulo: u64) -> u64 {

/// Calculates (`a` * `b`) mod `modulo` without overflow.
#[must_use]
#[cfg(not(feature="fastprime"))]
pub const fn mod_mul(a: u64, b: u64, modulo: u64) -> u64 {
((a as u128 * b as u128) % modulo as u128) as u64
}
Expand Down
47 changes: 29 additions & 18 deletions src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@
use crate::is_prime;


// Generalised function for nearest search by incrementing/decrementing by 1
// Any attempt at optimising this would be largely pointless since the largest prime gap under 2^64 is only 1550
// And is_prime's trial division already eliminates most of those
const fn bounded_search(mut n: u64, stride: u64) -> Option<u64>{

loop{

// Addition over Z/2^64, aka regular addition under optimisation flags
n= n.wrapping_add(stride);
// If either condition is met then we started either below or above the smallest or largest prime respectively
// Any two values from 2^64-58 to 1 would also work
if n == 0u64 || n == u64::MAX{
return None
}

if is_prime(n){
return Some(n)
}
}
}
/// Returns the largest prime smaller than `n` if there is one.
///
/// Scans for primes downwards from the input with [`is_prime`].
Expand All @@ -24,24 +45,10 @@ use crate::is_prime;
/// assert_eq!(NO_SUCH, None);
/// ```
#[must_use = "the function only returns a new value and does not modify its input"]
pub const fn previous_prime(mut n: u64) -> Option<u64> {
if n <= 2 {
None
} else if n == 3 {
Some(2)
} else {
n -= 1;
pub const fn previous_prime(n: u64) -> Option<u64> {

if n % 2 == 0 {
n -= 1;
}

while !is_prime(n) {
n -= 2;
}

Some(n)
}
// Adding by 2^64-1 over Z/2^64 is equivalent to subtracting by 1
bounded_search(n,u64::MAX)
}

/// Returns the smallest prime greater than `n` if there is one that
Expand All @@ -67,7 +74,10 @@ pub const fn previous_prime(mut n: u64) -> Option<u64> {
/// assert_eq!(NO_SUCH, None);
/// ```
#[must_use = "the function only returns a new value and does not modify its input"]
pub const fn next_prime(mut n: u64) -> Option<u64> {
pub const fn next_prime(n: u64) -> Option<u64> {

bounded_search(n,1)
/*
// The largest prime smaller than u64::MAX
if n >= 18_446_744_073_709_551_557 {
None
Expand All @@ -86,4 +96,5 @@ pub const fn next_prime(mut n: u64) -> Option<u64> {
Some(n)
}
*/
}

0 comments on commit 1b7f642

Please sign in to comment.