From 2d29e2ce852af7248f8eda1ba5aa824612ec6cfe Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 5 Aug 2017 18:36:57 +0100 Subject: [PATCH] Black-magic alternative to Range supporting custom parameters. Props to @sgrif or this probably wouldn't have happened. https://github.com/rust-lang/rust/issues/20041#issuecomment-320455707 --- src/distributions/mod.rs | 1 + src/distributions/range2.rs | 360 ++++++++++++++++++++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 src/distributions/range2.rs diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 1794ac490b..89bdf252c6 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -30,6 +30,7 @@ pub use self::exponential::Exp; mod default; mod uniform; pub mod range; +pub mod range2; pub mod gamma; pub mod normal; pub mod exponential; diff --git a/src/distributions/range2.rs b/src/distributions/range2.rs new file mode 100644 index 0000000000..a6aee82cc9 --- /dev/null +++ b/src/distributions/range2.rs @@ -0,0 +1,360 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Alternative design for `Range`. +//! +//! TODO: decide whether to replace the old `range` module with this. +//! Advantage: float ranges don't have to store a "zone" parameter. +//! Advantage: custom implementations can store extra parameters. +//! Possible advantage: easier implementations for custom types written in +//! terms of implementations of other types. +//! Disadvantage: complex? +//! +//! This is *almost* like having separate `RangeInt`, `RangeFloat`, +//! etc. (or just `RangeI32`, etc.) types, each implementing `Distribution`, +//! but it adds some magic to support generic `range` and `new_range` methods. + +use std::num::Wrapping as w; + +use Rng; +use distributions::{Distribution, Uniform01, Rand}; + +/// Generate a random value in the range [`low`, `high`). +/// +/// This is a convenience wrapper around `Range`. If this function will be called +/// repeatedly with the same arguments, one should use `Range`, as that will +/// amortize the computations that allow for perfect uniformity. +/// +/// # Panics +/// +/// Panics if `low >= high`. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::range2::range; +/// +/// let mut rng = rand::thread_rng(); +/// let n: u32 = range(0, 10, &mut rng); +/// println!("{}", n); +/// let m: f64 = range(-40.0f64, 1.3e5f64, &mut rng); +/// println!("{}", m); +/// ``` +pub fn range(low: X, high: X, rng: &mut R) -> X { + assert!(low < high, "distributions::range called with low >= high"); + Range { inner: X::T::new(low, high) }.sample(rng) +} + +/// Convenience function to construct a `Range` +pub fn new_range(low: X, high: X) -> Range { + assert!(low < high, "new_range called with `low >= high`"); + Range { inner: RangeImpl::new(low, high) } +} + +/// Sample values uniformly between two bounds. +/// +/// This gives a uniform distribution (assuming the RNG used to sample +/// it is itself uniform & the `RangeImpl` implementation is correct), +/// even for edge cases like `low = 0u8`, +/// `high = 170u8`, for which a naive modulo operation would return +/// numbers less than 85 with double the probability to those greater +/// than 85. +/// +/// Types should attempt to sample in `[low, high)`, i.e., not +/// including `high`, but this may be very difficult. All the +/// primitive integer types satisfy this property, and the float types +/// normally satisfy it, but rounding may mean `high` can occur. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::Distribution; +/// use rand::distributions::range2::new_range; +/// +/// fn main() { +/// let between = new_range(10, 10000); +/// let mut rng = rand::thread_rng(); +/// let mut sum = 0; +/// for _ in 0..1000 { +/// sum += between.sample(&mut rng); +/// } +/// println!("{}", sum); +/// } +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Range { + inner: T, +} + +impl Range { + /// Create a new `Range` instance that samples uniformly from + /// `[low, high)`. Panics if `low >= high`. + pub fn new(low: T::X, high: T::X) -> Range { + assert!(low < high, "Range::new called with `low >= high`"); + Range { inner: RangeImpl::new(low, high) } + } +} + +impl Distribution for Range { + fn sample(&self, rng: &mut R) -> T::X { + self.inner.sample(rng) + } +} + +/// Helper trait for creating implementations of `RangeImpl`. +pub trait SampleRange: PartialOrd+Sized { + type T: RangeImpl; +} + +/// Helper trait handling actual range sampling. +pub trait RangeImpl { + /// The type sampled by this implementation. + type X: PartialOrd; + + /// Construct self. + /// + /// This should not be called directly. `Range::new` asserts that + /// `low < high` before calling this. + fn new(low: Self::X, high: Self::X) -> Self; + + /// Sample a value. + fn sample(&self, rng: &mut R) -> Self::X; +} + +/// Implementation of `RangeImpl` for integer types. +#[derive(Clone, Copy, Debug)] +pub struct RangeInt { + low: X, + range: X, + zone: X, +} + +macro_rules! range_int_impl { + ($ty:ty, $unsigned:ident) => { + impl SampleRange for $ty { + type T = RangeInt<$ty>; + } + + impl RangeImpl for RangeInt<$ty> { + // we play free and fast with unsigned vs signed here + // (when $ty is signed), but that's fine, since the + // contract of this macro is for $ty and $unsigned to be + // "bit-equal", so casting between them is a no-op & a + // bijection. + + type X = $ty; + + fn new(low: Self::X, high: Self::X) -> Self { + let range = (w(high as $unsigned) - w(low as $unsigned)).0; + let unsigned_max: $unsigned = ::std::$unsigned::MAX; + + // this is the largest number that fits into $unsigned + // that `range` divides evenly, so, if we've sampled + // `n` uniformly from this region, then `n % range` is + // uniform in [0, range) + let zone = unsigned_max - unsigned_max % range; + + RangeInt { + low: low, + range: range as $ty, + zone: zone as $ty + } + } + + fn sample(&self, rng: &mut R) -> Self::X { + use $crate::distributions::uniform; + loop { + // rejection sample + let v: $unsigned = uniform(rng); + // until we find something that fits into the + // region which self.range evenly divides (this will + // be uniformly distributed) + if v < self.zone as $unsigned { + // and return it, with some adjustments + return (w(self.low) + w((v % self.range as $unsigned) as $ty)).0; + } + } + } + } + } +} + +range_int_impl! { i8, u8 } +range_int_impl! { i16, u16 } +range_int_impl! { i32, u32 } +range_int_impl! { i64, u64 } +range_int_impl! { isize, usize } +range_int_impl! { u8, u8 } +range_int_impl! { u16, u16 } +range_int_impl! { u32, u32 } +range_int_impl! { u64, u64 } +range_int_impl! { usize, usize } + +/// Implementation of `RangeImpl` for float types. +#[derive(Clone, Copy, Debug)] +pub struct RangeFloat { + low: X, + range: X, +} + +macro_rules! range_float_impl { + ($ty:ty) => { + impl SampleRange for $ty { + type T = RangeFloat<$ty>; + } + + impl RangeImpl for RangeFloat<$ty> { + type X = $ty; + + fn new(low: Self::X, high: Self::X) -> Self { + RangeFloat { + low: low, + range: high - low, + } + } + + fn sample(&self, rng: &mut R) -> Self::X { + let x: $ty = Rand::rand(rng, Uniform01); + self.low + self.range * x + } + } + } +} + +range_float_impl! { f32 } +range_float_impl! { f64 } + +#[cfg(test)] +mod tests { + use {Rng, thread_rng}; + use distributions::Rand; + use distributions::range2::{Range, range, new_range, RangeImpl, RangeFloat}; + + #[test] + fn test_fn_range() { + let mut r = thread_rng(); + for _ in 0..1000 { + let a = range(-3, 42, &mut r); + assert!(a >= -3 && a < 42); + assert_eq!(range(0, 1, &mut r), 0); + assert_eq!(range(-12, -11, &mut r), -12); + } + + for _ in 0..1000 { + let a = range(10, 42, &mut r); + assert!(a >= 10 && a < 42); + assert_eq!(range(0, 1, &mut r), 0); + assert_eq!(range(3_000_000, 3_000_001, &mut r), 3_000_000); + } + } + + #[test] + #[should_panic] + fn test_fn_range_panic_int() { + let mut r = thread_rng(); + range(5, -2, &mut r); + } + + #[test] + #[should_panic] + fn test_fn_range_panic_usize() { + let mut r = thread_rng(); + range(5, 2, &mut r); + } + + #[should_panic] + #[test] + fn test_range_bad_limits_equal() { + new_range(10, 10); + } + #[should_panic] + #[test] + fn test_range_bad_limits_flipped() { + new_range(10, 5); + } + + #[test] + fn test_integers() { + let mut rng = ::test::rng(); + macro_rules! t { + ($($ty:ident),*) => {{ + $( + let v: &[($ty, $ty)] = &[(0, 10), + (10, 127), + (::std::$ty::MIN, ::std::$ty::MAX)]; + for &(low, high) in v.iter() { + let range = new_range(low, high); + for _ in 0..1000 { + let v: $ty = Rand::rand(&mut rng, range); + assert!(low <= v && v < high); + } + } + )* + }} + } + t!(i8, i16, i32, i64, isize, + u8, u16, u32, u64, usize) + } + + #[test] + fn test_floats() { + let mut rng = ::test::rng(); + macro_rules! t { + ($($ty:ty),*) => {{ + $( + let v: &[($ty, $ty)] = &[(0.0, 100.0), + (-1e35, -1e25), + (1e-35, 1e-25), + (-1e35, 1e35)]; + for &(low, high) in v.iter() { + let range = new_range(low, high); + for _ in 0..1000 { + let v: $ty = Rand::rand(&mut rng, range); + assert!(low <= v && v < high); + } + } + )* + }} + } + + t!(f32, f64) + } + + #[test] + fn test_custom_range() { + #[derive(Clone, Copy, PartialEq, PartialOrd)] + struct MyF32 { + x: f32, + } + #[derive(Clone, Copy, Debug)] + struct RangeMyF32 { + inner: RangeFloat, + } + impl RangeImpl for RangeMyF32 { + type X = MyF32; + fn new(low: Self::X, high: Self::X) -> Self { + RangeMyF32 { + inner: RangeFloat::::new(low.x, high.x), + } + } + fn sample(&self, rng: &mut R) -> Self::X { + MyF32 { x: self.inner.sample(rng) } + } + } + + let (low, high) = (MyF32{ x: 17.0f32 }, MyF32{ x: 22.0f32 }); + let range = Range::::new(low, high); + let mut rng = ::test::rng(); + for _ in 0..100 { + let x = MyF32::rand(&mut rng, range); + assert!(low <= x && x < high); + } + } +}