Skip to content

Commit 59d893e

Browse files
authored
Fix a bug where primes_lt and sieve_lt could panic if the underlying segmented sieve happend to run down past 0. (#55)
* Add tests of primes_geq, sieve_lt and primes_lt * Make sieve_segment return an error when upper_limit is less than N, and use that signal in sieve_lt and primes_lt * Add test of sieve_segment error
1 parent 44caa41 commit 59d893e

File tree

2 files changed

+89
-15
lines changed

2 files changed

+89
-15
lines changed

src/generate.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,15 @@ pub const fn primes_lt<const N: usize, const MEM: usize>(
194194
// This is the smallest prime we have found so far.
195195
let mut smallest_found_prime = primes[N - 1 - total_primes_found];
196196
// Sieve for primes in the segment.
197-
let upper_sieve: [bool; MEM] = sieve_segment(&base_sieve, upper_limit);
197+
let (offset, upper_sieve) = match sieve_segment(&base_sieve, upper_limit) {
198+
Ok(res) => (0, res),
199+
Err(_) => ((MEM as u64 - upper_limit) as usize, base_sieve),
200+
};
198201

199202
let mut i: usize = 0;
200-
while i < MEM {
203+
while i < MEM - offset {
201204
// Iterate backwards through the upper sieve.
202-
if upper_sieve[MEM - 1 - i] {
205+
if upper_sieve[MEM - 1 - i - offset] {
203206
smallest_found_prime = upper_limit - 1 - i as u64;
204207
// Write every found prime to the primes array.
205208
primes[N - 1 - total_primes_found] = smallest_found_prime;
@@ -368,7 +371,10 @@ pub const fn primes_geq<const N: usize, const MEM: usize>(
368371
let base_sieve: [bool; MEM] = sieve();
369372
let mut sieve_limit = lower_limit;
370373
'generate: while total_found_primes < N {
371-
let upper_sieve = sieve_segment(&base_sieve, sieve_limit + mem64);
374+
let upper_sieve = match sieve_segment(&base_sieve, sieve_limit + mem64) {
375+
Ok(res) => res,
376+
Err(_) => panic!("can not happen since we set upper limit to mem + nonzero stuff"),
377+
};
372378

373379
let mut i = 0;
374380
while i < MEM {
@@ -463,6 +469,36 @@ mod test {
463469
for &prime in primes_geq::<2_000, 2_008>(3_998_000).unwrap().as_slice() {
464470
assert!(is_prime(prime));
465471
}
472+
assert_eq!(primes_geq::<0, 0>(10), Ok([]));
473+
assert_eq!(primes_geq::<3, 3>(2), Ok([2, 3, 5]));
474+
assert_eq!(
475+
primes_geq::<3, 3>(10),
476+
Err(GenerationError::TooSmallSieveSize)
477+
);
478+
assert_eq!(primes_geq::<2, 2>(3), Err(GenerationError::SieveOverrun(4)));
479+
}
480+
481+
#[test]
482+
fn sanity_check_primes_lt() {
483+
{
484+
const P: Result<[u64; 5], GenerationError> = primes_lt::<5, 5>(20);
485+
assert_eq!(P, Ok([7, 11, 13, 17, 19]));
486+
}
487+
{
488+
const P: Result<[u64; 5], GenerationError> = primes_lt::<5, 5>(12);
489+
assert_eq!(P, Ok([2, 3, 5, 7, 11]));
490+
}
491+
{
492+
const P: Result<[u64; 1], GenerationError> = primes_lt::<1, 2>(3);
493+
assert_eq!(P, Ok([2]));
494+
}
495+
assert_eq!(primes_lt::<2, 2>(2), Err(GenerationError::TooSmallLimit));
496+
assert_eq!(
497+
primes_lt::<2, 2>(5),
498+
Err(GenerationError::TooSmallSieveSize)
499+
);
500+
assert_eq!(primes_lt::<0, 2>(3), Ok([]));
501+
assert_eq!(primes_lt::<3, 5>(4), Err(GenerationError::OutOfPrimes));
466502
}
467503

468504
#[test]

src/sieve.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,40 @@ use core::fmt;
44

55
use crate::isqrt;
66

7+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8+
pub(crate) struct SegmentedSieveError;
9+
10+
impl 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+
16+
#[cfg(feature = "std")]
17+
impl std::error::Error for SegmentedSieveError {}
18+
719
/// Uses the primalities of the first `N` integers in `base_sieve` to sieve the numbers in the range `[upper_limit - N, upper_limit)`.
820
/// Assumes that the base sieve contains the prime status of the `N` fist integers. The output is only meaningful
9-
/// for the numbers below `N^2`. Fails to compile if `N` is 0.
21+
/// for the numbers below `N^2`.
22+
///
23+
/// # Errors
24+
///
25+
/// Returns an error if `upper_limit` < `N`
1026
#[must_use = "the function only returns a new value and does not modify its inputs"]
1127
pub(crate) const fn sieve_segment<const N: usize>(
1228
base_sieve: &[bool; N],
1329
upper_limit: u64,
14-
) -> [bool; N] {
30+
) -> Result<[bool; N], SegmentedSieveError> {
1531
let mut segment_sieve = [true; N];
1632

17-
let lower_limit = upper_limit - N as u64;
33+
let lower_limit = match upper_limit.checked_sub(N as u64) {
34+
Some(diff) => diff,
35+
None => return Err(SegmentedSieveError),
36+
};
1837

1938
if lower_limit == 0 && N > 1 {
2039
// If the lower limit is 0 we can just return the base sieve.
21-
return *base_sieve;
40+
return Ok(*base_sieve);
2241
} else if lower_limit == 1 && N > 0 {
2342
// In case 1 is included in the upper sieve we need to treat it as a special case
2443
// since it's not a multiple of any prime in `base_sieve` even though it's not prime.
@@ -48,7 +67,7 @@ pub(crate) const fn sieve_segment<const N: usize>(
4867
i += 1;
4968
}
5069

51-
segment_sieve
70+
Ok(segment_sieve)
5271
}
5372

5473
/// Returns an array of size `N` that indicates which of the `N` largest integers smaller than `upper_limit` are prime.
@@ -152,12 +171,16 @@ pub const fn sieve_lt<const N: usize, const MEM: usize>(
152171
let base_sieve: [bool; MEM] = sieve();
153172

154173
// Use the result to sieve the higher range.
155-
let upper_sieve = sieve_segment(&base_sieve, upper_limit);
174+
let (offset, upper_sieve) = match sieve_segment(&base_sieve, upper_limit) {
175+
Ok(res) => (0, res),
176+
// The sieve contained more entries than there are non-negative numbers below the upper limit.
177+
Err(_) => ((MEM as u64 - upper_limit) as usize, base_sieve),
178+
};
156179

157-
let mut ans = [false; N];
158180
let mut i = 0;
181+
let mut ans = [false; N];
159182
while i < N {
160-
ans[N - 1 - i] = upper_sieve[MEM - 1 - i];
183+
ans[N - 1 - i] = upper_sieve[MEM - 1 - i - offset];
161184
i += 1;
162185
}
163186
Ok(ans)
@@ -331,7 +354,10 @@ pub const fn sieve_geq<const N: usize, const MEM: usize>(
331354

332355
let base_sieve: [bool; MEM] = sieve();
333356

334-
let upper_sieve = sieve_segment(&base_sieve, upper_limit);
357+
let upper_sieve = match sieve_segment(&base_sieve, upper_limit) {
358+
Ok(res) => res,
359+
Err(_) => panic!("this is already checked above"),
360+
};
335361

336362
let mut ans = [false; N];
337363
let mut i = 0;
@@ -397,13 +423,25 @@ macro_rules! sieve_segment {
397423

398424
#[cfg(test)]
399425
mod test {
426+
use crate::sieve::SegmentedSieveError;
427+
400428
use super::{sieve, sieve_segment};
401429

402430
#[test]
403431
fn test_consistency_of_sieve_segment() {
404-
const P: [bool; 10] = sieve_segment(&sieve(), 10);
405-
const PP: [bool; 10] = sieve_segment(&sieve(), 11);
432+
const P: [bool; 10] = match sieve_segment(&sieve(), 10) {
433+
Ok(s) => s,
434+
Err(_) => panic!(),
435+
};
436+
const PP: [bool; 10] = match sieve_segment(&sieve(), 11) {
437+
Ok(s) => s,
438+
Err(_) => panic!(),
439+
};
406440
assert_eq!(P, sieve());
407441
assert_eq!(PP, sieve::<11>()[1..]);
442+
assert_eq!(
443+
sieve_segment::<5>(&[false, false, true, true, false], 4),
444+
Err(SegmentedSieveError)
445+
);
408446
}
409447
}

0 commit comments

Comments
 (0)