From 1b7f64280b70a0b4031b316550c523539a7a6f8b Mon Sep 17 00:00:00 2001 From: JASory Date: Thu, 28 Nov 2024 16:54:23 -0700 Subject: [PATCH] Added Machine-Prime, simplified nearest_prime 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 --- Cargo.toml | 6 ++++++ src/check.rs | 13 ++++++++++++- src/integer_math.rs | 2 ++ src/search.rs | 47 ++++++++++++++++++++++++++++----------------- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b13d5de..50a0419 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } @@ -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 diff --git a/src/check.rs b/src/check.rs index 60be5f7..e401417 100644 --- a/src/check.rs +++ b/src/check.rs @@ -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. @@ -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. @@ -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 { diff --git a/src/integer_math.rs b/src/integer_math.rs index 665f02a..00241b3 100644 --- a/src/integer_math.rs +++ b/src/integer_math.rs @@ -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; @@ -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 } diff --git a/src/search.rs b/src/search.rs index 68e0711..2fddd43 100644 --- a/src/search.rs +++ b/src/search.rs @@ -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{ + + 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`]. @@ -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 { - if n <= 2 { - None - } else if n == 3 { - Some(2) - } else { - n -= 1; +pub const fn previous_prime(n: u64) -> Option { - 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 @@ -67,7 +74,10 @@ pub const fn previous_prime(mut n: u64) -> Option { /// 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 { +pub const fn next_prime(n: u64) -> Option { + + bounded_search(n,1) +/* // The largest prime smaller than u64::MAX if n >= 18_446_744_073_709_551_557 { None @@ -86,4 +96,5 @@ pub const fn next_prime(mut n: u64) -> Option { Some(n) } + */ }