From 0b5d580b31b7e233971d693333fad5b9d9ed0363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 16:52:19 +0200 Subject: [PATCH 01/22] sieve_geq::(limit) --- src/error.rs | 40 -------------- src/generation.rs | 133 +++++++++++++++++++++++++++++++--------------- src/lib.rs | 24 ++++----- src/sieving.rs | 89 ++++++++++++++++++++----------- 4 files changed, 159 insertions(+), 127 deletions(-) delete mode 100644 src/error.rs diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index de0a554..0000000 --- a/src/error.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Contains the implementation of the error type that is returned by the segmented sieving and generation functions. - -use core::fmt; - -/// The error returned by [`primes_lt`](crate::primes_lt) and [`primes_geq`](crate::primes_geq) if the input -/// is invalid or does not work to produce the requested primes. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Error { - /// The limit was larger than `MEM^2`. - TooLargeLimit(u64, u64), - /// The limit was smaller than or equal to 2. - TooSmallLimit(u64), - /// Encountered a number larger than `MEM`^2. - SieveOverrun(u64), - /// Ran out of primes. - OutOfPrimes, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::TooLargeLimit(limit, mem_sqr) => write!( - f, - "the limit ({limit}) was larger than `MEM`^2 ({mem_sqr})" - ), - Self::TooSmallLimit(limit) => write!( - f, - "the limit was {limit}, which is smaller than or equal to 2" - ), - Self::SieveOverrun(number) => write!( - f, - "encountered the number {number} which would have needed `MEM` to be at least {} to sieve", crate::imath::isqrt(*number) + 1 - ), - Self::OutOfPrimes => write!(f, "ran out of primes before the array was filled"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} diff --git a/src/generation.rs b/src/generation.rs index 589ea92..c9b6011 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -1,4 +1,6 @@ -use crate::{error::Error, sieve, sieving::sieve_segment, Underlying}; +use core::fmt; + +use crate::{sieve, sieving::sieve_segment, Underlying}; /// Returns the `N` first prime numbers. /// Fails to compile if `N` is 0. @@ -121,7 +123,7 @@ pub const fn primes() -> [Underlying; N] { /// /// Basic usage: /// ``` -/// # use const_primes::{primes_lt, Error}; +/// # use const_primes::{primes_lt, GenerationError}; /// // Sieving up to 100 means the sieve needs to be of size ceil(sqrt(100)) = 10. /// // However, we only save the 4 largest primes in the constant. /// const PRIMES: [u64;4] = match primes_lt::<4, 10>(100) {Ok(ps) => ps, Err(_) => panic!()}; @@ -129,11 +131,11 @@ pub const fn primes() -> [Underlying; N] { /// ``` /// Compute larger primes: /// ``` -/// # use const_primes::{primes_lt, Error}; +/// # use const_primes::{primes_lt, GenerationError}; /// use const_primes::isqrt; /// const LIMIT: u64 = 5_000_000_030; /// # #[allow(long_running_const_eval)] -/// const BIG_PRIMES: Result<[u64; 3], Error> = primes_lt::<3, {isqrt(LIMIT) as usize + 1}>(LIMIT); +/// const BIG_PRIMES: Result<[u64; 3], GenerationError> = primes_lt::<3, {isqrt(LIMIT) as usize + 1}>(LIMIT); /// /// assert_eq!(BIG_PRIMES, Ok([4_999_999_903, 4_999_999_937, 5_000_000_029])); /// ``` @@ -143,24 +145,24 @@ pub const fn primes() -> [Underlying; N] { /// the number of primes that exists below the `upper_limit` we /// will get an error: /// ``` -/// # use const_primes::{primes_lt, Error}; +/// # use const_primes::{primes_lt, GenerationError}; /// const N: usize = 9; -/// const PRIMES: Result<[u64; N], Error> = primes_lt::(10); -/// assert_eq!(PRIMES, Err(Error::OutOfPrimes)); +/// const PRIMES: Result<[u64; N], GenerationError> = primes_lt::(10); +/// assert_eq!(PRIMES, Err(GenerationError::OutOfPrimes)); /// ``` /// /// We will also get an error if `upper_limit` is larger than `MEM`^2 or if `upper_limit` is smaller than or equal to 2. /// ``` -/// # use const_primes::{primes_lt, Error}; -/// const TOO_LARGE_LIMIT: Result<[u64; 3], Error> = primes_lt::<3, 5>(26); -/// const TOO_SMALL_LIMIT: Result<[u64 ;1], Error> = primes_lt::<1, 1>(1); -/// assert!(matches!(TOO_LARGE_LIMIT, Err(Error::TooLargeLimit(_, _)))); -/// assert!(matches!(TOO_SMALL_LIMIT, Err(Error::TooSmallLimit(_)))); +/// # use const_primes::{primes_lt, GenerationError}; +/// const TOO_LARGE_LIMIT: Result<[u64; 3], GenerationError> = primes_lt::<3, 5>(26); +/// const TOO_SMALL_LIMIT: Result<[u64 ;1], GenerationError> = primes_lt::<1, 1>(1); +/// assert!(matches!(TOO_LARGE_LIMIT, Err(GenerationError::TooLargeLimit(_, _)))); +/// assert!(matches!(TOO_SMALL_LIMIT, Err(GenerationError::TooSmallLimit(_)))); /// ``` #[must_use = "the function only returns a new value and does not modify its input"] pub const fn primes_lt( mut upper_limit: u64, -) -> Result<[u64; N], Error> { +) -> Result<[u64; N], GenerationError> { const { assert!(N > 0, "`N` must be at least 1"); assert!(MEM >= N, "`MEM` must be at least as large as `N`"); @@ -174,11 +176,11 @@ pub const fn primes_lt( }; if upper_limit <= 2 { - return Err(Error::TooSmallLimit(upper_limit)); + return Err(GenerationError::TooSmallLimit(upper_limit)); } if upper_limit > mem_sqr { - return Err(Error::TooLargeLimit(upper_limit, mem_sqr)); + return Err(GenerationError::TooLargeLimit(upper_limit, mem_sqr)); } let mut primes: [u64; N] = [0; N]; @@ -210,7 +212,7 @@ pub const fn primes_lt( } upper_limit = smallest_found_prime; if upper_limit <= 2 && total_primes_found < N { - return Err(Error::OutOfPrimes); + return Err(GenerationError::OutOfPrimes); } } @@ -226,10 +228,10 @@ pub const fn primes_lt( /// # Example /// /// ``` -/// # use const_primes::{primes_segment, Error}; +/// # use const_primes::{primes_segment, GenerationError}; /// const LIMIT: u64 = 5_000_000_031; -/// const PRIMES_GEQ: Result<[u64; 3], Error> = primes_segment!(3; >= LIMIT); -/// const PRIMES_LT: Result<[u64; 3], Error> = primes_segment!(3; < LIMIT); +/// const PRIMES_GEQ: Result<[u64; 3], GenerationError> = primes_segment!(3; >= LIMIT); +/// const PRIMES_LT: Result<[u64; 3], GenerationError> = primes_segment!(3; < LIMIT); /// // Can also be used at runtime: /// let primes_geq = primes_segment!(3; >= LIMIT); /// @@ -277,42 +279,42 @@ macro_rules! primes_segment { /// /// Basic usage: /// ``` -/// use const_primes::{primes_geq, Error}; +/// use const_primes::{primes_geq, GenerationError}; /// // Compute 5 primes larger than 40. The largest will be 59, so `MEM` needs to be at least 8. /// const PRIMES: [u64; 5] = match primes_geq::<5, 8>(40) {Ok(ps) => ps, Err(_) => panic!()}; /// assert_eq!(PRIMES, [41, 43, 47, 53, 59]); /// ``` /// Estimate the size of `MEM` using the square root of the limit (and some extra, proportional to `N`): /// ``` -/// # use const_primes::{primes_geq, Error}; +/// # use const_primes::{primes_geq, GenerationError}; /// use const_primes::isqrt; /// const N: usize = 3; /// const LIMIT: u64 = 5_000_000_030; /// # #[allow(long_running_const_eval)] -/// const PRIMES_GEQ: Result<[u64; N], Error> = primes_geq::(LIMIT); +/// const PRIMES_GEQ: Result<[u64; N], GenerationError> = primes_geq::(LIMIT); /// assert_eq!(PRIMES_GEQ, Ok([5_000_000_039, 5_000_000_059, 5_000_000_063])); -/// # Ok::<(), Error>(()) +/// # Ok::<(), GenerationError>(()) /// ``` /// # Errors /// /// Only primes smaller than `MEM^2` can be generated, so if the sieve /// encounters a number larger than that it results in an error: /// ``` -/// # use const_primes::{primes_geq, Error}; -/// const PRIMES: Result<[u64; 3], Error> = primes_geq::<3, 3>(5); -/// assert!(matches!(PRIMES, Err(Error::SieveOverrun(_)))); +/// # use const_primes::{primes_geq, GenerationError}; +/// const PRIMES: Result<[u64; 3], GenerationError> = primes_geq::<3, 3>(5); +/// assert!(matches!(PRIMES, Err(GenerationError::SieveOverrun(_)))); /// ``` /// /// Also returns an error if `lower_limit` is larger than or equal to `MEM^2`: /// ``` -/// # use const_primes::{primes_geq, Error}; -/// const PRIMES: Result<[u64; 5], Error> = primes_geq::<5, 5>(26); -/// assert!(matches!(PRIMES, Err(Error::TooLargeLimit(_, _)))); +/// # use const_primes::{primes_geq, GenerationError}; +/// const PRIMES: Result<[u64; 5], GenerationError> = primes_geq::<5, 5>(26); +/// assert!(matches!(PRIMES, Err(GenerationError::TooLargeLimit(_, _)))); /// ``` #[must_use = "the function only returns a new value and does not modify its input"] pub const fn primes_geq( lower_limit: u64, -) -> Result<[u64; N], Error> { +) -> Result<[u64; N], GenerationError> { const { assert!(N > 0, "`N` must be at least 1"); assert!(MEM >= N, "`MEM` must be at least as large as `N`"); @@ -325,14 +327,22 @@ pub const fn primes_geq( (mem64, mem_sqr) }; - // There are no primes smaller than 2, so we will always start looking at 2. - let new_lower_limit = if lower_limit >= 2 { lower_limit } else { 2 }; - - if new_lower_limit >= mem_sqr { - return Err(Error::TooLargeLimit(lower_limit, mem_sqr)); + // If `lower_limit` is 2 or less, this is the same as calling `primes`, + // so we just do that and convert the result to `u64`. + if lower_limit <= 2 { + let ans32: [u32; N] = primes(); + let mut ans64 = [0; N]; + let mut i = 0; + while i < N { + ans64[i] = ans32[i] as u64; + i += 1; + } + return Ok(ans64); } - let lower_limit = new_lower_limit; + if lower_limit >= mem_sqr { + return Err(GenerationError::TooLargeLimit(lower_limit, mem_sqr)); + } let mut primes = [0; N]; let mut total_found_primes = 0; @@ -351,7 +361,7 @@ pub const fn primes_geq( // the base sieve contains no information // about numbers larger than or equal to `MEM`^2. if largest_found_prime >= mem_sqr { - return Err(Error::SieveOverrun(largest_found_prime)); + return Err(GenerationError::SieveOverrun(largest_found_prime)); } if largest_found_prime >= lower_limit { @@ -371,6 +381,43 @@ pub const fn primes_geq( Ok(primes) } +/// The error returned by [`primes_lt`](crate::primes_lt) and [`primes_geq`](crate::primes_geq) if the input +/// is invalid or does not work to produce the requested primes. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum GenerationError { + /// The limit was larger than `MEM^2`. + TooLargeLimit(u64, u64), + /// The limit was smaller than or equal to 2. + TooSmallLimit(u64), + /// Encountered a number larger than `MEM`^2. + SieveOverrun(u64), + /// Ran out of primes. + OutOfPrimes, +} + +impl fmt::Display for GenerationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::TooLargeLimit(limit, mem_sqr) => write!( + f, + "the limit ({limit}) was larger than `MEM`^2 ({mem_sqr})" + ), + Self::TooSmallLimit(limit) => write!( + f, + "the limit was {limit}, which is smaller than or equal to 2" + ), + Self::SieveOverrun(number) => write!( + f, + "encountered the number {number} which would have needed `MEM` to be at least {} to sieve", crate::imath::isqrt(*number) + 1 + ), + Self::OutOfPrimes => write!(f, "ran out of primes before the array was filled"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for GenerationError {} + #[cfg(test)] mod test { use crate::is_prime; @@ -380,16 +427,16 @@ mod test { #[test] fn sanity_check_primes_geq() { { - const P: Result<[u64; 5], Error> = primes_geq::<5, 5>(10); + const P: Result<[u64; 5], GenerationError> = primes_geq::<5, 5>(10); assert_eq!(P, Ok([11, 13, 17, 19, 23])); } { - const P: Result<[u64; 5], Error> = primes_geq::<5, 5>(0); + const P: Result<[u64; 5], GenerationError> = primes_geq::<5, 5>(0); assert_eq!(P, Ok([2, 3, 5, 7, 11])); } { - const P: Result<[u64; 1], Error> = primes_geq::<1, 1>(0); - assert_eq!(P, Err(Error::TooLargeLimit(0, 1))); + const P: Result<[u64; 1], GenerationError> = primes_geq::<1, 1>(0); + assert_eq!(P, Ok([2])); } for &prime in primes_geq::<2_000, 2_008>(3_998_000).unwrap().as_slice() { assert!(is_prime(prime)); @@ -398,8 +445,8 @@ mod test { #[test] fn check_primes_segment() { - const P_GEQ: Result<[u64; 10], Error> = primes_segment!(10; >= 1000); - const P_LT: Result<[u64; 10], Error> = primes_segment!(10; < 1000); + const P_GEQ: Result<[u64; 10], GenerationError> = primes_segment!(10; >= 1000); + const P_LT: Result<[u64; 10], GenerationError> = primes_segment!(10; < 1000); assert_eq!( P_GEQ, diff --git a/src/lib.rs b/src/lib.rs index 8e54045..c9f91d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,19 +53,19 @@ //! (which must be at least the ceiling of the square root of the largest encountered number). //! This means that one can sieve to large numbers, but doesn't need to store the entire sieve in the binary. //! ``` -//! use const_primes::{primes_lt, Error, isqrt}; +//! use const_primes::{primes_lt, GenerationError, isqrt}; //! const LIMIT: u64 = 5_000_000_031; //! const N: usize = 3; -//! const PRIMES_LT: Result<[u64; N], Error> = primes_lt::(LIMIT); +//! const PRIMES_LT: Result<[u64; N], GenerationError> = primes_lt::(LIMIT); //! //! assert_eq!(PRIMES_LT, Ok([4_999_999_903, 4_999_999_937, 5_000_000_029])); //! ``` //! If you do not wish to compute the required sieve size yourself, //! you can use the provided macro [`primes_segment!`]: //! ``` -//! # use const_primes::{primes_segment, Error}; -//! const PRIMES_OVER_100: Result<[u64; 3], Error> = primes_segment!(3; >= 100); -//! const PRIMES_UNDER_100: Result<[u64; 3], Error> = primes_segment!(3; < 100); +//! # use const_primes::{primes_segment, GenerationError}; +//! const PRIMES_OVER_100: Result<[u64; 3], GenerationError> = primes_segment!(3; >= 100); +//! const PRIMES_UNDER_100: Result<[u64; 3], GenerationError> = primes_segment!(3; < 100); //! //! assert_eq!(PRIMES_OVER_100, Ok([101, 103, 107])); //! assert_eq!(PRIMES_UNDER_100, Ok([83, 89, 97])); @@ -116,7 +116,6 @@ // This is used since there is currently no way to be generic over types that can do arithmetic at compile time. type Underlying = u32; -mod error; mod generation; mod imath; mod miller_rabin; @@ -124,12 +123,11 @@ mod other_prime; mod sieving; mod wrapper; -pub use error::Error; -pub use generation::{primes, primes_geq, primes_lt}; +pub use generation::{primes, primes_geq, primes_lt, GenerationError}; pub use imath::isqrt; pub use miller_rabin::is_prime; pub use other_prime::{next_prime, previous_prime}; -pub use sieving::{sieve, sieve_geq, sieve_lt}; +pub use sieving::{sieve, sieve_geq, sieve_lt, SieveError}; pub use wrapper::Primes; /// Returns an array of size `N` where the value at a given index is how many primes are less than or equal to the index. @@ -255,7 +253,7 @@ mod test { ($($n:expr),+) => { $( { - const P: Result<[u64; $n], Error> = primes_lt::<$n, $n>(100); + const P: Result<[u64; $n], GenerationError> = primes_lt::<$n, $n>(100); for (i, prime) in P.unwrap().as_slice().into_iter().enumerate() { assert_eq!(PRECOMPUTED_PRIMES[25-$n..25][i], *prime as u32); } @@ -303,9 +301,9 @@ mod test { ($($n:expr),+) => { $( { - const P: [bool; $n] = sieve_geq(10); - assert_eq!(&PRIMALITIES[10..10+$n], P); - assert_eq!(&PRIMALITIES[10..10+$n], sieve_geq::<$n>(10)); + const P: Result<[bool; $n], SieveError> = sieve_geq::<$n, $n>(10); + assert_eq!(&PRIMALITIES[10..10+$n], P.unwrap()); + assert_eq!(&PRIMALITIES[10..10+$n], sieve_geq::<$n, $n>(10).unwrap()); } )+ }; diff --git a/src/sieving.rs b/src/sieving.rs index aa0e861..23d1a51 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -175,6 +175,24 @@ pub const fn sieve() -> [bool; N] { sieve } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SieveError { + TooLargeTotal, + TotalDoesntFitU64, +} + +impl core::fmt::Display for SieveError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::TooLargeTotal => write!(f, "`MEM + limit` must be less than or equal to `MEM`^2"), + Self::TotalDoesntFitU64 => write!(f, "`MEM + limit` must fit in a `u64`"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SieveError {} + /// Returns the prime status of the `N` smallest integers greater than or equal to `lower_limit`. /// Fails to compile if `N` is 0. /// @@ -182,49 +200,58 @@ pub const fn sieve() -> [bool; N] { /// /// Basic usage: /// ``` -/// # use const_primes::sieve_geq; -/// const PRIME_STATUS: [bool; 5] = sieve_geq(10); -/// // 10 11 12 13 14 -/// assert_eq!(PRIME_STATUS, [false, true, false, true, false]); +/// # use const_primes::{sieve_geq, SieveError}; +/// const PRIME_STATUS: Result<[bool; 5], SieveError> = sieve_geq::<5, 5>(10); +/// // 10 11 12 13 14 +/// assert_eq!(PRIME_STATUS, Ok([false, true, false, true, false])); /// ``` -/// # Panics +/// # Errors /// -/// Panics if `N + lower_limit` is larger than to `N^2`. In const contexts this is a compile error: -/// ```compile_fail -/// # use const_primes::sieve_geq; -/// const P: [bool; 5] = sieve_geq(21); +/// Returns an error if `MEM + lower_limit` is larger than `MEM^2`. +/// ``` +/// # use const_primes::{sieve_geq, SieveError}; +/// const P: Result<[bool; 5], SieveError> = sieve_geq::<5, 5>(21); +/// assert_eq!(P, Err(SieveError::TooLargeTotal)); /// ``` #[must_use = "the function only returns a new value and does not modify its input"] -pub const fn sieve_geq(lower_limit: u64) -> [bool; N] { +pub const fn sieve_geq( + lower_limit: u64, +) -> Result<[bool; N], SieveError> { const { assert!(N > 0, "`N` must be at least 1") } + let (mem64, mem_sqr) = const { + let mem64 = MEM as u64; + match mem64.checked_mul(mem64) { + Some(prod) => (mem64, prod), + None => panic!("`MEM`^2 must fit in a `u64`"), + } + }; - let n64 = N as u64; - - // Since panics are compile time errors in const contexts - // we check all the preconditions here and panic early. - let upper_limit = if let Some(sum) = n64.checked_add(lower_limit) { - sum - } else { - panic!("`N + lower_limit` must fit in a `u64`") + let Some(upper_limit) = mem64.checked_add(lower_limit) else { + return Err(SieveError::TotalDoesntFitU64); }; - if let Some(n_sqr) = n64.checked_mul(n64) { - assert!( - upper_limit <= n_sqr, - "`lower_limit + N` must be less than or equal to `N^2`" - ); - } else { - panic!("`N^2` must fit in a `u64`") - } - let base_sieve: [bool; N] = sieve(); + if upper_limit > mem_sqr { + return Err(SieveError::TooLargeTotal); + } - // If `lower_limit` is zero the upper range is the same as what we already sieved, - // so we return early. + // If `lower_limit` is zero then this is the same as just calling `sieve`, and we can return early. if lower_limit == 0 { - return base_sieve; + return Ok(sieve()); } - sieve_segment(&base_sieve, upper_limit) + let base_sieve: [bool; MEM] = sieve(); + + let upper_sieve = sieve_segment(&base_sieve, upper_limit); + + let mut ans = [false; N]; + let mut i = MEM; + let mut j = N; + while i > 1 { + ans[i - 1] = upper_sieve[j - 1]; + i -= 1; + j -= 1; + } + Ok(ans) } #[cfg(test)] From a7340cd1488dfaa1d8fd0813222d9f1d0d0d9325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 16:53:37 +0200 Subject: [PATCH 02/22] Remove redundant doclink --- src/generation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generation.rs b/src/generation.rs index c9b6011..58fbeed 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -381,7 +381,7 @@ pub const fn primes_geq( Ok(primes) } -/// The error returned by [`primes_lt`](crate::primes_lt) and [`primes_geq`](crate::primes_geq) if the input +/// The error returned by [`primes_lt`] and [`primes_geq`] if the input /// is invalid or does not work to produce the requested primes. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum GenerationError { From e63d9a4efa9eff68fdffa646b9501c7a037e1bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 16:54:39 +0200 Subject: [PATCH 03/22] Add docstring to SieveError --- src/sieving.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sieving.rs b/src/sieving.rs index 23d1a51..c00998a 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -175,6 +175,8 @@ pub const fn sieve() -> [bool; N] { sieve } +/// The error returned by [`sieve_lt`] and [`sieve_geq`] if the input +/// is invalid. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SieveError { TooLargeTotal, From b646bbdb6aae0a1f06d4260a586984900d23cde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 16:59:37 +0200 Subject: [PATCH 04/22] Clearer comments and document MEM >= N --- src/sieving.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sieving.rs b/src/sieving.rs index c00998a..d433751 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -196,7 +196,7 @@ impl core::fmt::Display for SieveError { impl std::error::Error for SieveError {} /// Returns the prime status of the `N` smallest integers greater than or equal to `lower_limit`. -/// Fails to compile if `N` is 0. +/// Fails to compile if `N` is 0, or if `MEM` is smaller than `N`. /// /// # Example /// @@ -219,7 +219,10 @@ impl std::error::Error for SieveError {} pub const fn sieve_geq( lower_limit: u64, ) -> Result<[bool; N], SieveError> { - const { assert!(N > 0, "`N` must be at least 1") } + const { + assert!(N > 0, "`N` must be at least 1"); + assert!(MEM >= N, "`MEM` must be at least as large as `N`"); + } let (mem64, mem_sqr) = const { let mem64 = MEM as u64; match mem64.checked_mul(mem64) { @@ -238,6 +241,8 @@ pub const fn sieve_geq( // If `lower_limit` is zero then this is the same as just calling `sieve`, and we can return early. if lower_limit == 0 { + // We do not merge it with the computation of `base_sieve` below, since here we only + // compute `N` values instead of `MEM`. return Ok(sieve()); } From b3ef1a4e8d73262d23a9f1bcb3aaf0b8b1a63593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 17:31:52 +0200 Subject: [PATCH 05/22] sieve_lt and sieve_geq have no currently known bugs --- README.md | 9 ++-- src/lib.rs | 15 ++++--- src/sieving.rs | 109 +++++++++++++++++++++++++++++-------------------- 3 files changed, 76 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 918fa83..a716b1d 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,13 @@ to store in the binary and the size of the sieve used during evaluation. The sie of the square root of the largest encountered value: ```rust // ceil(sqrt(5_000_000_063)) = 70_711 -const PRIMES_GEQ: Result<[u64; 3], const_primes::Error> = primes_geq::<3, 70_711>(5_000_000_031); +const PRIMES_GEQ: Result<[u64; 3], GenerationError> = primes_geq::<3, 70_711>(5_000_000_031); assert_eq!(PRIMES_GEQ, Ok([5_000_000_039, 5_000_000_059, 5_000_000_063])); ``` ```rust -const N: usize = 70711; -const PRIME_STATUS_LT: [bool; N] = sieve_lt(5_000_000_031); -// 5_000_000_028 5_000_000_029 5_000_000_030 -assert_eq!(PRIME_STATUS_LT[N - 3..], [false, true, false]); +const PRIME_STATUS_LT: Result<[bool; N], SieveError> = sieve_lt::<3, 70_711>(5_000_000_031); +// 5_000_000_028 5_000_000_029 5_000_000_030 +assert_eq!(PRIME_STATUS_LT, Ok([false, true, false])); ``` The sieving functions have yet to be modified for two generics, and must save the entire sieve in the binary. ## Other functionality diff --git a/src/lib.rs b/src/lib.rs index c9f91d1..22b37c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,11 +73,10 @@ //! it may, however, overestimate the required sieve size. //! //! ``` -//! # use const_primes::sieve_lt; -//! const N: usize = 70711; -//! const PRIME_STATUS_LT: [bool; N] = sieve_lt(5_000_000_031); -//! // 5_000_000_028 5_000_000_029 5_000_000_030 -//! assert_eq!(PRIME_STATUS_LT[N - 3..], [false, true, false]); +//! # use const_primes::{sieve_lt, SieveError}; +//! const PRIME_STATUS_LT: Result<[bool; 3], SieveError> = sieve_lt::<3, 70_711>(5_000_000_031); +//! // 5_000_000_028 5_000_000_029 5_000_000_030 +//! assert_eq!(PRIME_STATUS_LT, Ok([false, true, false])); //! ``` //! Unfortunately the output array must be large enough to contain the prime sieve, which scales with //! the square root of largest relavant number, which is why the examples use a size of over 70000 even though @@ -279,9 +278,9 @@ mod test { ($($n:expr),+) => { $( { - const P: [bool; $n] = sieve_lt(100); - assert_eq!(&PRIMALITIES[100-$n..], P); - assert_eq!(&PRIMALITIES[100-$n..], sieve_lt::<$n>(100)); + const P: Result<[bool; $n], SieveError> = sieve_lt::<$n, $n>(100); + assert_eq!(&PRIMALITIES[100-$n..], P.unwrap()); + assert_eq!(&PRIMALITIES[100-$n..], sieve_lt::<$n, $n>(100).unwrap()); } )+ }; diff --git a/src/sieving.rs b/src/sieving.rs index d433751..089043a 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -49,79 +49,98 @@ pub(crate) const fn sieve_segment( segment_sieve } -/// Returns an array of size `N` that indicates which of the integers in `[upper_limit - N, upper_limit)` are prime, -/// or in other words: the value at a given index represents whether `index + upper_limit - N` is prime. +/// Returns an array of size `N` that indicates which of the `N` integers in smaller than `upper_limit` are prime. /// /// If you just want the prime status of the first `N` integers, see [`sieve`]. /// /// Uses a sieve of Eratosthenes to sieve the first `N` integers /// and then uses the result to sieve the output range if needed. /// -/// Fails to compile if `N` is 0. +/// Fails to compile if `N` is 0, or if `MEM` is smaller than `N`. /// /// # Examples /// /// Basic usage /// ``` -/// # use const_primes::sieve_lt; -/// const PRIME_STATUSES: [bool; 10] = sieve_lt(30); +/// # use const_primes::{sieve_lt, SieveError}; +/// const PRIME_STATUSES: Result<[bool; 5], SieveError> = sieve_lt::<5, 6>(30); /// /// assert_eq!( /// PRIME_STATUSES, -/// // 20 21 22 23 24 25 26 27 28 29 -/// [false, false, false, true, false, false, false, false, false, true], +/// // 25 26 27 28 29 +/// Ok([false, false, false, false, true]), /// ); /// ``` /// Sieve limited ranges of very large values /// ``` -/// # use const_primes::sieve_lt; -/// const BIG_NUMBER: u64 = 5_000_000_031; -/// const CEIL_SQRT_BIG_NUMBER: usize = 70711; -/// const BIG_PRIME_STATUSES: [bool; CEIL_SQRT_BIG_NUMBER] = sieve_lt(BIG_NUMBER); +/// # use const_primes::{sieve_lt, SieveError}; +/// const BIG_PRIME_STATUSES: Result<[bool; 3], SieveError> = sieve_lt::<3, 70_711>(5_000_000_031); /// assert_eq!( -/// BIG_PRIME_STATUSES[CEIL_SQRT_BIG_NUMBER - 3..], -/// // 5_000_000_028 5_000_000_029 5_000_000_030 -/// [false, true, false], +/// BIG_PRIME_STATUSES, +/// // 5_000_000_028 5_000_000_029 5_000_000_030 +/// Ok([false, true, false]), /// ); /// ``` /// -/// # Panics +/// # Errors /// -/// Panics if `upper_limit` is not in the range `[N, N^2]`. In const contexts these are compile errors: -/// ```compile_fail -/// # use const_primes::sieve_lt; -/// const PRIME_STATUSES: [bool; 5] = sieve_lt(26); +/// Returns an error if `upper_limit` is larger than `MEM`^2: +/// ``` +/// # use const_primes::{sieve_lt, SieveError}; +/// const PS: Result<[bool; 5], SieveError> = sieve_lt::<5, 5>(26); +/// assert_eq!(PS, Err(SieveError::TooLargeTotal)); +/// ``` +/// or smaller than `N`: /// ``` -/// ```compile_fail -/// # use const_primes::sieve_lt; -/// const PRIME_STATUSES: [bool; 5] = sieve_lt(4); +/// # use const_primes::{sieve_lt, SieveError}; +/// const PS: Result<[bool; 5], SieveError> = sieve_lt::<5, 5>(4); +/// assert_eq!(PS, Err(SieveError::TooSmallLimit)); /// ``` #[must_use = "the function only returns a new value and does not modify its input"] -pub const fn sieve_lt(upper_limit: u64) -> [bool; N] { - const { assert!(N > 0, "`N` must be at least 1") } +pub const fn sieve_lt( + upper_limit: u64, +) -> Result<[bool; N], SieveError> { + const { + assert!(N > 0, "`N` must be at least 1"); + assert!(MEM >= N, "`MEM` must be at least as large as `N`"); + } - let n64 = N as u64; + let mem_sqr = const { + let mem64 = MEM as u64; + match mem64.checked_mul(mem64) { + Some(prod) => prod, + None => panic!("`MEM`^2 must fit in a `u64`"), + } + }; - // Since panics are compile time errors in const contexts - // we check all the preconditions here and panic early. - match n64.checked_mul(n64) { - Some(prod) => assert!( - upper_limit <= prod, - "`upper_limit` must be smaller than or equal to `N^2`" - ), - None => panic!("`N^2` must fit in a `u64`"), + if upper_limit > mem_sqr { + return Err(SieveError::TooLargeTotal); } - assert!(upper_limit >= n64, "`upper_limit` must be at least `N`"); - // Use a normal sieve of Eratosthenes for the first N numbers. - let base_sieve: [bool; N] = sieve(); + let n64 = N as u64; + + if upper_limit < n64 { + return Err(SieveError::TooSmallLimit); + } if upper_limit == n64 { // If we are not interested in sieving a larger range we can just return early. - return base_sieve; + return Ok(sieve()); } - sieve_segment(&base_sieve, upper_limit) + // Use a normal sieve of Eratosthenes for the first N numbers. + let base_sieve: [bool; MEM] = sieve(); + + // Use the result to sieve the higher range. + let upper_sieve = sieve_segment(&base_sieve, upper_limit); + + let mut ans = [false; N]; + let mut i = 0; + while i < N { + ans[N - 1 - i] = upper_sieve[MEM - 1 - i]; + i += 1; + } + Ok(ans) } /// Returns an array of size `N` where the value at a given index indicates whether the index is prime. @@ -179,6 +198,8 @@ pub const fn sieve() -> [bool; N] { /// is invalid. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SieveError { + TooSmallLimit, + TooLargeLimit, TooLargeTotal, TotalDoesntFitU64, } @@ -186,6 +207,8 @@ pub enum SieveError { impl core::fmt::Display for SieveError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { + Self::TooSmallLimit => write!(f, "`limit` must be at least `N`"), + Self::TooLargeLimit => write!(f, "`limit` must be less than or equal to `MEM`^2"), Self::TooLargeTotal => write!(f, "`MEM + limit` must be less than or equal to `MEM`^2"), Self::TotalDoesntFitU64 => write!(f, "`MEM + limit` must fit in a `u64`"), } @@ -251,12 +274,10 @@ pub const fn sieve_geq( let upper_sieve = sieve_segment(&base_sieve, upper_limit); let mut ans = [false; N]; - let mut i = MEM; - let mut j = N; - while i > 1 { - ans[i - 1] = upper_sieve[j - 1]; - i -= 1; - j -= 1; + let mut i = 0; + while i < N { + ans[i] = upper_sieve[i]; + i += 1; } Ok(ans) } From 2a119dc317ad94551b4dc2ea9c8550199e2ec13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 17:34:42 +0200 Subject: [PATCH 06/22] fix bugs in benchmarks --- benches/prime_benches.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benches/prime_benches.rs b/benches/prime_benches.rs index 3794bb1..06cf310 100644 --- a/benches/prime_benches.rs +++ b/benches/prime_benches.rs @@ -43,10 +43,10 @@ fn benchmarks(c: &mut Criterion) { b.iter(|| black_box(sieve::())) }); sieving.bench_function(format!("{N} integers < 100000000"), |b| { - b.iter(|| black_box(sieve_lt::(100000000))) + b.iter(|| black_box(sieve_lt::(100000000))) }); sieving.bench_function(format!("{N} integers >= 99990000"), |b| { - b.iter(|| black_box(sieve_geq::(99990000))) + b.iter(|| black_box(sieve_geq::(99990000))) }); } } From 6f212f437a1d4ca2feb46e4478e5ef77d56cdc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 17:44:11 +0200 Subject: [PATCH 07/22] Fix potential bugs in primes_segment! --- README.md | 2 +- src/generation.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a716b1d..1c30231 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ const PRIME_STATUS_LT: Result<[bool; N], SieveError> = sieve_lt::<3, 70_711>(5_0 // 5_000_000_028 5_000_000_029 5_000_000_030 assert_eq!(PRIME_STATUS_LT, Ok([false, true, false])); ``` -The sieving functions have yet to be modified for two generics, and must save the entire sieve in the binary. +The sieve size can be computed by the crate by using the macro `primes_segment!` and `sieve_segment!`. ## Other functionality Use `is_prime` to test whether a given number is prime: ```rust diff --git a/src/generation.rs b/src/generation.rs index 58fbeed..21bfe74 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -243,21 +243,21 @@ pub const fn primes_lt( macro_rules! primes_segment { ($n:expr; < $lim:expr) => { $crate::primes_lt::< - $n, + { $n }, { let mem = { $lim }; $crate::isqrt(mem) as ::core::primitive::usize + 1 }, - >($lim) + >({ $lim }) }; ($n:expr; >= $lim:expr) => { $crate::primes_geq::< - $n, + { $n }, { let mem = { $lim }; $crate::isqrt(mem) as ::core::primitive::usize + 1 + { $n } }, - >($lim) + >({ $lim }) }; } From c81e12949a33969e54729ce3c3130b78c61f90c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 19:21:09 +0200 Subject: [PATCH 08/22] Formatting --- src/sieving.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/sieving.rs b/src/sieving.rs index 089043a..2efb438 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -53,10 +53,7 @@ pub(crate) const fn sieve_segment( /// /// If you just want the prime status of the first `N` integers, see [`sieve`]. /// -/// Uses a sieve of Eratosthenes to sieve the first `N` integers -/// and then uses the result to sieve the output range if needed. -/// -/// Fails to compile if `N` is 0, or if `MEM` is smaller than `N`. +/// Fails to compile if `N` is 0, or if `MEM` is smaller than `N`. /// /// # Examples /// @@ -146,8 +143,6 @@ pub const fn sieve_lt( /// Returns an array of size `N` where the value at a given index indicates whether the index is prime. /// Fails to compile if `N` is 0. /// -/// Uses a sieve of Eratosthenes. -/// /// # Example /// /// ``` From 4fddc3818146d2bfced821c93a323894bccfd6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 19:43:17 +0200 Subject: [PATCH 09/22] Improve docstring examples for sieve_geq --- src/sieving.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/sieving.rs b/src/sieving.rs index 2efb438..99e4c19 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -51,7 +51,8 @@ pub(crate) const fn sieve_segment( /// Returns an array of size `N` that indicates which of the `N` integers in smaller than `upper_limit` are prime. /// -/// If you just want the prime status of the first `N` integers, see [`sieve`]. +/// If you just want the prime status of the first `N` integers, see [`sieve`], and if you want the prime status of +/// the integers above some number, see [`sieve_geq`]. /// /// Fails to compile if `N` is 0, or if `MEM` is smaller than `N`. /// @@ -214,17 +215,37 @@ impl core::fmt::Display for SieveError { impl std::error::Error for SieveError {} /// Returns the prime status of the `N` smallest integers greater than or equal to `lower_limit`. +/// /// Fails to compile if `N` is 0, or if `MEM` is smaller than `N`. /// +/// If you just want the prime status of the first N integers, see [`sieve`], and if you want the +/// prime status of the integers below some number, see [`sieve_lt`]. +/// /// # Example /// -/// Basic usage: +/// The size of the sieve, `MEM`, must be large enough for the largest sieved number to be smaller than `MEM`^2. +/// ``` +/// # use const_primes::sieve_geq; +/// // The three numbers larger than or equal to 9 are 9, 10 and 11. +/// const N: usize = 3; +/// const LIMIT: u64 = 9; +/// // We thus need a memory size of at least 4, since 3*3 < 11, which isn't enough. +/// const MEM: usize = 4; +/// const PRIME_STATUS: [bool; N] = match sieve_geq::(LIMIT) {Ok(s) => s, Err(_) => panic!()}; +/// // 9, 10, 11 +/// assert_eq!(PRIME_STATUS, [false, false, true]); +/// ``` +/// Enough memory can also be ensured with the help of functions provided by this crate: /// ``` /// # use const_primes::{sieve_geq, SieveError}; -/// const PRIME_STATUS: Result<[bool; 5], SieveError> = sieve_geq::<5, 5>(10); -/// // 10 11 12 13 14 -/// assert_eq!(PRIME_STATUS, Ok([false, true, false, true, false])); +/// use const_primes::isqrt; +/// const N: usize = 3; +/// const LIMIT: u64 = 5_000_000_038; +/// const PRIME_STATUS: Result<[bool; N], SieveError> = sieve_geq::(LIMIT); +/// // 5_000_000_038 5_000_000_039 5_000_000_040 +/// assert_eq!(PRIME_STATUS, Ok([false, true, false])); /// ``` +/// /// # Errors /// /// Returns an error if `MEM + lower_limit` is larger than `MEM^2`. From c01181d3d9d4ba6548a84959ea7e1d413753255d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 19:58:03 +0200 Subject: [PATCH 10/22] Clarify usage of size_lt and sieve_geq --- src/sieving.rs | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/sieving.rs b/src/sieving.rs index 99e4c19..b727628 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -51,6 +51,10 @@ pub(crate) const fn sieve_segment( /// Returns an array of size `N` that indicates which of the `N` integers in smaller than `upper_limit` are prime. /// +/// Uses a sieve of size `MEM` during evaluation, but stores only the requested values in the return array. +/// `MEM` must be large enough for the sieve to be able to determine the prime status of all numbers in the requested range, +/// that is: `MEM`^2 must be at least as large as `upper_limit`. +/// /// If you just want the prime status of the first `N` integers, see [`sieve`], and if you want the prime status of /// the integers above some number, see [`sieve_geq`]. /// @@ -60,19 +64,28 @@ pub(crate) const fn sieve_segment( /// /// Basic usage /// ``` -/// # use const_primes::{sieve_lt, SieveError}; -/// const PRIME_STATUSES: Result<[bool; 5], SieveError> = sieve_lt::<5, 6>(30); +/// # use const_primes::sieve_lt; +/// // The five largest numbers smaller than 30 are 25, 26, 27, 28 and 29. +/// const N: usize = 5; +/// const LIMIT: u64 = 30; +/// // We thus need a memory size of at least 6, since 5*5 < 29, and therefore isn't enough. +/// const MEM: usize = 6; +/// const PRIME_STATUSES: [bool; N] = match sieve_lt::(LIMIT) {Ok(s) => s, Err(_) => panic!()}; /// /// assert_eq!( /// PRIME_STATUSES, -/// // 25 26 27 28 29 -/// Ok([false, false, false, false, true]), +/// // 25 26 27 28 29 +/// [false, false, false, false, true], /// ); /// ``` -/// Sieve limited ranges of very large values +/// Sieve limited ranges of very large values. Functions provided by the crate can help you +/// compute the needed sieve size: /// ``` /// # use const_primes::{sieve_lt, SieveError}; -/// const BIG_PRIME_STATUSES: Result<[bool; 3], SieveError> = sieve_lt::<3, 70_711>(5_000_000_031); +/// use const_primes::isqrt; +/// const N: usize = 3; +/// const LIMIT: u64 = 5_000_000_031; +/// const BIG_PRIME_STATUSES: Result<[bool; N], SieveError> = sieve_lt::(LIMIT); /// assert_eq!( /// BIG_PRIME_STATUSES, /// // 5_000_000_028 5_000_000_029 5_000_000_030 @@ -216,6 +229,10 @@ impl std::error::Error for SieveError {} /// Returns the prime status of the `N` smallest integers greater than or equal to `lower_limit`. /// +/// Uses a sieve of size `MEM` during evaluation, but stores only the requested values in the binary. +/// `MEM` must be large enough for the sieve to be able to determine the prime status of all numbers in the requested range, +/// that is `MEM`^2 must be larger than `lower_limit + N`. +/// /// Fails to compile if `N` is 0, or if `MEM` is smaller than `N`. /// /// If you just want the prime status of the first N integers, see [`sieve`], and if you want the @@ -229,21 +246,22 @@ impl std::error::Error for SieveError {} /// // The three numbers larger than or equal to 9 are 9, 10 and 11. /// const N: usize = 3; /// const LIMIT: u64 = 9; -/// // We thus need a memory size of at least 4, since 3*3 < 11, which isn't enough. +/// // We thus need a memory size of at least 4, since 3*3 < 11, and therefore isn't enough. /// const MEM: usize = 4; /// const PRIME_STATUS: [bool; N] = match sieve_geq::(LIMIT) {Ok(s) => s, Err(_) => panic!()}; /// // 9, 10, 11 /// assert_eq!(PRIME_STATUS, [false, false, true]); /// ``` -/// Enough memory can also be ensured with the help of functions provided by this crate: +/// Sieve limited ranges of very large values. Functions provided by the crate can help you +/// compute the needed sieve size: /// ``` /// # use const_primes::{sieve_geq, SieveError}; /// use const_primes::isqrt; /// const N: usize = 3; /// const LIMIT: u64 = 5_000_000_038; -/// const PRIME_STATUS: Result<[bool; N], SieveError> = sieve_geq::(LIMIT); -/// // 5_000_000_038 5_000_000_039 5_000_000_040 -/// assert_eq!(PRIME_STATUS, Ok([false, true, false])); +/// const BIG_PRIME_STATUS: Result<[bool; N], SieveError> = sieve_geq::(LIMIT); +/// // 5_000_000_038 5_000_000_039 5_000_000_040 +/// assert_eq!(BIG_PRIME_STATUS, Ok([false, true, false])); /// ``` /// /// # Errors From 4878cf3a073f96e321738a940727bc3316d39173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:03:03 +0200 Subject: [PATCH 11/22] Return the right error variant --- src/sieving.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sieving.rs b/src/sieving.rs index b727628..bdea697 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -99,7 +99,7 @@ pub(crate) const fn sieve_segment( /// ``` /// # use const_primes::{sieve_lt, SieveError}; /// const PS: Result<[bool; 5], SieveError> = sieve_lt::<5, 5>(26); -/// assert_eq!(PS, Err(SieveError::TooLargeTotal)); +/// assert_eq!(PS, Err(SieveError::TooLargeLimit)); /// ``` /// or smaller than `N`: /// ``` @@ -125,7 +125,7 @@ pub const fn sieve_lt( }; if upper_limit > mem_sqr { - return Err(SieveError::TooLargeTotal); + return Err(SieveError::TooLargeLimit); } let n64 = N as u64; From c83853005d45ae5d434a8228d156bfbb4aa4255f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:05:17 +0200 Subject: [PATCH 12/22] Add example that shows SieveError::TotalDoesntFitU64 --- src/sieving.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sieving.rs b/src/sieving.rs index bdea697..15bc613 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -266,11 +266,13 @@ impl std::error::Error for SieveError {} /// /// # Errors /// -/// Returns an error if `MEM + lower_limit` is larger than `MEM^2`. +/// Returns an error if `MEM + lower_limit` is larger than `MEM^2` or doesn't fit in a `u64`. /// ``` /// # use const_primes::{sieve_geq, SieveError}; -/// const P: Result<[bool; 5], SieveError> = sieve_geq::<5, 5>(21); -/// assert_eq!(P, Err(SieveError::TooLargeTotal)); +/// const P1: Result<[bool; 5], SieveError> = sieve_geq::<5, 5>(21); +/// const P2: Result<[bool; 5], SieveError> = sieve_geq::<5, 5>(u64::MAX); +/// assert_eq!(P1, Err(SieveError::TooLargeTotal)); +/// assert_eq!(P2, Err(SieveError::TotalDoesntFitU64)); /// ``` #[must_use = "the function only returns a new value and does not modify its input"] pub const fn sieve_geq( From b677912372ab03b0a85574de53bd173046457991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:05:39 +0200 Subject: [PATCH 13/22] plural examples --- src/sieving.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sieving.rs b/src/sieving.rs index 15bc613..245ced3 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -238,7 +238,7 @@ impl std::error::Error for SieveError {} /// If you just want the prime status of the first N integers, see [`sieve`], and if you want the /// prime status of the integers below some number, see [`sieve_lt`]. /// -/// # Example +/// # Examples /// /// The size of the sieve, `MEM`, must be large enough for the largest sieved number to be smaller than `MEM`^2. /// ``` From ebdd5d01b45e2cab5d41570fc3239eb2bdc5db9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:14:56 +0200 Subject: [PATCH 14/22] Remove state from GenerationError --- src/generation.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/generation.rs b/src/generation.rs index 21bfe74..226bfd9 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -109,7 +109,7 @@ pub const fn primes() -> [Underlying; N] { /// Returns the `N` largest primes less than `upper_limit`. /// /// This function uses a segmented sieve of size `MEM` for computation, -/// but only saves the `N` requested primes in the binary. +/// but only returns the `N` requested primes in the output array. /// /// Set `MEM` such that `MEM*MEM >= upper_limit`. /// @@ -134,16 +134,16 @@ pub const fn primes() -> [Underlying; N] { /// # use const_primes::{primes_lt, GenerationError}; /// use const_primes::isqrt; /// const LIMIT: u64 = 5_000_000_030; -/// # #[allow(long_running_const_eval)] /// const BIG_PRIMES: Result<[u64; 3], GenerationError> = primes_lt::<3, {isqrt(LIMIT) as usize + 1}>(LIMIT); /// /// assert_eq!(BIG_PRIMES, Ok([4_999_999_903, 4_999_999_937, 5_000_000_029])); /// ``` +/// /// # Errors /// /// If the number of primes requested, `N`, is larger than -/// the number of primes that exists below the `upper_limit` we -/// will get an error: +/// the number of primes that exists below the `upper_limit` this function +/// returns an error: /// ``` /// # use const_primes::{primes_lt, GenerationError}; /// const N: usize = 9; @@ -151,13 +151,13 @@ pub const fn primes() -> [Underlying; N] { /// assert_eq!(PRIMES, Err(GenerationError::OutOfPrimes)); /// ``` /// -/// We will also get an error if `upper_limit` is larger than `MEM`^2 or if `upper_limit` is smaller than or equal to 2. +/// It also returns an error if `upper_limit` is larger than `MEM`^2 or if `upper_limit` is smaller than or equal to 2: /// ``` /// # use const_primes::{primes_lt, GenerationError}; /// const TOO_LARGE_LIMIT: Result<[u64; 3], GenerationError> = primes_lt::<3, 5>(26); -/// const TOO_SMALL_LIMIT: Result<[u64 ;1], GenerationError> = primes_lt::<1, 1>(1); -/// assert!(matches!(TOO_LARGE_LIMIT, Err(GenerationError::TooLargeLimit(_, _)))); -/// assert!(matches!(TOO_SMALL_LIMIT, Err(GenerationError::TooSmallLimit(_)))); +/// const TOO_SMALL_LIMIT: Result<[u64; 1], GenerationError> = primes_lt::<1, 1>(1); +/// assert_eq!(TOO_LARGE_LIMIT, Err(GenerationError::TooLargeLimit)); +/// assert_eq!(TOO_SMALL_LIMIT, Err(GenerationError::TooSmallLimit)); /// ``` #[must_use = "the function only returns a new value and does not modify its input"] pub const fn primes_lt( @@ -176,11 +176,11 @@ pub const fn primes_lt( }; if upper_limit <= 2 { - return Err(GenerationError::TooSmallLimit(upper_limit)); + return Err(GenerationError::TooSmallLimit); } if upper_limit > mem_sqr { - return Err(GenerationError::TooLargeLimit(upper_limit, mem_sqr)); + return Err(GenerationError::TooLargeLimit); } let mut primes: [u64; N] = [0; N]; @@ -264,7 +264,7 @@ macro_rules! primes_segment { /// Returns the `N` smallest primes greater than or equal to `lower_limit`. /// /// This function uses a segmented sieve of size `MEM` for computation, -/// but only saves the `N` requested primes in the binary. +/// but only returns the `N` requested primes in the output array. /// /// Set `MEM` such that `MEM`^2 is larger than the largest prime you will encounter. /// @@ -295,6 +295,7 @@ macro_rules! primes_segment { /// assert_eq!(PRIMES_GEQ, Ok([5_000_000_039, 5_000_000_059, 5_000_000_063])); /// # Ok::<(), GenerationError>(()) /// ``` +/// /// # Errors /// /// Only primes smaller than `MEM^2` can be generated, so if the sieve @@ -309,7 +310,7 @@ macro_rules! primes_segment { /// ``` /// # use const_primes::{primes_geq, GenerationError}; /// const PRIMES: Result<[u64; 5], GenerationError> = primes_geq::<5, 5>(26); -/// assert!(matches!(PRIMES, Err(GenerationError::TooLargeLimit(_, _)))); +/// assert_eq!(PRIMES, Err(GenerationError::TooLargeLimit)); /// ``` #[must_use = "the function only returns a new value and does not modify its input"] pub const fn primes_geq( @@ -341,7 +342,7 @@ pub const fn primes_geq( } if lower_limit >= mem_sqr { - return Err(GenerationError::TooLargeLimit(lower_limit, mem_sqr)); + return Err(GenerationError::TooLargeLimit); } let mut primes = [0; N]; @@ -386,9 +387,9 @@ pub const fn primes_geq( #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum GenerationError { /// The limit was larger than `MEM^2`. - TooLargeLimit(u64, u64), + TooLargeLimit, /// The limit was smaller than or equal to 2. - TooSmallLimit(u64), + TooSmallLimit, /// Encountered a number larger than `MEM`^2. SieveOverrun(u64), /// Ran out of primes. @@ -398,13 +399,13 @@ pub enum GenerationError { impl fmt::Display for GenerationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::TooLargeLimit(limit, mem_sqr) => write!( + Self::TooLargeLimit => write!( f, - "the limit ({limit}) was larger than `MEM`^2 ({mem_sqr})" + "the limit was larger than `MEM`^2" ), - Self::TooSmallLimit(limit) => write!( + Self::TooSmallLimit => write!( f, - "the limit was {limit}, which is smaller than or equal to 2" + "the limit was smaller than or equal to 2" ), Self::SieveOverrun(number) => write!( f, From 34368b5d09c5b85bb540cdfb78a2c5dd345a72f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:17:35 +0200 Subject: [PATCH 15/22] Clarify SieveOverrun in docstring --- src/generation.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/generation.rs b/src/generation.rs index 226bfd9..499508a 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -303,7 +303,8 @@ macro_rules! primes_segment { /// ``` /// # use const_primes::{primes_geq, GenerationError}; /// const PRIMES: Result<[u64; 3], GenerationError> = primes_geq::<3, 3>(5); -/// assert!(matches!(PRIMES, Err(GenerationError::SieveOverrun(_)))); +/// // The sieve is unable to determine the prime status of 9, since that is the same or larger than `MEM`^2. +/// assert_eq!(PRIMES, Err(GenerationError::SieveOverrun(9))); /// ``` /// /// Also returns an error if `lower_limit` is larger than or equal to `MEM^2`: From 80026858529e1e3024cad11124f354599f5e7ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:18:09 +0200 Subject: [PATCH 16/22] Correct error in SieveOverun docstring --- src/generation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generation.rs b/src/generation.rs index 499508a..9849388 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -391,7 +391,7 @@ pub enum GenerationError { TooLargeLimit, /// The limit was smaller than or equal to 2. TooSmallLimit, - /// Encountered a number larger than `MEM`^2. + /// Encountered a number larger than or equal to `MEM`^2. SieveOverrun(u64), /// Ran out of primes. OutOfPrimes, From efeff1de0b2dd5734486e572767b1f87ca927783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:19:28 +0200 Subject: [PATCH 17/22] Correct error in TooLargeLimit docstring --- src/generation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generation.rs b/src/generation.rs index 9849388..2162411 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -387,7 +387,7 @@ pub const fn primes_geq( /// is invalid or does not work to produce the requested primes. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum GenerationError { - /// The limit was larger than `MEM^2`. + /// The limit was larger than or equal to `MEM^2`. TooLargeLimit, /// The limit was smaller than or equal to 2. TooSmallLimit, From 772ba7b68bd7a51fa2b4d749a0195619040ea19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:27:58 +0200 Subject: [PATCH 18/22] Clarify estimation of sieve size in primes_lt and primes_geq --- src/generation.rs | 16 ++++++++++------ src/sieving.rs | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/generation.rs b/src/generation.rs index 2162411..d433544 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -129,12 +129,15 @@ pub const fn primes() -> [Underlying; N] { /// const PRIMES: [u64;4] = match primes_lt::<4, 10>(100) {Ok(ps) => ps, Err(_) => panic!()}; /// assert_eq!(PRIMES, [79, 83, 89, 97]); /// ``` -/// Compute larger primes: +/// Compute limited ranges of large primes. Functions provided by the crate can help you +/// compute the needed sieve size: /// ``` /// # use const_primes::{primes_lt, GenerationError}; /// use const_primes::isqrt; +/// const N: usize = 3; /// const LIMIT: u64 = 5_000_000_030; -/// const BIG_PRIMES: Result<[u64; 3], GenerationError> = primes_lt::<3, {isqrt(LIMIT) as usize + 1}>(LIMIT); +/// const MEM: usize = isqrt(LIMIT) as usize + 1; +/// const BIG_PRIMES: Result<[u64; N], GenerationError> = primes_lt::(LIMIT); /// /// assert_eq!(BIG_PRIMES, Ok([4_999_999_903, 4_999_999_937, 5_000_000_029])); /// ``` @@ -279,19 +282,20 @@ macro_rules! primes_segment { /// /// Basic usage: /// ``` -/// use const_primes::{primes_geq, GenerationError}; +/// use const_primes::primes_geq; /// // Compute 5 primes larger than 40. The largest will be 59, so `MEM` needs to be at least 8. /// const PRIMES: [u64; 5] = match primes_geq::<5, 8>(40) {Ok(ps) => ps, Err(_) => panic!()}; /// assert_eq!(PRIMES, [41, 43, 47, 53, 59]); /// ``` -/// Estimate the size of `MEM` using the square root of the limit (and some extra, proportional to `N`): +/// Compute limited ranges of large primes. Functions provided by the crate can help you +/// compute the needed sieve size: /// ``` /// # use const_primes::{primes_geq, GenerationError}; /// use const_primes::isqrt; /// const N: usize = 3; /// const LIMIT: u64 = 5_000_000_030; -/// # #[allow(long_running_const_eval)] -/// const PRIMES_GEQ: Result<[u64; N], GenerationError> = primes_geq::(LIMIT); +/// const MEM: usize = isqrt(LIMIT) as usize + 1 + N; +/// const PRIMES_GEQ: Result<[u64; N], GenerationError> = primes_geq::(LIMIT); /// assert_eq!(PRIMES_GEQ, Ok([5_000_000_039, 5_000_000_059, 5_000_000_063])); /// # Ok::<(), GenerationError>(()) /// ``` diff --git a/src/sieving.rs b/src/sieving.rs index 245ced3..1c84eb6 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -78,7 +78,7 @@ pub(crate) const fn sieve_segment( /// [false, false, false, false, true], /// ); /// ``` -/// Sieve limited ranges of very large values. Functions provided by the crate can help you +/// Sieve limited ranges of large values. Functions provided by the crate can help you /// compute the needed sieve size: /// ``` /// # use const_primes::{sieve_lt, SieveError}; @@ -252,7 +252,7 @@ impl std::error::Error for SieveError {} /// // 9, 10, 11 /// assert_eq!(PRIME_STATUS, [false, false, true]); /// ``` -/// Sieve limited ranges of very large values. Functions provided by the crate can help you +/// Sieve limited ranges of large values. Functions provided by the crate can help you /// compute the needed sieve size: /// ``` /// # use const_primes::{sieve_geq, SieveError}; From 83df0ff1b7d85fbad69b35092a27e70b938411d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:30:15 +0200 Subject: [PATCH 19/22] Clarify MEM computation in sieve_lt --- src/sieving.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sieving.rs b/src/sieving.rs index 1c84eb6..3028c5f 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -51,7 +51,7 @@ pub(crate) const fn sieve_segment( /// Returns an array of size `N` that indicates which of the `N` integers in smaller than `upper_limit` are prime. /// -/// Uses a sieve of size `MEM` during evaluation, but stores only the requested values in the return array. +/// Uses a sieve of size `MEM` during evaluation, but stores only the requested values in the output array. /// `MEM` must be large enough for the sieve to be able to determine the prime status of all numbers in the requested range, /// that is: `MEM`^2 must be at least as large as `upper_limit`. /// @@ -85,7 +85,8 @@ pub(crate) const fn sieve_segment( /// use const_primes::isqrt; /// const N: usize = 3; /// const LIMIT: u64 = 5_000_000_031; -/// const BIG_PRIME_STATUSES: Result<[bool; N], SieveError> = sieve_lt::(LIMIT); +/// const MEM: usize = isqrt(LIMIT) as usize + 1; +/// const BIG_PRIME_STATUSES: Result<[bool; N], SieveError> = sieve_lt::(LIMIT); /// assert_eq!( /// BIG_PRIME_STATUSES, /// // 5_000_000_028 5_000_000_029 5_000_000_030 @@ -229,7 +230,7 @@ impl std::error::Error for SieveError {} /// Returns the prime status of the `N` smallest integers greater than or equal to `lower_limit`. /// -/// Uses a sieve of size `MEM` during evaluation, but stores only the requested values in the binary. +/// Uses a sieve of size `MEM` during evaluation, but stores only the requested values in the output array. /// `MEM` must be large enough for the sieve to be able to determine the prime status of all numbers in the requested range, /// that is `MEM`^2 must be larger than `lower_limit + N`. /// @@ -259,7 +260,8 @@ impl std::error::Error for SieveError {} /// use const_primes::isqrt; /// const N: usize = 3; /// const LIMIT: u64 = 5_000_000_038; -/// const BIG_PRIME_STATUS: Result<[bool; N], SieveError> = sieve_geq::(LIMIT); +/// const MEM: usize = isqrt(LIMIT) as usize + 1 + N; +/// const BIG_PRIME_STATUS: Result<[bool; N], SieveError> = sieve_geq::(LIMIT); /// // 5_000_000_038 5_000_000_039 5_000_000_040 /// assert_eq!(BIG_PRIME_STATUS, Ok([false, true, false])); /// ``` From 42f2860ef105e08d0550aff785460d92c8d90b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:37:38 +0200 Subject: [PATCH 20/22] Clarify MEM computation in lib.rs docstring --- src/lib.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 22b37c5..77b39d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,10 @@ //! use const_primes::{primes_lt, GenerationError, isqrt}; //! const LIMIT: u64 = 5_000_000_031; //! const N: usize = 3; -//! const PRIMES_LT: Result<[u64; N], GenerationError> = primes_lt::(LIMIT); +//! // `const_primes::isqrt` can be used to compute the memory requirement of the sieve. +//! // Due to limitations on const generics, this can not be done inside the function. +//! const MEM: usize = isqrt(LIMIT) as usize + 1; +//! const PRIMES_LT: Result<[u64; N], GenerationError> = primes_lt::(LIMIT); //! //! assert_eq!(PRIMES_LT, Ok([4_999_999_903, 4_999_999_937, 5_000_000_029])); //! ``` @@ -73,8 +76,11 @@ //! it may, however, overestimate the required sieve size. //! //! ``` -//! # use const_primes::{sieve_lt, SieveError}; -//! const PRIME_STATUS_LT: Result<[bool; 3], SieveError> = sieve_lt::<3, 70_711>(5_000_000_031); +//! # use const_primes::{sieve_geq, SieveError, isqrt}; +//! const N: usize = 3; +//! const LIMIT: u64 = 5_000_000_038; +//! const MEM: usize = isqrt(LIMIT) as usize + 1 + N; +//! const PRIME_STATUS_LT: Result<[bool; N], SieveError> = sieve_geq::(LIMIT); //! // 5_000_000_028 5_000_000_029 5_000_000_030 //! assert_eq!(PRIME_STATUS_LT, Ok([false, true, false])); //! ``` From 194b05ec4cd981684ab90bde196b85c302fa6768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:38:26 +0200 Subject: [PATCH 21/22] Remove disclaimer for now fixed feature! --- src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 77b39d9..6f5a01b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,9 +84,6 @@ //! // 5_000_000_028 5_000_000_029 5_000_000_030 //! assert_eq!(PRIME_STATUS_LT, Ok([false, true, false])); //! ``` -//! Unfortunately the output array must be large enough to contain the prime sieve, which scales with -//! the square root of largest relavant number, which is why the examples use a size of over 70000 even though -//! they're only interested in three numbers. //! //! ## Other functionality //! From c201239c69ca242b1f5343f66e67606957a6e09f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20S=C3=B6rng=C3=A5rd?= Date: Tue, 7 May 2024 20:53:08 +0200 Subject: [PATCH 22/22] Implement sieve_segment --- src/sieving.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/sieving.rs b/src/sieving.rs index 3028c5f..8676e4d 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -320,6 +320,50 @@ pub const fn sieve_geq( Ok(ans) } +/// Call [`sieve_lt`] and [`sieve_geq`], and automatically compute the memory requirement of the sieve. +/// +/// Compute the value of the const generic `MEM` as `isqrt(upper_limit) + 1` for [`sieve_lt`] +/// and as `isqrt(lower_limit) + 1 + N` for [`sieve_geq`]. +/// +/// # Examples +/// +/// ``` +/// # use const_primes::{sieve_segment, SieveError}; +/// const PRIME_STATUS_LT: Result<[bool; 5], SieveError> = sieve_segment!(5; < 100_005); +/// const PRIME_STATUS_GEQ: Result<[bool; 7], SieveError> = sieve_segment!(7; >= 615); +/// assert_eq!( +/// PRIME_STATUS_LT, +/// // 100_000 100_101 100_102 100_103 100_104 +/// Ok([false, false, false, true, false]) +/// ); +/// assert_eq!( +/// PRIME_STATUS_GEQ, +/// // 615 616 617 618 619 620 621 +/// Ok([false, false, true, false, true, false, false]) +/// ); +/// ``` +#[macro_export] +macro_rules! sieve_segment { + ($n:expr; < $lim:expr) => { + $crate::sieve_lt::< + { $n }, + { + let mem = { $lim }; + $crate::isqrt(mem) as ::core::primitive::usize + 1 + }, + >({ $lim }) + }; + ($n:expr; >= $lim:expr) => { + $crate::sieve_geq::< + { $n }, + { + let mem = { $lim }; + $crate::isqrt(mem) as ::core::primitive::usize + 1 + { $n } + }, + >({ $lim }) + }; +} + #[cfg(test)] mod test { use super::{sieve, sieve_segment};