From 4213bc02dd621871a069ca007c45580e1a1549c1 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 11 Apr 2018 12:48:39 +0200 Subject: [PATCH 01/24] Small improvements to binomial docs --- src/distributions/binomial.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/distributions/binomial.rs b/src/distributions/binomial.rs index 8a03e1d5841..eb716f444b7 100644 --- a/src/distributions/binomial.rs +++ b/src/distributions/binomial.rs @@ -31,13 +31,17 @@ use std::f64::consts::PI; /// ``` #[derive(Clone, Copy, Debug)] pub struct Binomial { - n: u64, // number of trials - p: f64, // probability of success + /// Number of trials. + n: u64, + /// Probability of success. + p: f64, } impl Binomial { - /// Construct a new `Binomial` with the given shape parameters - /// `n`, `p`. Panics if `p <= 0` or `p >= 1`. + /// Construct a new `Binomial` with the given shape parameters `n` (number + /// of trials) and `p` (probability of success). + /// + /// Panics if `p <= 0` or `p >= 1`. pub fn new(n: u64, p: f64) -> Binomial { assert!(p > 0.0, "Binomial::new called with p <= 0"); assert!(p < 1.0, "Binomial::new called with p >= 1"); From eb47e3bdcbf517f41843703c67d0fe669cfbc264 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 19 Apr 2018 15:04:48 +0200 Subject: [PATCH 02/24] Document panics of gen_bool --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 01cc15dd0bf..24b6ed294f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -483,6 +483,10 @@ pub trait Rng: RngCore { /// println!("{}", rng.gen_bool(1.0 / 3.0)); /// ``` /// + /// # Panics + /// + /// If `p` < 0 or `p` > 1. + /// /// # Accuracy note /// /// `gen_bool` uses 32 bits of the RNG, so if you use it to generate close From 20eb2bf3aef3877d9d439fce0db047505c8cae64 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 11 Apr 2018 13:14:58 +0200 Subject: [PATCH 03/24] Implement Bernoulli distribution Fixes #300. --- src/distributions/bernoulli.rs | 129 +++++++++++++++++++++++++++++++++ src/distributions/mod.rs | 2 + 2 files changed, 131 insertions(+) create mode 100644 src/distributions/bernoulli.rs diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs new file mode 100644 index 00000000000..df986d4f7e2 --- /dev/null +++ b/src/distributions/bernoulli.rs @@ -0,0 +1,129 @@ +//! The Bernoulli distribution. + +use Rng; +use distributions::Distribution; + +/// The Bernoulli distribution. +/// +/// This is a special case of the Binomial distribution where `n = 1`. +/// +/// # Example +/// +/// ```rust +/// use rand::distributions::{Bernoulli, Distribution}; +/// +/// let d = Bernoulli::new(0.3); +/// let v: u64 = d.sample(&mut rand::thread_rng()); +/// println!("{} is from a Bernoulli distribution", v); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Bernoulli { + /// Probability of success. + p: f64, +} + +impl Bernoulli { + /// Construct a new `Bernoulli` with the given probability of success `p`. + /// + /// # Panics + /// + /// If `p < 0` or `p > 1`. + #[inline] + pub fn new(p: f64) -> Bernoulli { + assert!(p >= 0.0, "Bernoulli::new called with p < 0"); + assert!(p <= 1.0, "Bernoulli::new called with p > 1"); + Bernoulli { p } + } +} + +impl Distribution for Bernoulli { + #[inline] + fn sample(&self, rng: &mut R) -> bool { + rng.gen_bool(self.p) + } +} + +macro_rules! impl_bernoulli_int { + ($($t: ty), *) => { + $( + impl Distribution<$t> for Bernoulli { + #[inline] + fn sample(&self, rng: &mut R) -> $t { + rng.gen_bool(self.p) as $t + } + } + )* + } +} + +impl_bernoulli_int!(u8, u16, u32, u64, i8, i16, i32, i64); + +macro_rules! impl_bernoulli_float { + ($($t: ty), *) => { + $( + impl Distribution<$t> for Bernoulli { + #[inline] + fn sample(&self, rng: &mut R) -> $t { + rng.gen_bool(self.p) as u8 as $t + } + } + )* + } +} + +impl_bernoulli_float!(f32, f64); + +#[cfg(test)] +mod test { + use {Rng, SmallRng, FromEntropy}; + use distributions::Distribution; + use super::Bernoulli; + + #[test] + fn test_trivial() { + let mut r = SmallRng::from_entropy(); + let always_false = Bernoulli::new(0.0); + let always_true = Bernoulli::new(1.0); + for _ in 0..5 { + assert_eq!(r.sample::(&always_false), false); + assert_eq!(r.sample::(&always_true), true); + assert_eq!(Distribution::::sample(&always_false, &mut r), false); + assert_eq!(Distribution::::sample(&always_true, &mut r), true); + } + } + + #[test] + fn test_average() { + const P: f64 = 0.3; + let d = Bernoulli::new(P); + const N: u32 = 10_000_000; + + let mut sum: u32 = 0; + let mut rng = SmallRng::from_entropy(); + for _ in 0..N { + if d.sample(&mut rng) { + sum += 1; + } + } + let avg = (sum as f64) / (N as f64); + + assert!((avg - P).abs() < 1e-3); + } + + #[test] + fn test_nonbool() { + + macro_rules! assert_nonbool { + ($($t: ty), *) => { + let d = Bernoulli::new(0.3); + let r = SmallRng::from_entropy(); + $( + assert_eq!(r.clone().sample::<$t, _>(&d) as f64 != 0., + r.clone().sample::(&d)); + )* + } + } + + assert_nonbool!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64); + } +} diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index aaf330420c8..a7761e388bf 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -178,6 +178,7 @@ pub use self::uniform::Uniform as Range; #[doc(inline)] pub use self::poisson::Poisson; #[cfg(feature = "std")] #[doc(inline)] pub use self::binomial::Binomial; +#[doc(inline)] pub use self::bernoulli::Bernoulli; pub mod uniform; #[cfg(feature="std")] @@ -190,6 +191,7 @@ pub mod uniform; #[doc(hidden)] pub mod poisson; #[cfg(feature = "std")] #[doc(hidden)] pub mod binomial; +#[doc(hidden)] pub mod bernoulli; mod float; mod integer; From fe02da994897856f5ddf62fdc7f72afb9c329f4d Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 19 Apr 2018 17:07:53 +0200 Subject: [PATCH 04/24] Implement `Distribution for Bernoulli` only for bool --- src/distributions/bernoulli.rs | 49 +--------------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index df986d4f7e2..d4cf5cdfb88 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -13,7 +13,7 @@ use distributions::Distribution; /// use rand::distributions::{Bernoulli, Distribution}; /// /// let d = Bernoulli::new(0.3); -/// let v: u64 = d.sample(&mut rand::thread_rng()); +/// let v = d.sample(&mut rand::thread_rng()); /// println!("{} is from a Bernoulli distribution", v); /// ``` #[derive(Clone, Copy, Debug)] @@ -43,36 +43,6 @@ impl Distribution for Bernoulli { } } -macro_rules! impl_bernoulli_int { - ($($t: ty), *) => { - $( - impl Distribution<$t> for Bernoulli { - #[inline] - fn sample(&self, rng: &mut R) -> $t { - rng.gen_bool(self.p) as $t - } - } - )* - } -} - -impl_bernoulli_int!(u8, u16, u32, u64, i8, i16, i32, i64); - -macro_rules! impl_bernoulli_float { - ($($t: ty), *) => { - $( - impl Distribution<$t> for Bernoulli { - #[inline] - fn sample(&self, rng: &mut R) -> $t { - rng.gen_bool(self.p) as u8 as $t - } - } - )* - } -} - -impl_bernoulli_float!(f32, f64); - #[cfg(test)] mod test { use {Rng, SmallRng, FromEntropy}; @@ -109,21 +79,4 @@ mod test { assert!((avg - P).abs() < 1e-3); } - - #[test] - fn test_nonbool() { - - macro_rules! assert_nonbool { - ($($t: ty), *) => { - let d = Bernoulli::new(0.3); - let r = SmallRng::from_entropy(); - $( - assert_eq!(r.clone().sample::<$t, _>(&d) as f64 != 0., - r.clone().sample::(&d)); - )* - } - } - - assert_nonbool!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64); - } } From a2921b1dbfc59b5c07bf3676e9b83a9ddc393f3d Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 19 Apr 2018 17:23:07 +0200 Subject: [PATCH 05/24] Benchmark gen_bool vs. Bernoulli::sample --- benches/misc.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/benches/misc.rs b/benches/misc.rs index 4eb910c9d2d..b7d9ed218e9 100644 --- a/benches/misc.rs +++ b/benches/misc.rs @@ -36,6 +36,34 @@ fn misc_gen_bool_var(b: &mut Bencher) { }) } +#[bench] +fn misc_bernoulli(b: &mut Bencher) { + let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap(); + let d = rand::distributions::Bernoulli::new(0.18); + b.iter(|| { + let mut accum = true; + for _ in 0..::RAND_BENCH_N { + accum ^= rng.sample(d); + } + black_box(accum); + }) +} + +#[bench] +fn misc_bernoulli_var(b: &mut Bencher) { + let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap(); + b.iter(|| { + let mut p = 0.18; + let mut accum = true; + for _ in 0..::RAND_BENCH_N { + let d = rand::distributions::Bernoulli::new(p); + accum ^= rng.sample(d); + p += 0.0001; + } + black_box(accum); + }) +} + #[bench] fn misc_shuffle_100(b: &mut Bencher) { let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); From 588f0d0f7bbecc42b9a61aa11d2645fdf301a5ea Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 19 Apr 2018 17:45:19 +0200 Subject: [PATCH 06/24] Improve bool benchmarks --- benches/misc.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/benches/misc.rs b/benches/misc.rs index b7d9ed218e9..1d17cc50193 100644 --- a/benches/misc.rs +++ b/benches/misc.rs @@ -11,14 +11,15 @@ use rand::prelude::*; use rand::seq::*; #[bench] -fn misc_gen_bool(b: &mut Bencher) { +fn misc_gen_bool_const(b: &mut Bencher) { let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { + // Can be evaluated at compile time. let mut accum = true; for _ in 0..::RAND_BENCH_N { accum ^= rng.gen_bool(0.18); } - black_box(accum); + accum }) } @@ -27,25 +28,24 @@ fn misc_gen_bool_var(b: &mut Bencher) { let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { let mut p = 0.18; - let mut accum = true; + black_box(&mut p); // Avoid constant folding. for _ in 0..::RAND_BENCH_N { - accum ^= rng.gen_bool(p); - p += 0.0001; + black_box(rng.gen_bool(p)); } - black_box(accum); }) } #[bench] -fn misc_bernoulli(b: &mut Bencher) { +fn misc_bernoulli_const(b: &mut Bencher) { let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap(); let d = rand::distributions::Bernoulli::new(0.18); b.iter(|| { + // Can be evaluated at compile time. let mut accum = true; for _ in 0..::RAND_BENCH_N { accum ^= rng.sample(d); } - black_box(accum); + accum }) } @@ -54,13 +54,11 @@ fn misc_bernoulli_var(b: &mut Bencher) { let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { let mut p = 0.18; - let mut accum = true; + black_box(&mut p); // Avoid constant folding. + let d = rand::distributions::Bernoulli::new(p); for _ in 0..::RAND_BENCH_N { - let d = rand::distributions::Bernoulli::new(p); - accum ^= rng.sample(d); - p += 0.0001; + black_box(rng.sample(d)); } - black_box(accum); }) } From 9e234def46b61dc645f77dc65121edfe983d1b97 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 23 Apr 2018 13:30:06 +0200 Subject: [PATCH 07/24] Move implementation from gen_bool to Bernoulli::sample This avoids redoing some calculations. --- src/distributions/bernoulli.rs | 10 ++++++---- src/lib.rs | 6 ++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index d4cf5cdfb88..961a932298b 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -18,8 +18,8 @@ use distributions::Distribution; /// ``` #[derive(Clone, Copy, Debug)] pub struct Bernoulli { - /// Probability of success. - p: f64, + /// Probability of success, relative to the maximal integer. + p_int: u32, } impl Bernoulli { @@ -32,14 +32,16 @@ impl Bernoulli { pub fn new(p: f64) -> Bernoulli { assert!(p >= 0.0, "Bernoulli::new called with p < 0"); assert!(p <= 1.0, "Bernoulli::new called with p > 1"); - Bernoulli { p } + let p_int = (p * f64::from(::core::u32::MAX)) as u32; + Bernoulli { p_int } } } impl Distribution for Bernoulli { #[inline] fn sample(&self, rng: &mut R) -> bool { - rng.gen_bool(self.p) + // If `p` is constant, this will be evaluated at compile-time. + rng.gen::() <= self.p_int } } diff --git a/src/lib.rs b/src/lib.rs index 24b6ed294f1..80df25c2d95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -497,10 +497,8 @@ pub trait Rng: RngCore { /// from an RNG just to consistently generate `false` does not match with /// the intent of this method. fn gen_bool(&mut self, p: f64) -> bool { - assert!(p >= 0.0 && p <= 1.0); - // If `p` is constant, this will be evaluated at compile-time. - let p_int = (p * f64::from(core::u32::MAX)) as u32; - self.gen::() <= p_int + let d = distributions::Bernoulli::new(p); + self.sample(d) } /// Return a random element from `values`. From 9ed874f09ef71f80816e1a82c1f6b7288903673a Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 23 Apr 2018 15:27:57 +0200 Subject: [PATCH 08/24] Use generated floats in Bernoulli::sample This is slower, but uses 52 random bits instead of 32 bits. Using the previous approach with 64 bits is not feasible due to a [Rust bug]. [Rust bug]: https://github.com/rust-lang/rust/issues/10184 --- src/distributions/bernoulli.rs | 8 +++----- src/lib.rs | 12 +++--------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 961a932298b..47fb08cccef 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -19,7 +19,7 @@ use distributions::Distribution; #[derive(Clone, Copy, Debug)] pub struct Bernoulli { /// Probability of success, relative to the maximal integer. - p_int: u32, + p: f64, } impl Bernoulli { @@ -32,16 +32,14 @@ impl Bernoulli { pub fn new(p: f64) -> Bernoulli { assert!(p >= 0.0, "Bernoulli::new called with p < 0"); assert!(p <= 1.0, "Bernoulli::new called with p > 1"); - let p_int = (p * f64::from(::core::u32::MAX)) as u32; - Bernoulli { p_int } + Bernoulli { p } } } impl Distribution for Bernoulli { #[inline] fn sample(&self, rng: &mut R) -> bool { - // If `p` is constant, this will be evaluated at compile-time. - rng.gen::() <= self.p_int + rng.gen::() <= self.p } } diff --git a/src/lib.rs b/src/lib.rs index 80df25c2d95..3fb0fa8b2dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -474,6 +474,8 @@ pub trait Rng: RngCore { /// Return a bool with a probability `p` of being true. /// + /// This is a wrapper around [`distributions::Bernoulli`]. + /// /// # Example /// /// ```rust @@ -487,15 +489,7 @@ pub trait Rng: RngCore { /// /// If `p` < 0 or `p` > 1. /// - /// # Accuracy note - /// - /// `gen_bool` uses 32 bits of the RNG, so if you use it to generate close - /// to or more than `2^32` results, a tiny bias may become noticable. - /// A notable consequence of the method used here is that the worst case is - /// `rng.gen_bool(0.0)`: it has a chance of 1 in `2^32` of being true, while - /// it should always be false. But using `gen_bool` to consume *many* values - /// from an RNG just to consistently generate `false` does not match with - /// the intent of this method. + /// [`distributions::Bernoulli`]: distributions/bernoulli/struct.Bernoulli.html fn gen_bool(&mut self, p: f64) -> bool { let d = distributions::Bernoulli::new(p); self.sample(d) From 0dfa229e1d840d2046b529117cc7fb816cdc1464 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 23 Apr 2018 20:53:41 +0200 Subject: [PATCH 09/24] Use 64 bit for generating bools This should be about as fast as before. A special test has been added to make sure the new implementation is correct for large probabilities. --- src/distributions/bernoulli.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 47fb08cccef..57f22c8cf2d 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -19,7 +19,7 @@ use distributions::Distribution; #[derive(Clone, Copy, Debug)] pub struct Bernoulli { /// Probability of success, relative to the maximal integer. - p: f64, + p_int: u64, } impl Bernoulli { @@ -32,14 +32,20 @@ impl Bernoulli { pub fn new(p: f64) -> Bernoulli { assert!(p >= 0.0, "Bernoulli::new called with p < 0"); assert!(p <= 1.0, "Bernoulli::new called with p > 1"); - Bernoulli { p } + let p_int = if p != 1.0 { + (p * (::core::u64::MAX as f64)) as u64 + } else { + ::core::u64::MAX + }; + Bernoulli { p_int } } } impl Distribution for Bernoulli { #[inline] fn sample(&self, rng: &mut R) -> bool { - rng.gen::() <= self.p + let r: u64 = rng.gen(); + r <= self.p_int } } From 5bc6b494d5476b07ac03d9ccb5430bf5438f754f Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 23 Apr 2018 21:03:11 +0200 Subject: [PATCH 10/24] Add note on precision --- src/distributions/bernoulli.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 57f22c8cf2d..6c7b59c4a63 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -28,6 +28,12 @@ impl Bernoulli { /// # Panics /// /// If `p < 0` or `p > 1`. + /// + /// # Precision + /// + /// For `p = 1.0`, the resulting distribution will always generate true. + /// For `p = 0.0`, there is a chance of 264 to incorrectly + /// generate true. #[inline] pub fn new(p: f64) -> Bernoulli { assert!(p >= 0.0, "Bernoulli::new called with p < 0"); From e07efce02062f71a19a63b23e94e792379070672 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 24 Apr 2018 21:49:40 +0200 Subject: [PATCH 11/24] Make sure Bernoulli::new(1.) always generates true --- src/distributions/bernoulli.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 6c7b59c4a63..fec0d4b067e 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -32,15 +32,15 @@ impl Bernoulli { /// # Precision /// /// For `p = 1.0`, the resulting distribution will always generate true. - /// For `p = 0.0`, there is a chance of 264 to incorrectly - /// generate true. + /// For `p = 0.0`, the resulting distribution will always generate false. #[inline] pub fn new(p: f64) -> Bernoulli { assert!(p >= 0.0, "Bernoulli::new called with p < 0"); assert!(p <= 1.0, "Bernoulli::new called with p > 1"); - let p_int = if p != 1.0 { + let p_int = if p < 1.0 { (p * (::core::u64::MAX as f64)) as u64 } else { + // Avoid overflow: u64::MAX as f64 cannot be represented as u64. ::core::u64::MAX }; Bernoulli { p_int } @@ -50,8 +50,12 @@ impl Bernoulli { impl Distribution for Bernoulli { #[inline] fn sample(&self, rng: &mut R) -> bool { + // Make sure to always return true for p = 1.0. + if self.p_int == ::core::u64::MAX { + return true; + } let r: u64 = rng.gen(); - r <= self.p_int + r < self.p_int } } From 06cd3896032a046d9a3fc968f5bc297606521879 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 24 Apr 2018 21:53:59 +0200 Subject: [PATCH 12/24] Add copyright notice --- src/distributions/bernoulli.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index fec0d4b067e..32b769c3df9 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -1,3 +1,12 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://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. //! The Bernoulli distribution. use Rng; From e0df2c45bdf65b1d82badc50e5d7e8400b82827c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 24 Apr 2018 22:00:15 +0200 Subject: [PATCH 13/24] Bernoulli: Add comment on precision based on @pitdicker's suggestion --- src/distributions/bernoulli.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 32b769c3df9..68ab54b64b5 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -25,6 +25,12 @@ use distributions::Distribution; /// let v = d.sample(&mut rand::thread_rng()); /// println!("{} is from a Bernoulli distribution", v); /// ``` +/// +/// # Precision +/// +/// This `Bernoulli` distribution uses 64 bits from the RNG (a `u64`), making +/// its bias less than 1 in 264. In practice the floating point +/// accuracy of `p` will usually be the limiting factor. #[derive(Clone, Copy, Debug)] pub struct Bernoulli { /// Probability of success, relative to the maximal integer. From 914f54a7cdacaef7f508387603796120128cbde4 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 25 Apr 2018 15:30:38 +0200 Subject: [PATCH 14/24] Bernoulli: Correct remarks about precision Also support construction from integers. --- src/distributions/bernoulli.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 68ab54b64b5..ed801d3c952 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -28,9 +28,9 @@ use distributions::Distribution; /// /// # Precision /// -/// This `Bernoulli` distribution uses 64 bits from the RNG (a `u64`), making -/// its bias less than 1 in 264. In practice the floating point -/// accuracy of `p` will usually be the limiting factor. +/// This `Bernoulli` distribution uses 64 bits from the RNG (a `u64`), +/// so only probabilities that are multiples of 2-64 can be +/// represented. #[derive(Clone, Copy, Debug)] pub struct Bernoulli { /// Probability of success, relative to the maximal integer. @@ -48,6 +48,10 @@ impl Bernoulli { /// /// For `p = 1.0`, the resulting distribution will always generate true. /// For `p = 0.0`, the resulting distribution will always generate false. + /// Due to the limitations of floating point numbers, not all internally + /// supported probabilities (multiples of 2-64) can be specified + /// using this constructor. If you need more precision, use + /// `Bernoulli::from_int` instead. #[inline] pub fn new(p: f64) -> Bernoulli { assert!(p >= 0.0, "Bernoulli::new called with p < 0"); @@ -60,6 +64,18 @@ impl Bernoulli { }; Bernoulli { p_int } } + + /// Construct a new `Bernoulli` with the probability of success given as an + /// integer `p_int = p * 2^64`. + /// + /// `p_int = 0` corresponds to `p = 0.0`. + /// `p_int = u64::MAX` corresponds to `p = 1.0`. + /// + /// This is more precise than using `Bernoulli::new`. + #[inline] + pub fn from_int(p_int: u64) -> Bernoulli { + Bernoulli { p_int } + } } impl Distribution for Bernoulli { From d47d3f0a45e52ff95cb24533d231b3e978230a3e Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 25 Apr 2018 17:35:01 +0200 Subject: [PATCH 15/24] Address review feedback * Remove `Bernoulli::from_int`. * Improve precision comments. * Only use one branch for asserts. --- src/distributions/bernoulli.rs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index ed801d3c952..281d8250953 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -48,14 +48,14 @@ impl Bernoulli { /// /// For `p = 1.0`, the resulting distribution will always generate true. /// For `p = 0.0`, the resulting distribution will always generate false. - /// Due to the limitations of floating point numbers, not all internally - /// supported probabilities (multiples of 2-64) can be specified - /// using this constructor. If you need more precision, use - /// `Bernoulli::from_int` instead. + /// + /// This method is accurate for any input `p` in the range `[0, 1]` which is + /// a multiple of 2-64. If you need more precision, use `Uniform` + /// and a comparison instead. (Note that not all multiples of + /// 2-64 in `[0, 1]` can be represented as a `f64`.) #[inline] pub fn new(p: f64) -> Bernoulli { - assert!(p >= 0.0, "Bernoulli::new called with p < 0"); - assert!(p <= 1.0, "Bernoulli::new called with p > 1"); + assert!(p >= 0.0 & p <= 1.0, "Bernoulli::new not called with 0 <= p <= 0"); let p_int = if p < 1.0 { (p * (::core::u64::MAX as f64)) as u64 } else { @@ -64,18 +64,6 @@ impl Bernoulli { }; Bernoulli { p_int } } - - /// Construct a new `Bernoulli` with the probability of success given as an - /// integer `p_int = p * 2^64`. - /// - /// `p_int = 0` corresponds to `p = 0.0`. - /// `p_int = u64::MAX` corresponds to `p = 1.0`. - /// - /// This is more precise than using `Bernoulli::new`. - #[inline] - pub fn from_int(p_int: u64) -> Bernoulli { - Bernoulli { p_int } - } } impl Distribution for Bernoulli { From 959160f0f9e13b157e23caf5a8de813c61459bf8 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 26 Apr 2018 17:34:33 +0200 Subject: [PATCH 16/24] Don't mention `Uniform` alternative --- src/distributions/bernoulli.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 281d8250953..e8b23b4c174 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -50,8 +50,7 @@ impl Bernoulli { /// For `p = 0.0`, the resulting distribution will always generate false. /// /// This method is accurate for any input `p` in the range `[0, 1]` which is - /// a multiple of 2-64. If you need more precision, use `Uniform` - /// and a comparison instead. (Note that not all multiples of + /// a multiple of 2-64. (Note that not all multiples of /// 2-64 in `[0, 1]` can be represented as a `f64`.) #[inline] pub fn new(p: f64) -> Bernoulli { From bfd81c424ef60a0c6752c3bb1212aec0fc4a8c70 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 3 May 2018 18:06:46 +0200 Subject: [PATCH 17/24] Fix chained comparison --- src/distributions/bernoulli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index e8b23b4c174..bb008a9c32e 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -54,7 +54,7 @@ impl Bernoulli { /// 2-64 in `[0, 1]` can be represented as a `f64`.) #[inline] pub fn new(p: f64) -> Bernoulli { - assert!(p >= 0.0 & p <= 1.0, "Bernoulli::new not called with 0 <= p <= 0"); + assert!((p >= 0.0) & (p <= 1.0), "Bernoulli::new not called with 0 <= p <= 0"); let p_int = if p < 1.0 { (p * (::core::u64::MAX as f64)) as u64 } else { From 6fe648f7b24b374eb0ce61562311ea3a1a92ac94 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 3 May 2018 18:09:48 +0200 Subject: [PATCH 18/24] Inline a few `Rng` methods --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 3fb0fa8b2dd..45971662a0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -352,6 +352,7 @@ pub trait Rng: RngCore { /// [`Uniform`]: distributions/uniform/struct.Uniform.html /// [`Uniform::new`]: distributions/uniform/struct.Uniform.html#method.new /// [`Uniform::sample_single`]: distributions/uniform/struct.Uniform.html#method.sample_single + #[inline] fn gen_range(&mut self, low: T, high: T) -> T { Uniform::sample_single(low, high, self) } @@ -370,6 +371,7 @@ pub trait Rng: RngCore { /// // distribution can be inferred. /// let y = rng.sample::(Uniform::new(10, 15)); /// ``` + #[inline] fn sample>(&mut self, distr: D) -> T { distr.sample(self) } @@ -401,6 +403,7 @@ pub trait Rng: RngCore { /// println!("Not a 6; rolling again!"); /// } /// ``` + #[inline] fn sample_iter<'a, T, D: Distribution>(&'a mut self, distr: &'a D) -> distributions::DistIter<'a, D, Self, T> where Self: Sized { @@ -490,6 +493,7 @@ pub trait Rng: RngCore { /// If `p` < 0 or `p` > 1. /// /// [`distributions::Bernoulli`]: distributions/bernoulli/struct.Bernoulli.html + #[inline] fn gen_bool(&mut self, p: f64) -> bool { let d = distributions::Bernoulli::new(p); self.sample(d) From 7b1e068f38297a17ae4ebc7b79d806a2ea2c4ea1 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 5 May 2018 13:16:36 +0200 Subject: [PATCH 19/24] Add test against possible undefined behavior --- tests/bool.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/bool.rs diff --git a/tests/bool.rs b/tests/bool.rs new file mode 100644 index 00000000000..f3a5c23fd15 --- /dev/null +++ b/tests/bool.rs @@ -0,0 +1,26 @@ +#![cfg(feature = "std")] + +extern crate rand; +extern crate libc; + +use rand::{FromEntropy, SmallRng}; +use rand::distributions::{Distribution, Bernoulli}; + +#[link_name = "m"] +extern "C" { + fn nextafter(x: f64, y: f64) -> f64; +} + +/// This test should make sure that we don't accidentally have undefined +/// behavior for large propabilties due to +/// https://github.com/rust-lang/rust/issues/10184. +#[test] +fn large_probability() { + let p = unsafe { nextafter(1., 0.) }; + assert!(p < 1.); + let d = Bernoulli::new(p); + let mut rng = SmallRng::from_entropy(); + for _ in 0..10 { + assert!(d.sample(&mut rng)); + } +} From 67ba295597887698e6523352750f3e26b82643e3 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 11 May 2018 12:54:46 +0200 Subject: [PATCH 20/24] Fix imports --- src/distributions/bernoulli.rs | 3 ++- tests/bool.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index bb008a9c32e..ff5f1acc76c 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -79,7 +79,8 @@ impl Distribution for Bernoulli { #[cfg(test)] mod test { - use {Rng, SmallRng, FromEntropy}; + use rngs::SmallRng; + use {Rng, FromEntropy}; use distributions::Distribution; use super::Bernoulli; diff --git a/tests/bool.rs b/tests/bool.rs index f3a5c23fd15..1d81b69edf3 100644 --- a/tests/bool.rs +++ b/tests/bool.rs @@ -3,7 +3,8 @@ extern crate rand; extern crate libc; -use rand::{FromEntropy, SmallRng}; +use rand::rngs::SmallRng; +use rand::FromEntropy; use rand::distributions::{Distribution, Bernoulli}; #[link_name = "m"] From 0c85fb8a1de59ee358e6d97d11c7510f2dab69b5 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 11 May 2018 13:04:44 +0200 Subject: [PATCH 21/24] Improve bool test * Don't use libc. * Add assert message. * Add example for undefined behavior. --- tests/bool.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/bool.rs b/tests/bool.rs index 1d81b69edf3..e0d8c3bf759 100644 --- a/tests/bool.rs +++ b/tests/bool.rs @@ -1,27 +1,22 @@ -#![cfg(feature = "std")] +#![no_std] extern crate rand; -extern crate libc; use rand::rngs::SmallRng; use rand::FromEntropy; use rand::distributions::{Distribution, Bernoulli}; -#[link_name = "m"] -extern "C" { - fn nextafter(x: f64, y: f64) -> f64; -} - /// This test should make sure that we don't accidentally have undefined /// behavior for large propabilties due to /// https://github.com/rust-lang/rust/issues/10184. +/// Expressions like `1.0*(u64::MAX as f64) as u64` have to be avoided. #[test] fn large_probability() { - let p = unsafe { nextafter(1., 0.) }; + let p = 1. - ::core::f64::EPSILON / 2.; assert!(p < 1.); let d = Bernoulli::new(p); let mut rng = SmallRng::from_entropy(); for _ in 0..10 { - assert!(d.sample(&mut rng)); + assert!(d.sample(&mut rng), "extremely unlikely to fail by accident"); } } From 67c55eaf214fdf340a4eae4829cd8069a0cc3a9d Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 11 May 2018 13:23:09 +0200 Subject: [PATCH 22/24] Fix tests on no_std --- src/distributions/bernoulli.rs | 7 +++---- tests/bool.rs | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index ff5f1acc76c..e3ccf965136 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -79,14 +79,13 @@ impl Distribution for Bernoulli { #[cfg(test)] mod test { - use rngs::SmallRng; - use {Rng, FromEntropy}; + use Rng; use distributions::Distribution; use super::Bernoulli; #[test] fn test_trivial() { - let mut r = SmallRng::from_entropy(); + let mut r = ::test::rng(1); let always_false = Bernoulli::new(0.0); let always_true = Bernoulli::new(1.0); for _ in 0..5 { @@ -104,7 +103,7 @@ mod test { const N: u32 = 10_000_000; let mut sum: u32 = 0; - let mut rng = SmallRng::from_entropy(); + let mut rng = ::test::rng(2); for _ in 0..N { if d.sample(&mut rng) { sum += 1; diff --git a/tests/bool.rs b/tests/bool.rs index e0d8c3bf759..c4208a009a3 100644 --- a/tests/bool.rs +++ b/tests/bool.rs @@ -2,8 +2,8 @@ extern crate rand; +use rand::SeedableRng; use rand::rngs::SmallRng; -use rand::FromEntropy; use rand::distributions::{Distribution, Bernoulli}; /// This test should make sure that we don't accidentally have undefined @@ -15,7 +15,8 @@ fn large_probability() { let p = 1. - ::core::f64::EPSILON / 2.; assert!(p < 1.); let d = Bernoulli::new(p); - let mut rng = SmallRng::from_entropy(); + let mut rng = SmallRng::from_seed( + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); for _ in 0..10 { assert!(d.sample(&mut rng), "extremely unlikely to fail by accident"); } From fcd0154e429f0103c59add2f46c5b9d0fd96bd26 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 15 May 2018 08:16:16 +0200 Subject: [PATCH 23/24] Clarify constant in Bernoulli --- src/distributions/bernoulli.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index e3ccf965136..2361fac0c21 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -55,10 +55,14 @@ impl Bernoulli { #[inline] pub fn new(p: f64) -> Bernoulli { assert!((p >= 0.0) & (p <= 1.0), "Bernoulli::new not called with 0 <= p <= 0"); + // Technically, this should be 2^64 or `u64::MAX + 1` because we compare + // using `<` when sampling. However, `u64::MAX` rounds to an `f64` + // larger than `u64::MAX` anyway. + const MAX_P_INT: f64 = ::core::u64::MAX as f64; let p_int = if p < 1.0 { - (p * (::core::u64::MAX as f64)) as u64 + (p * MAX_P_INT) as u64 } else { - // Avoid overflow: u64::MAX as f64 cannot be represented as u64. + // Avoid overflow: `MAX_P_INT` cannot be represented as u64. ::core::u64::MAX }; Bernoulli { p_int } From c440d3ec9dd918a8b96ddf708d1c5c9bce1a0695 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 15 May 2018 08:17:28 +0200 Subject: [PATCH 24/24] Remove inline annotations from generic functions They are redundant, because generic functions can be inlined anyway. --- src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 45971662a0a..0599fc1ea02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -318,7 +318,6 @@ pub trait Rng: RngCore { /// println!("{}", x); /// println!("{:?}", rng.gen::<(f64, bool)>()); /// ``` - #[inline(always)] fn gen(&mut self) -> T where Standard: Distribution { Standard.sample(self) } @@ -352,7 +351,6 @@ pub trait Rng: RngCore { /// [`Uniform`]: distributions/uniform/struct.Uniform.html /// [`Uniform::new`]: distributions/uniform/struct.Uniform.html#method.new /// [`Uniform::sample_single`]: distributions/uniform/struct.Uniform.html#method.sample_single - #[inline] fn gen_range(&mut self, low: T, high: T) -> T { Uniform::sample_single(low, high, self) } @@ -371,7 +369,6 @@ pub trait Rng: RngCore { /// // distribution can be inferred. /// let y = rng.sample::(Uniform::new(10, 15)); /// ``` - #[inline] fn sample>(&mut self, distr: D) -> T { distr.sample(self) } @@ -403,7 +400,6 @@ pub trait Rng: RngCore { /// println!("Not a 6; rolling again!"); /// } /// ``` - #[inline] fn sample_iter<'a, T, D: Distribution>(&'a mut self, distr: &'a D) -> distributions::DistIter<'a, D, Self, T> where Self: Sized { @@ -897,7 +893,6 @@ pub fn weak_rng() -> XorShiftRng { /// [`thread_rng`]: fn.thread_rng.html /// [`Standard`]: distributions/struct.Standard.html #[cfg(feature="std")] -#[inline] pub fn random() -> T where Standard: Distribution { thread_rng().gen() } @@ -918,7 +913,6 @@ pub fn random() -> T where Standard: Distribution { /// println!("{:?}", sample); /// ``` #[cfg(feature="std")] -#[inline(always)] #[deprecated(since="0.4.0", note="renamed to seq::sample_iter")] pub fn sample(rng: &mut R, iterable: I, amount: usize) -> Vec where I: IntoIterator,