Skip to content

Commit

Permalink
Change gen_range to accept Range types
Browse files Browse the repository at this point in the history
This adds support for `Rng::range(a..b)` and `Rng::range(a..=b)`,
replacing `Rng::range(a, b)`. `a` and `b` must now implement
`PartialEq`.

This is a breaking change. In most cases, replacing
`Rng::gen_range(a, b)` with `Rng::gen_range(a..b)` should be enough,
however type annotations may be necessary, and `a` and `b` can no longer
be references or SIMD types.

SIMD types are still supported by `UniformSampler::sample_single`.

Some clippy warnings were fixed as well.
  • Loading branch information
vks committed Aug 1, 2020
1 parent 2cdc6ca commit 870aea3
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 66 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,10 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.
- impl PartialEq+Eq for StdRng, SmallRng, and StepRng (#975)
- Added a `serde1` feature and added Serialize/Deserialize to `UniformInt` and `WeightedIndex` (#974)

### Changes
- `gen_range(a, b)` was replaced with `gen_range(a..b)`, and `gen_range(a..=b)`
is supported (#744, #1003). Note that `a` and `b` can no longer be references.

## [0.7.3] - 2020-01-10
### Fixes
- The `Bernoulli` distribution constructors now reports an error on NaN and on
Expand Down
4 changes: 2 additions & 2 deletions benches/distributions.rs
Expand Up @@ -197,7 +197,7 @@ macro_rules! gen_range_int {
let mut high = $high;
let mut accum: $ty = 0;
for _ in 0..RAND_BENCH_N {
accum = accum.wrapping_add(rng.gen_range($low, high));
accum = accum.wrapping_add(rng.gen_range($low..high));
// force recalculation of range each time
high = high.wrapping_add(1) & std::$ty::MAX;
}
Expand Down Expand Up @@ -237,7 +237,7 @@ macro_rules! gen_range_float {
let mut low = $low;
let mut accum: $ty = 0.0;
for _ in 0..RAND_BENCH_N {
accum += rng.gen_range(low, high);
accum += rng.gen_range(low..high);
// force recalculation of range each time
low += 0.9;
high += 1.1;
Expand Down
21 changes: 10 additions & 11 deletions src/distributions/mod.rs
Expand Up @@ -11,8 +11,8 @@
//!
//! This module is the home of the [`Distribution`] trait and several of its
//! implementations. It is the workhorse behind some of the convenient
//! functionality of the [`Rng`] trait, e.g. [`Rng::gen`], [`Rng::gen_range`] and
//! of course [`Rng::sample`].
//! functionality of the [`Rng`] trait, e.g. [`Rng::gen`] and of course
//! [`Rng::sample`].
//!
//! Abstractly, a [probability distribution] describes the probability of
//! occurrence of each value in its sample space.
Expand Down Expand Up @@ -54,16 +54,16 @@
//! space to be specified as an arbitrary range within its target type `T`.
//! Both [`Standard`] and [`Uniform`] are in some sense uniform distributions.
//!
//! Values may be sampled from this distribution using [`Rng::gen_range`] or
//! Values may be sampled from this distribution using [`Rng::sample(Range)`] or
//! by creating a distribution object with [`Uniform::new`],
//! [`Uniform::new_inclusive`] or `From<Range>`. When the range limits are not
//! known at compile time it is typically faster to reuse an existing
//! distribution object than to call [`Rng::gen_range`].
//! `Uniform` object than to call [`Rng::sample(Range)`].
//!
//! User types `T` may also implement `Distribution<T>` for [`Uniform`],
//! although this is less straightforward than for [`Standard`] (see the
//! documentation in the [`uniform`] module. Doing so enables generation of
//! values of type `T` with [`Rng::gen_range`].
//! documentation in the [`uniform`] module). Doing so enables generation of
//! values of type `T` with [`Rng::sample(Range)`].
//!
//! ## Open and half-open ranges
//!
Expand Down Expand Up @@ -315,11 +315,10 @@ where
/// multiplicative method: `(rng.gen::<$uty>() >> N) as $ty * (ε/2)`.
///
/// See also: [`Open01`] which samples from `(0, 1)`, [`OpenClosed01`] which
/// samples from `(0, 1]` and `Rng::gen_range(0, 1)` which also samples from
/// `[0, 1)`. Note that `Open01` and `gen_range` (which uses [`Uniform`]) use
/// transmute-based methods which yield 1 bit less precision but may perform
/// faster on some architectures (on modern Intel CPUs all methods have
/// approximately equal performance).
/// samples from `(0, 1]` and `Rng::gen_range(0..1)` which also samples from
/// `[0, 1)`. Note that `Open01` uses transmute-based methods which yield 1 bit
/// less precision but may perform faster on some architectures (on modern Intel
/// CPUs all methods have approximately equal performance).
///
/// [`Uniform`]: uniform::Uniform
#[derive(Clone, Copy, Debug)]
Expand Down
99 changes: 76 additions & 23 deletions src/distributions/uniform.rs
@@ -1,4 +1,4 @@
// Copyright 2018 Developers of the Rand project.
// Copyright 2018-2020 Developers of the Rand project.
// Copyright 2017 The Rust Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
Expand Down Expand Up @@ -34,7 +34,7 @@
//! let side = Uniform::new(-10.0, 10.0);
//!
//! // sample between 1 and 10 points
//! for _ in 0..rng.gen_range(1, 11) {
//! for _ in 0..rng.gen_range(1..=10) {
//! // sample a point from the square with sides -10 - 10 in two dimensions
//! let (x, y) = (rng.sample(side), rng.sample(side));
//! println!("Point: {}, {}", x, y);
Expand Down Expand Up @@ -105,11 +105,12 @@

#[cfg(not(feature = "std"))] use core::time::Duration;
#[cfg(feature = "std")] use std::time::Duration;
use core::ops::{Range, RangeInclusive};

use crate::distributions::float::IntoFloat;
use crate::distributions::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, WideningMultiply};
use crate::distributions::Distribution;
use crate::Rng;
use crate::{Rng, RngCore};

#[cfg(not(feature = "std"))]
#[allow(unused_imports)] // rustc doesn't detect that this is actually used
Expand All @@ -124,7 +125,8 @@ use serde::{Serialize, Deserialize};
///
/// [`Uniform::new`] and [`Uniform::new_inclusive`] construct a uniform
/// distribution sampling from the given range; these functions may do extra
/// work up front to make sampling of multiple values faster.
/// work up front to make sampling of multiple values faster. If only one sample
/// from the range is required, [`Rng::gen_range`] can be more efficient.
///
/// When sampling from a constant range, many calculations can happen at
/// compile-time and all methods should be fast; for floating-point ranges and
Expand All @@ -139,27 +141,35 @@ use serde::{Serialize, Deserialize};
/// are of lower quality than the high bits.
///
/// Implementations must sample in `[low, high)` range for
/// `Uniform::new(low, high)`, i.e., excluding `high`. In particular care must
/// `Uniform::new(low, high)`, i.e., excluding `high`. In particular, care must
/// be taken to ensure that rounding never results values `< low` or `>= high`.
///
/// # Example
///
/// ```
/// use rand::distributions::{Distribution, Uniform};
///
/// fn main() {
/// let between = Uniform::from(10..10000);
/// let mut rng = rand::thread_rng();
/// let mut sum = 0;
/// for _ in 0..1000 {
/// sum += between.sample(&mut rng);
/// }
/// println!("{}", sum);
/// let between = Uniform::from(10..10000);
/// let mut rng = rand::thread_rng();
/// let mut sum = 0;
/// for _ in 0..1000 {
/// sum += between.sample(&mut rng);
/// }
/// println!("{}", sum);
/// ```
///
/// For a single sample, [`Rng::gen_range`] may be prefered:
///
/// ```
/// use rand::Rng;
///
/// let mut rng = rand::thread_rng();
/// println!("{}", rng.gen_range(0..10));
/// ```
///
/// [`new`]: Uniform::new
/// [`new_inclusive`]: Uniform::new_inclusive
/// [`Rng::gen_range`]: Rng::gen_range
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct Uniform<X: SampleUniform>(X::Sampler);
Expand Down Expand Up @@ -287,18 +297,19 @@ pub trait UniformSampler: Sized {
}
}

impl<X: SampleUniform> From<::core::ops::Range<X>> for Uniform<X> {
impl<X: SampleUniform> From<Range<X>> for Uniform<X> {
fn from(r: ::core::ops::Range<X>) -> Uniform<X> {
Uniform::new(r.start, r.end)
}
}

impl<X: SampleUniform> From<::core::ops::RangeInclusive<X>> for Uniform<X> {
impl<X: SampleUniform> From<RangeInclusive<X>> for Uniform<X> {
fn from(r: ::core::ops::RangeInclusive<X>) -> Uniform<X> {
Uniform::new_inclusive(r.start(), r.end())
}
}


/// Helper trait similar to [`Borrow`] but implemented
/// only for SampleUniform and references to SampleUniform in
/// order to resolve ambiguity issues.
Expand Down Expand Up @@ -327,6 +338,43 @@ where Borrowed: SampleUniform
}
}

/// Range that supports generating a single sample efficiently.
///
/// Any type implementing this trait can be used to specify the sampled range
/// for `Rng::gen_range`.
pub trait SampleRange<T> {
/// Generate a sample from the given range.
fn sample_single<R: RngCore + ?Sized>(self, rng: &mut R) -> T;

/// Check whether the range is empty.
fn is_empty(&self) -> bool;
}

impl<T: SampleUniform + PartialOrd> SampleRange<T> for Range<T> {
#[inline]
fn sample_single<R: RngCore + ?Sized>(self, rng: &mut R) -> T {
T::Sampler::sample_single(self.start, self.end, rng)
}

#[inline]
fn is_empty(&self) -> bool {
!(self.start < self.end)
}
}

impl<T: SampleUniform + PartialOrd> SampleRange<T> for RangeInclusive<T> {
#[inline]
fn sample_single<R: RngCore + ?Sized>(self, rng: &mut R) -> T {
T::Sampler::sample_single_inclusive(self.start(), self.end(), rng)
}

#[inline]
fn is_empty(&self) -> bool {
!(self.start() <= self.end())
}
}


////////////////////////////////////////////////////////////////////////////////

// What follows are all back-ends.
Expand Down Expand Up @@ -425,7 +473,7 @@ macro_rules! uniform_int_impl {
};

UniformInt {
low: low,
low,
// These are really $unsigned values, but store as $ty:
range: range as $ty,
z: ints_to_reject as $unsigned as $ty,
Expand Down Expand Up @@ -570,7 +618,7 @@ macro_rules! uniform_simd_int_impl {
let zone = unsigned_max - ints_to_reject;

UniformInt {
low: low,
low,
// These are really $unsigned values, but store as $ty:
range: range.cast(),
z: zone.cast(),
Expand Down Expand Up @@ -985,7 +1033,7 @@ impl UniformSampler for UniformDuration {
max_nanos,
secs,
} => {
// constant folding means this is at least as fast as `gen_range`
// constant folding means this is at least as fast as `Rng::sample(Range)`
let nano_range = Uniform::new(0, 1_000_000_000);
loop {
let s = secs.sample(rng);
Expand Down Expand Up @@ -1112,7 +1160,7 @@ mod tests {
}

for _ in 0..1000 {
let v: $ty = rng.gen_range(low, high);
let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng);
assert!($le(low, v) && $lt(v, high));
}
}
Expand Down Expand Up @@ -1194,7 +1242,8 @@ mod tests {
assert!(low_scalar <= v && v < high_scalar);
let v = rng.sample(my_incl_uniform).extract(lane);
assert!(low_scalar <= v && v <= high_scalar);
let v = rng.gen_range(low, high).extract(lane);
let v = <$ty as SampleUniform>::Sampler
::sample_single(low, high, &mut rng).extract(lane);
assert!(low_scalar <= v && v < high_scalar);
}

Expand All @@ -1205,7 +1254,9 @@ mod tests {

assert_eq!(zero_rng.sample(my_uniform).extract(lane), low_scalar);
assert_eq!(zero_rng.sample(my_incl_uniform).extract(lane), low_scalar);
assert_eq!(zero_rng.gen_range(low, high).extract(lane), low_scalar);
assert_eq!(<$ty as SampleUniform>::Sampler
::sample_single(low, high, &mut zero_rng)
.extract(lane), low_scalar);
assert!(max_rng.sample(my_uniform).extract(lane) < high_scalar);
assert!(max_rng.sample(my_incl_uniform).extract(lane) <= high_scalar);

Expand All @@ -1218,7 +1269,9 @@ mod tests {
(-1i64 << $bits_shifted) as u64,
);
assert!(
lowering_max_rng.gen_range(low, high).extract(lane) < high_scalar
<$ty as SampleUniform>::Sampler
::sample_single(low, high, &mut lowering_max_rng)
.extract(lane) < high_scalar
);
}
}
Expand Down Expand Up @@ -1266,7 +1319,7 @@ mod tests {
use std::panic::catch_unwind;
fn range<T: SampleUniform>(low: T, high: T) {
let mut rng = crate::test::rng(253);
rng.gen_range(low, high);
T::Sampler::sample_single(low, high, &mut rng);
}

macro_rules! t {
Expand Down

0 comments on commit 870aea3

Please sign in to comment.