Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gen_range(a, b) -> gen_range(a..b) and gen_range(a..=b) #1003

Merged
merged 2 commits into from Aug 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
132 changes: 108 additions & 24 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 @@ -268,20 +278,38 @@ pub trait UniformSampler: Sized {
let uniform: Self = UniformSampler::new(low, high);
uniform.sample(rng)
}

/// Sample a single value uniformly from a range with inclusive lower bound
/// and inclusive upper bound `[low, high]`.
///
/// By default this is implemented using
/// `UniformSampler::new_inclusive(low, high).sample(rng)`. However, for
/// some types more optimal implementations for single usage may be provided
/// via this method.
/// Results may not be identical.
fn sample_single_inclusive<R: Rng + ?Sized, B1, B2>(low: B1, high: B2, rng: &mut R)
-> Self::X
where B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized
{
let uniform: Self = UniformSampler::new_inclusive(low, high);
uniform.sample(rng)
}
}

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 @@ -310,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 @@ -408,13 +473,14 @@ 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,
}
}

#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
let range = self.range as $unsigned as $u_large;
if range > 0 {
Expand All @@ -433,6 +499,7 @@ macro_rules! uniform_int_impl {
}
}

#[inline]
fn sample_single<R: Rng + ?Sized, B1, B2>(low_b: B1, high_b: B2, rng: &mut R) -> Self::X
where
B1: SampleBorrow<Self::X> + Sized,
Expand All @@ -441,7 +508,19 @@ macro_rules! uniform_int_impl {
let low = *low_b.borrow();
let high = *high_b.borrow();
assert!(low < high, "UniformSampler::sample_single: low >= high");
let range = high.wrapping_sub(low) as $unsigned as $u_large;
Self::sample_single_inclusive(low, high - 1, rng)
}

#[inline]
fn sample_single_inclusive<R: Rng + ?Sized, B1, B2>(low_b: B1, high_b: B2, rng: &mut R) -> Self::X
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
let low = *low_b.borrow();
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;
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 @@ -539,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 @@ -954,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 @@ -1081,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 @@ -1163,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 @@ -1174,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 @@ -1187,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 @@ -1235,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