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

Range syntax distribution #9

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/distributions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
//! that do not need to record state.
use core::prelude::*;
use core::num::{Float, Int};
use std::ops::Range;

use {Rng, Rand};

pub use self::range::Range;
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
pub use self::normal::{Normal, LogNormal};
pub use self::exponential::Exp;
Expand Down Expand Up @@ -134,7 +134,7 @@ impl<'a, T: Clone> WeightedChoice<'a, T> {
items: items,
// we're likely to be generating numbers in this range
// relatively often, so might as well cache it
weight_range: Range::new(0, running_total)
weight_range: 0..running_total
}
}
}
Expand Down
107 changes: 55 additions & 52 deletions src/distributions/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

// this is surprisingly complicated to be both generic & correct

use core::prelude::{PartialOrd};
use core::num::Int;
use core::ops::Range;

use Rng;
use distributions::{Sample, IndependentSample};
Expand All @@ -35,10 +35,10 @@ use distributions::{Sample, IndependentSample};
/// # Example
///
/// ```rust
/// use rand::distributions::{IndependentSample, Range};
/// use rand::distributions::IndependentSample;
///
/// fn main() {
/// let between = Range::new(10, 10000);
/// let between = 10..10000;
/// let mut rng = rand::thread_rng();
/// let mut sum = 0;
/// for _ in 0..1000 {
Expand All @@ -47,20 +47,6 @@ use distributions::{Sample, IndependentSample};
/// println!("{}", sum);
/// }
/// ```
pub struct Range<X> {
low: X,
range: X,
accept_zone: X
}

impl<X: SampleRange + PartialOrd> Range<X> {
/// Create a new `Range` instance that samples uniformly from
/// `[low, high)`. Panics if `low >= high`.
pub fn new(low: X, high: X) -> Range<X> {
assert!(low < high, "Range::new called with `low >= high`");
SampleRange::construct_range(low, high)
}
}

impl<Sup: SampleRange> Sample<Sup> for Range<Sup> {
#[inline]
Expand All @@ -76,11 +62,6 @@ impl<Sup: SampleRange> IndependentSample<Sup> for Range<Sup> {
/// uniformly between two values. This should not be used directly,
/// and is only to facilitate `Range`.
pub trait SampleRange {
/// Construct the `Range` object that `sample_range`
/// requires. This should not ever be called directly, only via
/// `Range::new`, which will check that `low < high`, so this
/// function doesn't have to repeat the check.
fn construct_range(low: Self, high: Self) -> Range<Self>;

/// Sample a value from the given `Range` with the given `Rng` as
/// a source of randomness.
Expand All @@ -96,33 +77,26 @@ macro_rules! integer_impl {
// "bit-equal", so casting between them is a no-op & a
// bijection.

fn construct_range(low: $ty, high: $ty) -> Range<$ty> {
let range = high as $unsigned - low as $unsigned;
#[inline]
fn sample_range<R: Rng>(r: &Range<$ty>, rng: &mut R) -> $ty {
assert!(r.start < r.end, "Rng.sample_range called with low >= high");
let range = r.end as $unsigned - r.start as $unsigned;
let unsigned_max: $unsigned = Int::max_value();

// 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;

Range {
low: low,
range: range as $ty,
accept_zone: zone as $ty
}
}
#[inline]
fn sample_range<R: Rng>(r: &Range<$ty>, rng: &mut R) -> $ty {
loop {
// rejection sample
let v = rng.gen::<$unsigned>();
// until we find something that fits into the
// region which r.range evenly divides (this will
// be uniformly distributed)
if v < r.accept_zone as $unsigned {
if v < zone {
// and return it, with some adjustments
return r.low + (v % r.range as $unsigned) as $ty;
return r.start + (v % range as $unsigned) as $ty;
}
}
}
Expand All @@ -144,15 +118,10 @@ integer_impl! { usize, usize }
macro_rules! float_impl {
($ty:ty) => {
impl SampleRange for $ty {
fn construct_range(low: $ty, high: $ty) -> Range<$ty> {
Range {
low: low,
range: high - low,
accept_zone: 0.0 // unused
}
}
#[inline]
fn sample_range<R: Rng>(r: &Range<$ty>, rng: &mut R) -> $ty {
r.low + r.range * rng.gen()
assert!(r.start < r.end, "Rng.gen_range called with low >= high");
r.start + (r.end - r.start) * rng.gen()
}
}
}
Expand All @@ -166,17 +135,18 @@ mod tests {
use std::num::Int;
use std::prelude::v1::*;
use distributions::{Sample, IndependentSample};
use super::Range as Range;

#[should_fail]
#[test]
fn test_range_bad_limits_equal() {
Range::new(10, 10);
let mut rng = ::test::rng();
(10..10).sample(&mut rng);
}
#[should_fail]
#[test]
fn test_range_bad_limits_flipped() {
Range::new(10, 5);
let mut rng = ::test::rng();
(10..5).sample(&mut rng);
}

#[test]
Expand All @@ -189,11 +159,10 @@ mod tests {
(10, 127),
(Int::min_value(), Int::max_value())];
for &(low, high) in v.iter() {
let mut sampler: Range<$ty> = Range::new(low, high);
for _ in 0..1000 {
let v = sampler.sample(&mut rng);
let v = (low..high).sample(&mut rng);
assert!(low <= v && v < high);
let v = sampler.ind_sample(&mut rng);
let v = (low..high).ind_sample(&mut rng);
assert!(low <= v && v < high);
}
}
Expand All @@ -215,11 +184,10 @@ mod tests {
(1e-35, 1e-25),
(-1e35, 1e35)];
for &(low, high) in v.iter() {
let mut sampler: Range<$ty> = Range::new(low, high);
for _ in 0..1000 {
let v = sampler.sample(&mut rng);
let v = (low..high).sample(&mut rng);
assert!(low <= v && v < high);
let v = sampler.ind_sample(&mut rng);
let v = (low..high).ind_sample(&mut rng);
assert!(low <= v && v < high);
}
}
Expand All @@ -231,3 +199,38 @@ mod tests {
}

}

#[cfg(test)]
mod bench {
extern crate test;
use std::prelude::v1::*;
use self::test::Bencher;
use std::mem::size_of;
use distributions::IndependentSample;

#[bench]
fn rand_float(b: &mut Bencher) {
let mut rng = ::test::weak_rng();
let range = -2.71828..3.14159;

b.iter(|| {
for _ in 0..::RAND_BENCH_N {
range.ind_sample(&mut rng);
}
});
b.bytes = size_of::<f64>() as u64 * ::RAND_BENCH_N;
}

#[bench]
fn rand_int(b: &mut Bencher) {
let mut rng = ::test::weak_rng();
let range = -99..6418i32;

b.iter(|| {
for _ in 0..::RAND_BENCH_N {
range.ind_sample(&mut rng);
}
});
b.bytes = size_of::<f64>() as u64 * ::RAND_BENCH_N;
}
}
20 changes: 8 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@
//! multiply this fraction by 4.
//!
//! ```
//! use rand::distributions::{IndependentSample, Range};
//! use rand::distributions::IndependentSample;
//!
//! fn main() {
//! let between = Range::new(-1f64, 1.);
//! let between = -1f64..1.;
//! let mut rng = rand::thread_rng();
//!
//! let total = 1_000_000;
Expand Down Expand Up @@ -131,7 +131,8 @@
//!
//! ```
//! use rand::Rng;
//! use rand::distributions::{IndependentSample, Range};
//! use rand::distributions::IndependentSample;
//! use std::ops::Range;
//!
//! struct SimulationResult {
//! win: bool,
Expand Down Expand Up @@ -179,7 +180,7 @@
//! let num_simulations = 10000;
//!
//! let mut rng = rand::thread_rng();
//! let random_door = Range::new(0, 3);
//! let random_door = 0..3;
//!
//! let (mut switch_wins, mut switch_losses) = (0, 0);
//! let (mut keep_wins, mut keep_losses) = (0, 0);
Expand Down Expand Up @@ -239,7 +240,7 @@ use IsaacRng as IsaacWordRng;
#[cfg(target_pointer_width = "64")]
use Isaac64Rng as IsaacWordRng;

use distributions::{Range, IndependentSample};
use distributions::IndependentSample;
use distributions::range::SampleRange;

pub mod distributions;
Expand Down Expand Up @@ -404,11 +405,7 @@ pub trait Rng : Sized {

/// Generate a random value in the range [`low`, `high`).
///
/// This is a convenience wrapper around
/// `distributions::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, as they only happen on initialization.
/// This is a convenience function
///
/// # Panics
///
Expand All @@ -426,8 +423,7 @@ pub trait Rng : Sized {
/// println!("{}", m);
/// ```
fn gen_range<T: PartialOrd + SampleRange>(&mut self, low: T, high: T) -> T {
assert!(low < high, "Rng.gen_range called with low >= high");
Range::new(low, high).ind_sample(self)
(low..high).ind_sample(self)
}

/// Return a bool with a 1 in n chance of true
Expand Down