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