diff --git a/CHANGELOG.md b/CHANGELOG.md index febfc7ad830..0a859752aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/benches/distributions.rs b/benches/distributions.rs index c42c71c9f1b..9ab74cf563d 100644 --- a/benches/distributions.rs +++ b/benches/distributions.rs @@ -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; } @@ -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; diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 525e29eddd6..001084e985c 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -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. @@ -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`. 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` 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 //! @@ -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)] diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index a8fb0dc3f27..480b859644c 100644 --- a/src/distributions/uniform.rs +++ b/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 = high`. /// /// # Example @@ -147,19 +149,27 @@ use serde::{Serialize, Deserialize}; /// ``` /// 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::Sampler); @@ -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(low: B1, high: B2, rng: &mut R) + -> Self::X + where B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized + { + let uniform: Self = UniformSampler::new_inclusive(low, high); + uniform.sample(rng) + } } -impl From<::core::ops::Range> for Uniform { +impl From> for Uniform { fn from(r: ::core::ops::Range) -> Uniform { Uniform::new(r.start, r.end) } } -impl From<::core::ops::RangeInclusive> for Uniform { +impl From> for Uniform { fn from(r: ::core::ops::RangeInclusive) -> Uniform { 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. @@ -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 { + /// Generate a sample from the given range. + fn sample_single(self, rng: &mut R) -> T; + + /// Check whether the range is empty. + fn is_empty(&self) -> bool; +} + +impl SampleRange for Range { + #[inline] + fn sample_single(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 SampleRange for RangeInclusive { + #[inline] + fn sample_single(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. @@ -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(&self, rng: &mut R) -> Self::X { let range = self.range as $unsigned as $u_large; if range > 0 { @@ -433,6 +499,7 @@ macro_rules! uniform_int_impl { } } + #[inline] fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Self::X where B1: SampleBorrow + Sized, @@ -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(low_b: B1, high_b: B2, rng: &mut R) -> Self::X + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + 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 @@ -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(), @@ -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); @@ -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)); } } @@ -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); } @@ -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); @@ -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 ); } } @@ -1235,7 +1319,7 @@ mod tests { use std::panic::catch_unwind; fn range(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 { diff --git a/src/rng.rs b/src/rng.rs index 391b6dd0e83..80f61a4ce49 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -10,7 +10,7 @@ //! [`Rng`] trait use rand_core::{Error, RngCore}; -use crate::distributions::uniform::{SampleBorrow, SampleUniform, UniformSampler}; +use crate::distributions::uniform::{SampleRange, SampleUniform}; use crate::distributions::{self, Distribution, Standard}; use core::num::Wrapping; use core::{mem, slice}; @@ -93,16 +93,17 @@ pub trait Rng: RngCore { Standard.sample(self) } - /// Generate a random value in the range [`low`, `high`), i.e. inclusive of - /// `low` and exclusive of `high`. + /// Generate a random value in the given range. /// /// This function is optimised for the case that only a single sample is /// made from the given range. See also the [`Uniform`] distribution /// type which may be faster if sampling from the same range repeatedly. /// + /// Only `gen_range(low..high)` and `gen_range(low..=high)` are supported. + /// /// # Panics /// - /// Panics if `low >= high`. + /// Panics if the range is empty. /// /// # Example /// @@ -110,19 +111,26 @@ pub trait Rng: RngCore { /// use rand::{thread_rng, Rng}; /// /// let mut rng = thread_rng(); - /// let n: u32 = rng.gen_range(0, 10); + /// + /// // Exclusive range + /// let n: u32 = rng.gen_range(0..10); /// println!("{}", n); - /// let m: f64 = rng.gen_range(-40.0f64, 1.3e5f64); + /// let m: f64 = rng.gen_range(-40.0..1.3e5); /// println!("{}", m); + /// + /// // Inclusive range + /// let n: u32 = rng.gen_range(0..=10); + /// println!("{}", n); /// ``` /// /// [`Uniform`]: distributions::uniform::Uniform - fn gen_range(&mut self, low: B1, high: B2) -> T + fn gen_range(&mut self, range: R) -> T where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, + T: SampleUniform, + R: SampleRange { - T::Sampler::sample_single(low, high, self) + assert!(!range.is_empty(), "cannot sample empty range"); + range.sample_single(self) } /// Sample a new value, using the given distribution. @@ -472,23 +480,38 @@ mod test { } #[test] - fn test_gen_range() { + fn test_gen_range_int() { let mut r = rng(101); for _ in 0..1000 { - let a = r.gen_range(-4711, 17); + let a = r.gen_range(-4711..17); assert!(a >= -4711 && a < 17); - let a = r.gen_range(-3i8, 42); + let a = r.gen_range(-3i8..42); assert!(a >= -3i8 && a < 42i8); - let a = r.gen_range(&10u16, 99); + let a: u16 = r.gen_range(10..99); assert!(a >= 10u16 && a < 99u16); - let a = r.gen_range(-100i32, &2000); + let a = r.gen_range(-100i32..2000); assert!(a >= -100i32 && a < 2000i32); - let a = r.gen_range(&12u32, &24u32); - assert!(a >= 12u32 && a < 24u32); + let a: u32 = r.gen_range(12..=24); + assert!(a >= 12u32 && a <= 24u32); + + assert_eq!(r.gen_range(0u32..1), 0u32); + assert_eq!(r.gen_range(-12i64..-11), -12i64); + assert_eq!(r.gen_range(3_000_000..3_000_001), 3_000_000); + } + } - assert_eq!(r.gen_range(0u32, 1), 0u32); - assert_eq!(r.gen_range(-12i64, -11), -12i64); - assert_eq!(r.gen_range(3_000_000, 3_000_001), 3_000_000); + #[test] + fn test_gen_range_float() { + let mut r = rng(101); + for _ in 0..1000 { + let a = r.gen_range(-4.5..1.7); + assert!(a >= -4.5 && a < 1.7); + let a = r.gen_range(-1.1..=-0.3); + assert!(a >= -1.1 && a <= -0.3); + + assert_eq!(r.gen_range(0.0f32..=0.0), 0.); + assert_eq!(r.gen_range(-11.0..=-11.0), -11.); + assert_eq!(r.gen_range(3_000_000.0..=3_000_000.0), 3_000_000.); } } @@ -496,14 +519,14 @@ mod test { #[should_panic] fn test_gen_range_panic_int() { let mut r = rng(102); - r.gen_range(5, -2); + r.gen_range(5..-2); } #[test] #[should_panic] fn test_gen_range_panic_usize() { let mut r = rng(103); - r.gen_range(5, 2); + r.gen_range(5..2); } #[test] @@ -522,7 +545,7 @@ mod test { let mut r = &mut rng as &mut dyn RngCore; r.next_u32(); r.gen::(); - assert_eq!(r.gen_range(0, 1), 0); + assert_eq!(r.gen_range(0..1), 0); let _c: u8 = Standard.sample(&mut r); } @@ -534,7 +557,7 @@ mod test { let mut r = Box::new(rng) as Box; r.next_u32(); r.gen::(); - assert_eq!(r.gen_range(0, 1), 0); + assert_eq!(r.gen_range(0..1), 0); let _c: u8 = Standard.sample(&mut r); } diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 6ec62c763dd..201e1475748 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -119,6 +119,6 @@ mod test { use crate::Rng; let mut r = crate::thread_rng(); r.gen::(); - assert_eq!(r.gen_range(0, 1), 0); + assert_eq!(r.gen_range(0..1), 0); } } diff --git a/src/seq/index.rs b/src/seq/index.rs index 8638b86bcfd..0ab5aec20ef 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -274,7 +274,7 @@ where R: Rng + ?Sized { debug_assert!(amount <= length); let mut indices = Vec::with_capacity(amount as usize); for j in length - amount..length { - let t = rng.gen_range(0, j + 1); + let t = rng.gen_range(0..=j); if floyd_shuffle { if let Some(pos) = indices.iter().position(|&x| x == t) { indices.insert(pos, j); @@ -290,7 +290,7 @@ where R: Rng + ?Sized { // Reimplement SliceRandom::shuffle with smaller indices for i in (1..amount).rev() { // invariant: elements with index > i have been locked in place. - indices.swap(i as usize, rng.gen_range(0, i + 1) as usize); + indices.swap(i as usize, rng.gen_range(0..=i) as usize); } } IndexVec::from(indices) @@ -314,7 +314,7 @@ where R: Rng + ?Sized { let mut indices: Vec = Vec::with_capacity(length as usize); indices.extend(0..length); for i in 0..amount { - let j: u32 = rng.gen_range(i, length); + let j: u32 = rng.gen_range(i..length); indices.swap(i as usize, j as usize); } indices.truncate(amount as usize); diff --git a/src/seq/mod.rs b/src/seq/mod.rs index c4ffec608f0..13f2311bc3c 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -520,9 +520,9 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator #[inline] fn gen_index(rng: &mut R, ubound: usize) -> usize { if ubound <= (core::u32::MAX as usize) { - rng.gen_range(0, ubound as u32) as usize + rng.gen_range(0..ubound as u32) as usize } else { - rng.gen_range(0, ubound) + rng.gen_range(0..ubound) } }