diff --git a/README.md b/README.md index 918fa83..1c30231 100644 --- a/README.md +++ b/README.md @@ -55,16 +55,15 @@ 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. +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/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))) }); } } 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..d433544 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. @@ -107,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`. /// @@ -121,46 +123,49 @@ 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!()}; /// 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, Error}; +/// # use const_primes::{primes_lt, GenerationError}; /// use const_primes::isqrt; +/// const N: usize = 3; /// 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 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])); /// ``` +/// /// # 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, 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. +/// 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, 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_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( 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 +179,11 @@ pub const fn primes_lt( }; if upper_limit <= 2 { - return Err(Error::TooSmallLimit(upper_limit)); + return Err(GenerationError::TooSmallLimit); } if upper_limit > mem_sqr { - return Err(Error::TooLargeLimit(upper_limit, mem_sqr)); + return Err(GenerationError::TooLargeLimit); } let mut primes: [u64; N] = [0; N]; @@ -210,7 +215,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 +231,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); /// @@ -241,28 +246,28 @@ 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 }) }; } /// 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. /// @@ -277,42 +282,45 @@ macro_rules! primes_segment { /// /// Basic usage: /// ``` -/// use const_primes::{primes_geq, Error}; +/// 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, 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 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::<(), 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); +/// // 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`: /// ``` -/// # 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_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( 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 +333,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); + } let mut primes = [0; N]; let mut total_found_primes = 0; @@ -351,7 +367,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 +387,43 @@ pub const fn primes_geq( Ok(primes) } +/// 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 { + /// The limit was larger than or equal to `MEM^2`. + TooLargeLimit, + /// The limit was smaller than or equal to 2. + TooSmallLimit, + /// Encountered a number larger than or equal to `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 => write!( + f, + "the limit was larger than `MEM`^2" + ), + Self::TooSmallLimit => write!( + f, + "the limit was 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 +433,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 +451,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..6f5a01b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,19 +53,22 @@ //! (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::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])); //! ``` //! 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])); @@ -73,15 +76,14 @@ //! 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_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])); //! ``` -//! 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 //! @@ -116,7 +118,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 +125,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 +255,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); } @@ -281,9 +281,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()); } )+ }; @@ -303,9 +303,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..8676e4d 100644 --- a/src/sieving.rs +++ b/src/sieving.rs @@ -49,86 +49,115 @@ 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 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`. /// -/// Uses a sieve of Eratosthenes to sieve the first `N` integers -/// and then uses the result to sieve the output range if needed. +/// 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. +/// 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); +/// // 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, -/// // 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 +/// [false, false, false, false, true], /// ); /// ``` -/// Sieve limited ranges of very large values +/// Sieve limited ranges of large values. Functions provided by the crate can help you +/// compute the needed sieve size: /// ``` -/// # 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}; +/// use const_primes::isqrt; +/// const N: usize = 3; +/// const LIMIT: u64 = 5_000_000_031; +/// const MEM: usize = isqrt(LIMIT) as usize + 1; +/// const BIG_PRIME_STATUSES: Result<[bool; N], SieveError> = sieve_lt::(LIMIT); /// 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: /// ``` -/// ```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>(26); +/// assert_eq!(PS, Err(SieveError::TooLargeLimit)); +/// ``` +/// or smaller than `N`: +/// ``` +/// # 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::TooLargeLimit); } - 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. /// Fails to compile if `N` is 0. /// -/// Uses a sieve of Eratosthenes. -/// /// # Example /// /// ``` @@ -175,56 +204,164 @@ 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 { + TooSmallLimit, + TooLargeLimit, + TooLargeTotal, + TotalDoesntFitU64, +} + +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`"), + } + } +} + +#[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. /// -/// # Example +/// 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`. +/// +/// Fails to compile if `N` is 0, or if `MEM` is smaller than `N`. /// -/// Basic usage: +/// 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`]. +/// +/// # Examples +/// +/// 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; -/// const PRIME_STATUS: [bool; 5] = sieve_geq(10); -/// // 10 11 12 13 14 -/// assert_eq!(PRIME_STATUS, [false, true, false, true, false]); +/// // 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, 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]); +/// ``` +/// 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}; +/// use const_primes::isqrt; +/// const N: usize = 3; +/// const LIMIT: u64 = 5_000_000_038; +/// 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])); /// ``` -/// # Panics /// -/// 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); +/// # Errors +/// +/// 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 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(lower_limit: u64) -> [bool; N] { - const { assert!(N > 0, "`N` must be at least 1") } - - let n64 = N as u64; +pub const fn sieve_geq( + lower_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 (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`"), + } + }; - // 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; + // 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()); + } + + let base_sieve: [bool; MEM] = sieve(); + + let upper_sieve = sieve_segment(&base_sieve, upper_limit); + + let mut ans = [false; N]; + let mut i = 0; + while i < N { + ans[i] = upper_sieve[i]; + i += 1; } + Ok(ans) +} - sieve_segment(&base_sieve, upper_limit) +/// 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)]