From e6fb622af103a0d642a9b54c78c9183aec5c0d8b Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 18 Nov 2013 22:27:48 +1100 Subject: [PATCH] std::rand: wrappers for floats from [0,1] and (0,1). Provide `Closed01` and `Open01` that generate directly from the closed/open intervals from 0 to 1, in contrast to the plain impls for f32 and f64 which generate the half-open [0,1). Fixes #7755. --- src/libstd/rand/distributions/gamma.rs | 15 +--- src/libstd/rand/distributions/mod.rs | 10 +-- src/libstd/rand/mod.rs | 40 +++++++++++ src/libstd/rand/rand_impls.rs | 96 ++++++++++++++++++++------ 4 files changed, 122 insertions(+), 39 deletions(-) diff --git a/src/libstd/rand/distributions/gamma.rs b/src/libstd/rand/distributions/gamma.rs index 735b083df75f9..7e8c193cb777e 100644 --- a/src/libstd/rand/distributions/gamma.rs +++ b/src/libstd/rand/distributions/gamma.rs @@ -10,7 +10,7 @@ //! The Gamma distribution. -use rand::Rng; +use rand::{Rng, Open01}; use super::{IndependentSample, Sample, StandardNormal, Exp}; use num; @@ -142,11 +142,7 @@ impl IndependentSample for Gamma { } impl IndependentSample for GammaSmallShape { fn ind_sample(&self, rng: &mut R) -> f64 { - // Need (0, 1) here. - let mut u = rng.gen::(); - while u == 0. { - u = rng.gen(); - } + let u = *rng.gen::>(); self.large_shape.ind_sample(rng) * num::pow(u, self.inv_shape) } @@ -161,12 +157,7 @@ impl IndependentSample for GammaLargeShape { } let v = v_cbrt * v_cbrt * v_cbrt; - // Need (0, 1) here, not [0, 1). This would be faster if - // we were generating an f64 in (0, 1) directly. - let mut u = rng.gen::(); - while u == 0.0 { - u = rng.gen(); - } + let u = *rng.gen::>(); let x_sqr = x * x; if u < 1.0 - 0.0331 * x_sqr * x_sqr || diff --git a/src/libstd/rand/distributions/mod.rs b/src/libstd/rand/distributions/mod.rs index c832438647017..9697fc22ccd5f 100644 --- a/src/libstd/rand/distributions/mod.rs +++ b/src/libstd/rand/distributions/mod.rs @@ -23,7 +23,7 @@ that do not need to record state. use iter::range; use option::{Some, None}; use num; -use rand::{Rng,Rand}; +use rand::{Rng, Rand, Open01}; use clone::Clone; pub use self::range::Range; @@ -276,10 +276,12 @@ impl Rand for StandardNormal { let mut x = 1.0f64; let mut y = 0.0f64; - // FIXME #7755: infinities? while -2.0 * y < x * x { - x = rng.gen::().ln() / ziggurat_tables::ZIG_NORM_R; - y = rng.gen::().ln(); + let x_ = *rng.gen::>(); + let y_ = *rng.gen::>(); + + x = x_.ln() / ziggurat_tables::ZIG_NORM_R; + y = y_.ln(); } if u < 0.0 { x - ziggurat_tables::ZIG_NORM_R } else { ziggurat_tables::ZIG_NORM_R - x } diff --git a/src/libstd/rand/mod.rs b/src/libstd/rand/mod.rs index e320898175180..a2b555028faaf 100644 --- a/src/libstd/rand/mod.rs +++ b/src/libstd/rand/mod.rs @@ -647,6 +647,46 @@ pub fn random() -> T { task_rng().gen() } +/// A wrapper for generating floating point numbers uniformly in the +/// open interval `(0,1)` (not including either endpoint). +/// +/// Use `Closed01` for the closed interval `[0,1]`, and the default +/// `Rand` implementation for `f32` and `f64` for the half-open +/// `[0,1)`. +/// +/// # Example +/// ```rust +/// use std::rand::{random, Open01}; +/// +/// fn main() { +/// println!("f32 from (0,1): {}", *random::>()); +/// +/// let x: Open01 = random(); +/// println!("f64 from (0,1): {}", *x); +/// } +/// ``` +pub struct Open01(F); + +/// A wrapper for generating floating point numbers uniformly in the +/// closed interval `[0,1]` (including both endpoints). +/// +/// Use `Open01` for the closed interval `(0,1)`, and the default +/// `Rand` implementation of `f32` and `f64` for the half-open +/// `[0,1)`. +/// +/// # Example +/// ```rust +/// use std::rand::{random, Closed01}; +/// +/// fn main() { +/// println!("f32 from [0,1]: {}", *random::>()); +/// +/// let x: Closed01 = random(); +/// println!("f64 from [0,1]: {}", *x); +/// } +/// ``` +pub struct Closed01(F); + #[cfg(test)] mod test { use iter::{Iterator, range}; diff --git a/src/libstd/rand/rand_impls.rs b/src/libstd/rand/rand_impls.rs index aad0d4e861c58..ff722b739a86a 100644 --- a/src/libstd/rand/rand_impls.rs +++ b/src/libstd/rand/rand_impls.rs @@ -94,32 +94,52 @@ impl Rand for u64 { } } -impl Rand for f32 { - /// A random `f32` in the range `[0, 1)`, using 24 bits of - /// precision. - #[inline] - fn rand(rng: &mut R) -> f32 { - // using any more than 24 bits will cause (e.g.) 0xffff_ffff - // to correspond to 1 exactly, so we need to drop 8 to - // guarantee the open end. +macro_rules! float_impls { + ($mod_name:ident, $ty:ty, $mantissa_bits:expr, $method_name:ident, $ignored_bits:expr) => { + mod $mod_name { + use rand::{Rand, Rng, Open01, Closed01}; - static SCALE: f32 = (1u32 << 24) as f32; - (rng.next_u32() >> 8) as f32 / SCALE - } -} - -impl Rand for f64 { - /// A random `f64` in the range `[0, 1)`, using 53 bits of - /// precision. - #[inline] - fn rand(rng: &mut R) -> f64 { - // as for f32, but using more bits. + static SCALE: $ty = (1u64 << $mantissa_bits) as $ty; - static SCALE: f64 = (1u64 << 53) as f64; - (rng.next_u64() >> 11) as f64 / SCALE + impl Rand for $ty { + /// Generate a floating point number in the half-open + /// interval `[0,1)`. + /// + /// See `Closed01` for the closed interval `[0,1]`, + /// and `Open01` for the open interval `(0,1)`. + #[inline] + fn rand(rng: &mut R) -> $ty { + // using any more than `mantissa_bits` bits will + // cause (e.g.) 0xffff_ffff to correspond to 1 + // exactly, so we need to drop some (8 for f32, 11 + // for f64) to guarantee the open end. + (rng.$method_name() >> $ignored_bits) as $ty / SCALE + } + } + impl Rand for Open01<$ty> { + #[inline] + fn rand(rng: &mut R) -> Open01<$ty> { + // add a small amount (specifically 2 bits below + // the precision of f64/f32 at 1.0), so that small + // numbers are larger than 0, but large numbers + // aren't pushed to/above 1. + Open01(((rng.$method_name() >> $ignored_bits) as $ty + 0.25) / SCALE) + } + } + impl Rand for Closed01<$ty> { + #[inline] + fn rand(rng: &mut R) -> Closed01<$ty> { + // divide by the maximum value of the numerator to + // get a non-zero probability of getting exactly + // 1.0. + Closed01((rng.$method_name() >> $ignored_bits) as $ty / (SCALE - 1.0)) + } + } + } } } - +float_impls! { f64_rand_impls, f64, 53, next_u64, 11 } +float_impls! { f32_rand_impls, f32, 24, next_u32, 8 } impl Rand for char { #[inline] @@ -206,7 +226,10 @@ impl Rand for @T { #[cfg(test)] mod tests { - use rand::Rng; + use rand::{Rng, task_rng, Open01, Closed01}; + use iter::range; + use option::{None, Some}; + struct ConstantRng(u64); impl Rng for ConstantRng { fn next_u32(&mut self) -> u32 { @@ -216,9 +239,36 @@ mod tests { **self } } + fn floating_point_edge_cases() { // the test for exact equality is correct here. assert!(ConstantRng(0xffff_ffff).gen::() != 1.0) assert!(ConstantRng(0xffff_ffff_ffff_ffff).gen::() != 1.0) } + + fn rand_open() { + // this is unlikely to catch an incorrect implementation that + // generates exactly 0 or 1, but it keeps it sane. + let mut rng = task_rng(); + for _ in range(0, 1_000) { + // strict inequalities + let f = *rng.gen::>(); + assert!(0.0 < f && f < 1.0); + + let f = *rng.gen::>(); + assert!(0.0 < f && f < 1.0); + } + } + + fn rand_closed() { + let mut rng = task_rng(); + for _ in range(0, 1_000) { + // strict inequalities + let f = *rng.gen::>(); + assert!(0.0 <= f && f <= 1.0); + + let f = *rng.gen::>(); + assert!(0.0 <= f && f <= 1.0); + } + } }