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}