Skip to content

Commit

Permalink
distributions/uniform: fix panic in gen_range(0..=MAX)
Browse files Browse the repository at this point in the history
This commit fixes a panic when generating a single sample in an
inclusive range that spans the entire integer range, eg for u8:
```rust
rng.gen_range(0..=u8::MAX)
// panicked at 'attempt to add with overflow', src/distributions/uniform.rs:529:42
```
[Playground example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&code=use%20rand%3A%3ARng%3B%0A%0Afn%20main()%20%7B%0A%20%20%20%20rand%3A%3Athread_rng().gen_range(0u8..%3D255u8)%3B%0A%7D).

The cause is a discrepancy between the "single sample" and the "many
samples" codepaths:
```rust
// Ok
UniformSampler::new_inclusive(u8::MIN, u8::MAX).sample(&mut rng);
// Panic
UniformSampler::sample_single_inclusive(u8::MIN, u8::MAX, &mut rng);
```
In `sample`, a `range` of 0 is interpreted to mean "sample from the
whole range".
In `sample_range_inclusive`, no check is performed, which leads to
overflow when computing the `ints_to_reject`.

**Testing**
- Added a test case.
- Old code panics, new code passes.
  • Loading branch information
mautier committed Jan 12, 2021
1 parent bda9974 commit 4e8c7a4
Showing 1 changed file with 11 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/distributions/uniform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,12 @@ macro_rules! uniform_int_impl {
let high = *high_b.borrow();
assert!(low <= high, "UniformSampler::sample_single_inclusive: low > high");
let range = high.wrapping_sub(low).wrapping_add(1) as $unsigned as $u_large;
// If the above resulted in wrap-around to 0, the range is $ty::MIN..=$ty::MAX,
// and any integer will do.
if range == 0 {
return rng.gen();
}

let zone = if ::core::$unsigned::MAX <= ::core::u16::MAX as $unsigned {
// Using a modulus is faster than the approximation for
// i8 and i16. I suppose we trade the cost of one
Expand Down Expand Up @@ -1235,6 +1241,11 @@ mod tests {
let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng);
assert!($le(low, v) && $lt(v, high));
}

for _ in 0..1000 {
let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive(low, high, &mut rng);
assert!($le(low, v) && $le(v, high));
}
}
}};

Expand Down

0 comments on commit 4e8c7a4

Please sign in to comment.