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}