Skip to content

Commit 2d29e2c

Browse files
committed
Black-magic alternative to Range supporting custom parameters.
Props to @sgrif or this probably wouldn't have happened. rust-lang/rust#20041 (comment)
1 parent 549febb commit 2d29e2c

File tree

2 files changed

+361
-0
lines changed

2 files changed

+361
-0
lines changed

src/distributions/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub use self::exponential::Exp;
3030
mod default;
3131
mod uniform;
3232
pub mod range;
33+
pub mod range2;
3334
pub mod gamma;
3435
pub mod normal;
3536
pub mod exponential;

src/distributions/range2.rs

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Alternative design for `Range`.
12+
//!
13+
//! TODO: decide whether to replace the old `range` module with this.
14+
//! Advantage: float ranges don't have to store a "zone" parameter.
15+
//! Advantage: custom implementations can store extra parameters.
16+
//! Possible advantage: easier implementations for custom types written in
17+
//! terms of implementations of other types.
18+
//! Disadvantage: complex?
19+
//!
20+
//! This is *almost* like having separate `RangeInt<T>`, `RangeFloat<T>`,
21+
//! etc. (or just `RangeI32`, etc.) types, each implementing `Distribution`,
22+
//! but it adds some magic to support generic `range` and `new_range` methods.
23+
24+
use std::num::Wrapping as w;
25+
26+
use Rng;
27+
use distributions::{Distribution, Uniform01, Rand};
28+
29+
/// Generate a random value in the range [`low`, `high`).
30+
///
31+
/// This is a convenience wrapper around `Range`. If this function will be called
32+
/// repeatedly with the same arguments, one should use `Range`, as that will
33+
/// amortize the computations that allow for perfect uniformity.
34+
///
35+
/// # Panics
36+
///
37+
/// Panics if `low >= high`.
38+
///
39+
/// # Example
40+
///
41+
/// ```rust
42+
/// use rand::distributions::range2::range;
43+
///
44+
/// let mut rng = rand::thread_rng();
45+
/// let n: u32 = range(0, 10, &mut rng);
46+
/// println!("{}", n);
47+
/// let m: f64 = range(-40.0f64, 1.3e5f64, &mut rng);
48+
/// println!("{}", m);
49+
/// ```
50+
pub fn range<X: SampleRange, R: Rng+?Sized>(low: X, high: X, rng: &mut R) -> X {
51+
assert!(low < high, "distributions::range called with low >= high");
52+
Range { inner: X::T::new(low, high) }.sample(rng)
53+
}
54+
55+
/// Convenience function to construct a `Range`
56+
pub fn new_range<X: SampleRange>(low: X, high: X) -> Range<X::T> {
57+
assert!(low < high, "new_range called with `low >= high`");
58+
Range { inner: RangeImpl::new(low, high) }
59+
}
60+
61+
/// Sample values uniformly between two bounds.
62+
///
63+
/// This gives a uniform distribution (assuming the RNG used to sample
64+
/// it is itself uniform & the `RangeImpl` implementation is correct),
65+
/// even for edge cases like `low = 0u8`,
66+
/// `high = 170u8`, for which a naive modulo operation would return
67+
/// numbers less than 85 with double the probability to those greater
68+
/// than 85.
69+
///
70+
/// Types should attempt to sample in `[low, high)`, i.e., not
71+
/// including `high`, but this may be very difficult. All the
72+
/// primitive integer types satisfy this property, and the float types
73+
/// normally satisfy it, but rounding may mean `high` can occur.
74+
///
75+
/// # Example
76+
///
77+
/// ```rust
78+
/// use rand::distributions::Distribution;
79+
/// use rand::distributions::range2::new_range;
80+
///
81+
/// fn main() {
82+
/// let between = new_range(10, 10000);
83+
/// let mut rng = rand::thread_rng();
84+
/// let mut sum = 0;
85+
/// for _ in 0..1000 {
86+
/// sum += between.sample(&mut rng);
87+
/// }
88+
/// println!("{}", sum);
89+
/// }
90+
/// ```
91+
#[derive(Clone, Copy, Debug)]
92+
pub struct Range<T: RangeImpl> {
93+
inner: T,
94+
}
95+
96+
impl<T: RangeImpl> Range<T> {
97+
/// Create a new `Range` instance that samples uniformly from
98+
/// `[low, high)`. Panics if `low >= high`.
99+
pub fn new(low: T::X, high: T::X) -> Range<T> {
100+
assert!(low < high, "Range::new called with `low >= high`");
101+
Range { inner: RangeImpl::new(low, high) }
102+
}
103+
}
104+
105+
impl<T: RangeImpl> Distribution<T::X> for Range<T> {
106+
fn sample<R: Rng+?Sized>(&self, rng: &mut R) -> T::X {
107+
self.inner.sample(rng)
108+
}
109+
}
110+
111+
/// Helper trait for creating implementations of `RangeImpl`.
112+
pub trait SampleRange: PartialOrd+Sized {
113+
type T: RangeImpl<X = Self>;
114+
}
115+
116+
/// Helper trait handling actual range sampling.
117+
pub trait RangeImpl {
118+
/// The type sampled by this implementation.
119+
type X: PartialOrd;
120+
121+
/// Construct self.
122+
///
123+
/// This should not be called directly. `Range::new` asserts that
124+
/// `low < high` before calling this.
125+
fn new(low: Self::X, high: Self::X) -> Self;
126+
127+
/// Sample a value.
128+
fn sample<R: Rng+?Sized>(&self, rng: &mut R) -> Self::X;
129+
}
130+
131+
/// Implementation of `RangeImpl` for integer types.
132+
#[derive(Clone, Copy, Debug)]
133+
pub struct RangeInt<X> {
134+
low: X,
135+
range: X,
136+
zone: X,
137+
}
138+
139+
macro_rules! range_int_impl {
140+
($ty:ty, $unsigned:ident) => {
141+
impl SampleRange for $ty {
142+
type T = RangeInt<$ty>;
143+
}
144+
145+
impl RangeImpl for RangeInt<$ty> {
146+
// we play free and fast with unsigned vs signed here
147+
// (when $ty is signed), but that's fine, since the
148+
// contract of this macro is for $ty and $unsigned to be
149+
// "bit-equal", so casting between them is a no-op & a
150+
// bijection.
151+
152+
type X = $ty;
153+
154+
fn new(low: Self::X, high: Self::X) -> Self {
155+
let range = (w(high as $unsigned) - w(low as $unsigned)).0;
156+
let unsigned_max: $unsigned = ::std::$unsigned::MAX;
157+
158+
// this is the largest number that fits into $unsigned
159+
// that `range` divides evenly, so, if we've sampled
160+
// `n` uniformly from this region, then `n % range` is
161+
// uniform in [0, range)
162+
let zone = unsigned_max - unsigned_max % range;
163+
164+
RangeInt {
165+
low: low,
166+
range: range as $ty,
167+
zone: zone as $ty
168+
}
169+
}
170+
171+
fn sample<R: Rng+?Sized>(&self, rng: &mut R) -> Self::X {
172+
use $crate::distributions::uniform;
173+
loop {
174+
// rejection sample
175+
let v: $unsigned = uniform(rng);
176+
// until we find something that fits into the
177+
// region which self.range evenly divides (this will
178+
// be uniformly distributed)
179+
if v < self.zone as $unsigned {
180+
// and return it, with some adjustments
181+
return (w(self.low) + w((v % self.range as $unsigned) as $ty)).0;
182+
}
183+
}
184+
}
185+
}
186+
}
187+
}
188+
189+
range_int_impl! { i8, u8 }
190+
range_int_impl! { i16, u16 }
191+
range_int_impl! { i32, u32 }
192+
range_int_impl! { i64, u64 }
193+
range_int_impl! { isize, usize }
194+
range_int_impl! { u8, u8 }
195+
range_int_impl! { u16, u16 }
196+
range_int_impl! { u32, u32 }
197+
range_int_impl! { u64, u64 }
198+
range_int_impl! { usize, usize }
199+
200+
/// Implementation of `RangeImpl` for float types.
201+
#[derive(Clone, Copy, Debug)]
202+
pub struct RangeFloat<X> {
203+
low: X,
204+
range: X,
205+
}
206+
207+
macro_rules! range_float_impl {
208+
($ty:ty) => {
209+
impl SampleRange for $ty {
210+
type T = RangeFloat<$ty>;
211+
}
212+
213+
impl RangeImpl for RangeFloat<$ty> {
214+
type X = $ty;
215+
216+
fn new(low: Self::X, high: Self::X) -> Self {
217+
RangeFloat {
218+
low: low,
219+
range: high - low,
220+
}
221+
}
222+
223+
fn sample<R: Rng+?Sized>(&self, rng: &mut R) -> Self::X {
224+
let x: $ty = Rand::rand(rng, Uniform01);
225+
self.low + self.range * x
226+
}
227+
}
228+
}
229+
}
230+
231+
range_float_impl! { f32 }
232+
range_float_impl! { f64 }
233+
234+
#[cfg(test)]
235+
mod tests {
236+
use {Rng, thread_rng};
237+
use distributions::Rand;
238+
use distributions::range2::{Range, range, new_range, RangeImpl, RangeFloat};
239+
240+
#[test]
241+
fn test_fn_range() {
242+
let mut r = thread_rng();
243+
for _ in 0..1000 {
244+
let a = range(-3, 42, &mut r);
245+
assert!(a >= -3 && a < 42);
246+
assert_eq!(range(0, 1, &mut r), 0);
247+
assert_eq!(range(-12, -11, &mut r), -12);
248+
}
249+
250+
for _ in 0..1000 {
251+
let a = range(10, 42, &mut r);
252+
assert!(a >= 10 && a < 42);
253+
assert_eq!(range(0, 1, &mut r), 0);
254+
assert_eq!(range(3_000_000, 3_000_001, &mut r), 3_000_000);
255+
}
256+
}
257+
258+
#[test]
259+
#[should_panic]
260+
fn test_fn_range_panic_int() {
261+
let mut r = thread_rng();
262+
range(5, -2, &mut r);
263+
}
264+
265+
#[test]
266+
#[should_panic]
267+
fn test_fn_range_panic_usize() {
268+
let mut r = thread_rng();
269+
range(5, 2, &mut r);
270+
}
271+
272+
#[should_panic]
273+
#[test]
274+
fn test_range_bad_limits_equal() {
275+
new_range(10, 10);
276+
}
277+
#[should_panic]
278+
#[test]
279+
fn test_range_bad_limits_flipped() {
280+
new_range(10, 5);
281+
}
282+
283+
#[test]
284+
fn test_integers() {
285+
let mut rng = ::test::rng();
286+
macro_rules! t {
287+
($($ty:ident),*) => {{
288+
$(
289+
let v: &[($ty, $ty)] = &[(0, 10),
290+
(10, 127),
291+
(::std::$ty::MIN, ::std::$ty::MAX)];
292+
for &(low, high) in v.iter() {
293+
let range = new_range(low, high);
294+
for _ in 0..1000 {
295+
let v: $ty = Rand::rand(&mut rng, range);
296+
assert!(low <= v && v < high);
297+
}
298+
}
299+
)*
300+
}}
301+
}
302+
t!(i8, i16, i32, i64, isize,
303+
u8, u16, u32, u64, usize)
304+
}
305+
306+
#[test]
307+
fn test_floats() {
308+
let mut rng = ::test::rng();
309+
macro_rules! t {
310+
($($ty:ty),*) => {{
311+
$(
312+
let v: &[($ty, $ty)] = &[(0.0, 100.0),
313+
(-1e35, -1e25),
314+
(1e-35, 1e-25),
315+
(-1e35, 1e35)];
316+
for &(low, high) in v.iter() {
317+
let range = new_range(low, high);
318+
for _ in 0..1000 {
319+
let v: $ty = Rand::rand(&mut rng, range);
320+
assert!(low <= v && v < high);
321+
}
322+
}
323+
)*
324+
}}
325+
}
326+
327+
t!(f32, f64)
328+
}
329+
330+
#[test]
331+
fn test_custom_range() {
332+
#[derive(Clone, Copy, PartialEq, PartialOrd)]
333+
struct MyF32 {
334+
x: f32,
335+
}
336+
#[derive(Clone, Copy, Debug)]
337+
struct RangeMyF32 {
338+
inner: RangeFloat<f32>,
339+
}
340+
impl RangeImpl for RangeMyF32 {
341+
type X = MyF32;
342+
fn new(low: Self::X, high: Self::X) -> Self {
343+
RangeMyF32 {
344+
inner: RangeFloat::<f32>::new(low.x, high.x),
345+
}
346+
}
347+
fn sample<R: Rng+?Sized>(&self, rng: &mut R) -> Self::X {
348+
MyF32 { x: self.inner.sample(rng) }
349+
}
350+
}
351+
352+
let (low, high) = (MyF32{ x: 17.0f32 }, MyF32{ x: 22.0f32 });
353+
let range = Range::<RangeMyF32>::new(low, high);
354+
let mut rng = ::test::rng();
355+
for _ in 0..100 {
356+
let x = MyF32::rand(&mut rng, range);
357+
assert!(low <= x && x < high);
358+
}
359+
}
360+
}

0 commit comments

Comments
 (0)