const_primes/sieve.rs
1//! This module contains implementations of prime sieves.
2
3use core::fmt;
4
5use crate::isqrt;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub(crate) struct SegmentedSieveError;
9
10impl fmt::Display for SegmentedSieveError {
11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12 write!(f, "the upper limit was smaller than `N`")
13 }
14}
15
16impl core::error::Error for SegmentedSieveError {}
17
18/// Uses the primalities of the first `N` integers in `base_sieve` to sieve the numbers in the range `[upper_limit - N, upper_limit)`.
19/// Assumes that the base sieve contains the prime status of the `N` fist integers. The output is only meaningful
20/// for the numbers below `N^2`.
21///
22/// # Errors
23///
24/// Returns an error if `upper_limit` < `N`
25#[must_use = "the function only returns a new value and does not modify its inputs"]
26pub(crate) const fn sieve_segment<const N: usize>(
27 base_sieve: &[bool; N],
28 upper_limit: u64,
29) -> Result<[bool; N], SegmentedSieveError> {
30 let mut segment_sieve = [true; N];
31
32 let Some(lower_limit) = upper_limit.checked_sub(N as u64) else {
33 return Err(SegmentedSieveError);
34 };
35
36 if lower_limit == 0 && N > 1 {
37 // If the lower limit is 0 we can just return the base sieve.
38 return Ok(*base_sieve);
39 } else if lower_limit == 1 && N > 0 {
40 // In case 1 is included in the upper sieve we need to treat it as a special case
41 // since it's not a multiple of any prime in `base_sieve` even though it's not prime.
42 segment_sieve[0] = false;
43 }
44
45 let mut i = 0;
46 while i < N {
47 if base_sieve[i] {
48 let prime = i as u64;
49
50 // Find the smallest multiple of the prime larger than or equal to `lower_limit`.
51 let mut composite = (lower_limit / prime) * prime;
52 if composite < lower_limit {
53 composite += prime;
54 }
55 if composite == prime {
56 composite += prime;
57 }
58
59 // Sieve all numbers in the segment that are multiples of the prime.
60 while composite < upper_limit {
61 segment_sieve[(composite - lower_limit) as usize] = false;
62 composite += prime;
63 }
64 }
65 i += 1;
66 }
67
68 Ok(segment_sieve)
69}
70
71/// Returns an array of size `N` that indicates which of the `N` largest integers smaller than `upper_limit` are prime.
72///
73/// Uses a sieve of size `MEM` during evaluation, but stores only the requested values in the output array.
74/// `MEM` must be large enough for the sieve to be able to determine the prime status of all numbers in the requested range,
75/// that is: `MEM`^2 must be at least as large as `upper_limit`.
76///
77/// If you just want the prime status of the first `N` integers, see [`sieve`], and if you want the prime status of
78/// the integers above some number, see [`sieve_geq`].
79///
80/// If you do not wish to compute the size requirement of the sieve manually, take a look at [`sieve_segment!`](crate::sieve_segment).
81///
82/// # Examples
83///
84/// Basic usage
85///
86/// ```
87/// # use const_primes::sieve_lt;
88/// // The five largest numbers smaller than 30 are 25, 26, 27, 28 and 29.
89/// const N: usize = 5;
90/// const LIMIT: u64 = 30;
91/// // We thus need a memory size of at least 6, since 5*5 < 29, and therefore isn't enough.
92/// const MEM: usize = 6;
93/// const PRIME_STATUSES: [bool; N] = match sieve_lt::<N, MEM>(LIMIT) {Ok(s) => s, Err(_) => panic!()};
94///
95/// assert_eq!(
96/// PRIME_STATUSES,
97/// // 25 26 27 28 29
98/// [false, false, false, false, true],
99/// );
100/// ```
101///
102/// Sieve limited ranges of large values. Functions provided by the crate can help you
103/// compute the needed sieve size:
104///
105/// ```
106/// # use const_primes::{sieve_lt, SieveError};
107/// use const_primes::isqrt;
108/// const N: usize = 3;
109/// const LIMIT: u64 = 5_000_000_031;
110/// const MEM: usize = isqrt(LIMIT) as usize + 1;
111/// const BIG_PRIME_STATUSES: Result<[bool; N], SieveError> = sieve_lt::<N, MEM>(LIMIT);
112/// assert_eq!(
113/// BIG_PRIME_STATUSES,
114/// // 5_000_000_028 5_000_000_029 5_000_000_030
115/// Ok([false, true, false]),
116/// );
117/// ```
118///
119/// # Errors
120///
121/// Returns an error if `upper_limit` is larger than `MEM`^2:
122///
123/// ```
124/// # use const_primes::{sieve_lt, SieveError};
125/// const PS: Result<[bool; 5], SieveError> = sieve_lt::<5, 5>(26);
126/// assert_eq!(PS, Err(SieveError::TooSmallSieveSize));
127/// ```
128///
129/// or smaller than `N`:
130///
131/// ```
132/// # use const_primes::{sieve_lt, SieveError};
133/// const PS: Result<[bool; 5], SieveError> = sieve_lt::<5, 5>(4);
134/// assert_eq!(PS, Err(SieveError::TooSmallLimit));
135/// ```
136///
137/// It is a compile error if `MEM` is smaller than `N`, or if `MEM`^2 does not fit in a `u64`:
138///
139/// ```compile_fail
140/// # use const_primes::{sieve_lt, SieveError};
141/// const TOO_SMALL_MEM: Result<[bool; 5], SieveError> = sieve_lt::<5, 2>(20);
142/// ```
143///
144/// ```compile_fail
145/// # use const_primes::{sieve_lt, SieveError};
146/// const TOO_LARGE_MEM: Result<[bool; 5], SieveError> = sieve_lt::<5, 1_000_000_000_000>(20);
147/// ```
148#[must_use = "the function only returns a new value and does not modify its input"]
149pub const fn sieve_lt<const N: usize, const MEM: usize>(
150 upper_limit: u64,
151) -> Result<[bool; N], SieveError> {
152 const { assert!(MEM >= N, "`MEM` must be at least as large as `N`") }
153
154 let mem_sqr = const {
155 let mem64 = MEM as u64;
156 match mem64.checked_mul(mem64) {
157 Some(mem_sqr) => mem_sqr,
158 None => panic!("`MEM`^2 must fit in a `u64`"),
159 }
160 };
161
162 if upper_limit > mem_sqr {
163 return Err(SieveError::TooSmallSieveSize);
164 }
165
166 let n64 = N as u64;
167
168 if upper_limit < n64 {
169 return Err(SieveError::TooSmallLimit);
170 }
171
172 if N == 0 {
173 return Ok([false; N]);
174 }
175
176 if upper_limit == n64 {
177 // If we are not interested in sieving a larger range we can just return early.
178 return Ok(sieve());
179 }
180
181 // Use a normal sieve of Eratosthenes for the first N numbers.
182 let base_sieve: [bool; MEM] = sieve();
183
184 // Use the result to sieve the higher range.
185 let (offset, upper_sieve) = match sieve_segment(&base_sieve, upper_limit) {
186 Ok(res) => (0, res),
187 // The sieve contained more entries than there are non-negative numbers below the upper limit, just use the base sieve.
188 Err(_) => ((MEM as u64 - upper_limit) as usize, base_sieve),
189 };
190
191 let mut i = 0;
192 let mut ans = [false; N];
193 while i < N {
194 ans[N - 1 - i] = upper_sieve[MEM - 1 - i - offset];
195 i += 1;
196 }
197 Ok(ans)
198}
199
200/// Returns an array of size `N` where the value at a given index indicates whether the index is prime.
201///
202/// # Example
203///
204/// ```
205/// # use const_primes::sieve;
206/// const PRIMALITY: [bool; 10] = sieve();
207/// // 0 1 2 3 4 5 6 7 8 9
208/// assert_eq!(PRIMALITY, [false, false, true, true, false, true, false, true, false, false]);
209/// ```
210#[must_use = "the function only returns a new value"]
211pub const fn sieve<const N: usize>() -> [bool; N] {
212 let mut sieve = [true; N];
213 if N == 0 {
214 return sieve;
215 }
216 if N > 0 {
217 sieve[0] = false;
218 }
219 if N > 1 {
220 sieve[1] = false;
221 }
222
223 let mut number: usize = 2;
224 let bound = isqrt(N as u64);
225 // For all numbers up to and including sqrt(n):
226 while (number as u64) <= bound {
227 if sieve[number] {
228 // If a number is prime we enumerate all multiples of it
229 // starting from its square,
230 let Some(mut composite) = number.checked_mul(number) else {
231 break;
232 };
233
234 // and mark them as not prime.
235 while composite < N {
236 sieve[composite] = false;
237 composite = match composite.checked_add(number) {
238 Some(sum) => sum,
239 None => break,
240 };
241 }
242 }
243 number += 1;
244 }
245
246 sieve
247}
248
249/// The error returned by [`sieve_lt`] and [`sieve_geq`] if the input
250/// is invalid or does not work to sieve the requested range.
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
252#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
253#[cfg_attr(
254 feature = "rkyv",
255 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
256)]
257pub enum SieveError {
258 /// The limit was less than or equal to `N` (for `sieve_lt`).
259 TooSmallLimit,
260 /// `MEM`^2 was smaller than the largest encountered value.
261 TooSmallSieveSize,
262 /// `limit + MEM` did not fit in a `u64`.
263 TotalDoesntFitU64,
264}
265
266impl fmt::Display for SieveError {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 match self {
269 Self::TooSmallLimit => write!(f, "`limit` must be at least `N`"),
270 Self::TooSmallSieveSize => {
271 write!(f, "`MEM`^2 was smaller than the largest encountered value")
272 }
273 Self::TotalDoesntFitU64 => write!(f, "`MEM + limit` must fit in a `u64`"),
274 }
275 }
276}
277
278impl core::error::Error for SieveError {}
279
280/// Returns an array of size `N` that indicates which of the `N` smallest integers greater than or equal to `lower_limit` are prime.
281///
282/// Uses a sieve of size `MEM` during evaluation, but stores only the requested values in the output array.
283/// `MEM` must be large enough for the sieve to be able to determine the prime status of all numbers in the requested range,
284/// that is `MEM`^2 must be larger than `lower_limit + N`.
285///
286/// If you just want the prime status of the first N integers, see [`sieve`], and if you want the
287/// prime status of the integers below some number, see [`sieve_lt`].
288///
289/// If you do not wish to compute the size requirement of the sieve manually, take a look at [`sieve_segment!`](crate::sieve_segment).
290///
291/// # Examples
292///
293/// The size of the sieve, `MEM`, must be large enough for the largest sieved number to be smaller than `MEM`^2:
294///
295/// ```
296/// # use const_primes::sieve_geq;
297/// // The three numbers larger than or equal to 9 are 9, 10 and 11.
298/// const N: usize = 3;
299/// const LIMIT: u64 = 9;
300/// // We thus need a memory size of at least 4, since 3*3 < 11, and therefore isn't enough.
301/// const MEM: usize = 4;
302/// const PRIME_STATUS: [bool; N] = match sieve_geq::<N, MEM>(LIMIT) {Ok(s) => s, Err(_) => panic!()};
303/// // 9, 10, 11
304/// assert_eq!(PRIME_STATUS, [false, false, true]);
305/// ```
306///
307/// Sieve limited ranges of large values. Functions provided by the crate can help you
308/// compute the needed sieve size:
309///
310/// ```
311/// # use const_primes::{sieve_geq, SieveError};
312/// use const_primes::isqrt;
313/// const N: usize = 3;
314/// const LIMIT: u64 = 5_000_000_038;
315/// const MEM: usize = isqrt(LIMIT) as usize + 1 + N;
316/// const BIG_PRIME_STATUS: Result<[bool; N], SieveError> = sieve_geq::<N, MEM>(LIMIT);
317/// // 5_000_000_038 5_000_000_039 5_000_000_040
318/// assert_eq!(BIG_PRIME_STATUS, Ok([false, true, false]));
319/// ```
320///
321/// # Errors
322///
323/// Returns an error if `MEM + lower_limit` is larger than `MEM^2` or doesn't fit in a `u64`:
324///
325/// ```
326/// # use const_primes::{sieve_geq, SieveError};
327/// const P1: Result<[bool; 5], SieveError> = sieve_geq::<5, 5>(21);
328/// const P2: Result<[bool; 5], SieveError> = sieve_geq::<5, 5>(u64::MAX);
329/// assert_eq!(P1, Err(SieveError::TooSmallSieveSize));
330/// assert_eq!(P2, Err(SieveError::TotalDoesntFitU64));
331/// ```
332///
333/// It is a compile error if `MEM` is smaller than `N`, or if `MEM`^2 does not fit in a `u64`:
334///
335/// ```compile_fail
336/// # use const_primes::{sieve_geq, SieveError};
337/// const TOO_SMALL_MEM: Result<[bool; 5], SieveError> = sieve_geq::<5, 2>(100);
338/// ```
339///
340/// ```compile_fail
341/// # use const_primes::{sieve_geq, SieveError};
342/// const TOO_LARGE_MEM: Result<[bool; 5], SieveError> = sieve_geq::<5, 1_000_000_000_000>(100);
343/// ```
344#[must_use = "the function only returns a new value and does not modify its input"]
345pub const fn sieve_geq<const N: usize, const MEM: usize>(
346 lower_limit: u64,
347) -> Result<[bool; N], SieveError> {
348 const { assert!(MEM >= N, "`MEM` must be at least as large as `N`") }
349
350 let (mem64, mem_sqr) = const {
351 let mem64 = MEM as u64;
352 match mem64.checked_mul(mem64) {
353 Some(mem_sqr) => (mem64, mem_sqr),
354 None => panic!("`MEM`^2 must fit in a `u64`"),
355 }
356 };
357
358 let Some(upper_limit) = mem64.checked_add(lower_limit) else {
359 return Err(SieveError::TotalDoesntFitU64);
360 };
361
362 if upper_limit > mem_sqr {
363 return Err(SieveError::TooSmallSieveSize);
364 }
365
366 if N == 0 {
367 return Ok([false; N]);
368 }
369
370 // If `lower_limit` is zero then this is the same as just calling `sieve`, and we can return early.
371 if lower_limit == 0 {
372 // We do not merge it with the computation of `base_sieve` below, since here we only
373 // compute `N` values instead of `MEM`.
374 return Ok(sieve());
375 }
376
377 let base_sieve: [bool; MEM] = sieve();
378
379 let Ok(upper_sieve) = sieve_segment(&base_sieve, upper_limit) else {
380 panic!("this is already checked above")
381 };
382
383 let mut ans = [false; N];
384 let mut i = 0;
385 while i < N {
386 ans[i] = upper_sieve[i];
387 i += 1;
388 }
389 Ok(ans)
390}
391
392/// Generate arrays of the prime status of large numbers without having to store the prime status
393/// of every single integer smaller than the target in the result, and thus potentially the binary.
394///
395/// Calls [`sieve_geq`] or [`sieve_lt`], and automatically computes the memory requirement of the sieve.
396///
397/// Sieve the `N` smallest numbers larger than or equal to some limit as `sieve_segment!(N; >= LIMIT)`,
398/// and the `N` largest numbers smaller than some limit as `sieve_segment!(N; < LIMIT)`.
399///
400/// Computes the sieve size as `isqrt(upper_limit) + 1` for [`sieve_lt`]
401/// and as `isqrt(lower_limit) + 1 + N` for [`sieve_geq`].
402/// This may overestimate the memory requirement for `sieve_geq`.
403///
404/// # Examples
405///
406/// ```
407/// # use const_primes::{sieve_segment, SieveError};
408/// const PRIME_STATUS_LT: Result<[bool; 5], SieveError> = sieve_segment!(5; < 100_005);
409/// const PRIME_STATUS_GEQ: Result<[bool; 7], SieveError> = sieve_segment!(7; >= 615);
410/// assert_eq!(
411/// PRIME_STATUS_LT,
412/// // 100_000 100_101 100_102 100_103 100_104
413/// Ok([false, false, false, true, false])
414/// );
415/// assert_eq!(
416/// PRIME_STATUS_GEQ,
417/// // 615 616 617 618 619 620 621
418/// Ok([false, false, true, false, true, false, false])
419/// );
420/// ```
421///
422/// # Errors
423///
424/// Has the same error behaviour as [`sieve_geq`] and [`sieve_lt`], with the exception
425/// that it sets `MEM` such that the sieve doesn't run out of memory.
426#[macro_export]
427macro_rules! sieve_segment {
428 ($n:expr; < $lim:expr) => {
429 $crate::sieve_lt::<
430 { $n },
431 {
432 let mem: u64 = { $lim };
433 $crate::isqrt(mem) as ::core::primitive::usize + 1
434 },
435 >({ $lim })
436 };
437 ($n:expr; >= $lim:expr) => {
438 $crate::sieve_geq::<
439 { $n },
440 {
441 let mem: u64 = { $lim };
442 $crate::isqrt(mem) as ::core::primitive::usize + 1 + { $n }
443 },
444 >({ $lim })
445 };
446}
447
448#[cfg(test)]
449mod test {
450 use crate::SieveError;
451
452 use super::{sieve, sieve_geq, sieve_lt, sieve_segment, SegmentedSieveError};
453
454 #[test]
455 fn test_consistency_of_sieve_segment() {
456 const P: [bool; 10] = match sieve_segment(&sieve(), 10) {
457 Ok(s) => s,
458 Err(_) => panic!(),
459 };
460 const PP: [bool; 10] = match sieve_segment(&sieve(), 11) {
461 Ok(s) => s,
462 Err(_) => panic!(),
463 };
464 assert_eq!(P, sieve());
465 assert_eq!(PP, sieve::<11>()[1..]);
466 assert_eq!(
467 sieve_segment::<5>(&[false, false, true, true, false], 4),
468 Err(SegmentedSieveError)
469 );
470 assert_eq!(sieve_segment(&sieve::<5>(), 5), Ok(sieve()));
471 }
472
473 #[test]
474 fn test_sieve_lt() {
475 assert_eq!(sieve_lt::<5, 5>(30), Err(SieveError::TooSmallSieveSize));
476 assert_eq!(sieve_lt::<5, 5>(4), Err(SieveError::TooSmallLimit));
477 assert_eq!(sieve_lt::<5, 5>(5), Ok(sieve()));
478 assert_eq!(sieve_lt::<2, 5>(20), Ok([false, true]));
479 }
480
481 #[test]
482 fn test_sieve() {
483 assert_eq!(sieve(), [false; 0]);
484 }
485
486 #[test]
487 fn test_sieve_geq() {
488 assert_eq!(
489 sieve_geq::<5, 5>(u64::MAX),
490 Err(SieveError::TotalDoesntFitU64)
491 );
492 assert_eq!(sieve_geq::<5, 5>(30), Err(SieveError::TooSmallSieveSize));
493 assert_eq!(sieve_geq::<0, 1>(0), Ok([false; 0]))
494 }
495}