Skip to content

Commit caaaf0a

Browse files
committed
Switch the Standard distribution for floats to [0, 1)
1 parent 35a9476 commit caaaf0a

File tree

5 files changed

+96
-52
lines changed

5 files changed

+96
-52
lines changed

benches/distributions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ distr!(distr_standard_codepoint, char, Standard);
9696

9797
distr_float!(distr_standard_f32, f32, Standard);
9898
distr_float!(distr_standard_f64, f64, Standard);
99+
distr_float!(distr_open01_f32, f32, Open01);
100+
distr_float!(distr_open01_f64, f64, Open01);
99101

100102
// distributions
101103
distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56));

src/distributions/float.rs

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,30 @@ use core::mem;
1414
use Rng;
1515
use distributions::{Distribution, Standard};
1616

17+
/// A distribution to sample floating point numbers uniformly in the open
18+
/// interval `(0, 1)`, i.e. not including either endpoint.
19+
///
20+
/// All values that can be generated are of the form `n * ε + ε/2`. For `f32`
21+
/// the 22 most significant random bits of an `u32` are used, for `f64` 52 from
22+
/// an `u64`. The conversion uses a transmute-based method.
23+
///
24+
/// To sample from the half-open range `[0, 1)` instead, use the [`Standard`]
25+
/// distribution.
26+
///
27+
/// # Example
28+
/// ```rust
29+
/// use rand::{thread_rng, Rng};
30+
/// use rand::distributions::Open01;
31+
///
32+
/// let val: f32 = thread_rng().sample(Open01);
33+
/// println!("f32 from (0, 1): {}", val);
34+
/// ```
35+
///
36+
/// [`Standard`]: struct.Standard.html
37+
#[derive(Clone, Copy, Debug)]
38+
pub struct Open01;
39+
40+
1741
pub(crate) trait IntoFloat {
1842
type F;
1943

@@ -29,8 +53,7 @@ pub(crate) trait IntoFloat {
2953
}
3054

3155
macro_rules! float_impls {
32-
($ty:ty, $uty:ty, $fraction_bits:expr, $exponent_bias:expr,
33-
$next_u:ident) => {
56+
($ty:ty, $uty:ty, $fraction_bits:expr, $exponent_bias:expr) => {
3457
impl IntoFloat for $uty {
3558
type F = $ty;
3659
#[inline(always)]
@@ -43,26 +66,42 @@ macro_rules! float_impls {
4366
}
4467

4568
impl Distribution<$ty> for Standard {
46-
/// Generate a floating point number in the open interval `(0, 1)`
47-
/// (not including either endpoint) with a uniform distribution.
4869
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
70+
// Multiply-based method; 24/53 random bits; [0, 1) interval.
71+
// We use the most significant bits because for simple RNGs
72+
// those are usually more random.
73+
let float_size = mem::size_of::<$ty>() * 8;
74+
let precision = $fraction_bits + 1;
75+
let scale = 1.0 / ((1 as $uty << precision) as $ty);
76+
77+
let value: $uty = rng.gen();
78+
scale * (value >> (float_size - precision)) as $ty
79+
}
80+
}
81+
82+
impl Distribution<$ty> for Open01 {
83+
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
84+
// Transmute-based method; 23/52 random bits; (0, 1) interval.
85+
// We use the most significant bits because for simple RNGs
86+
// those are usually more random.
4987
const EPSILON: $ty = 1.0 / (1u64 << $fraction_bits) as $ty;
5088
let float_size = mem::size_of::<$ty>() * 8;
5189

52-
let value = rng.$next_u();
90+
let value: $uty = rng.gen();
5391
let fraction = value >> (float_size - $fraction_bits);
5492
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
5593
}
5694
}
5795
}
5896
}
59-
float_impls! { f32, u32, 23, 127, next_u32 }
60-
float_impls! { f64, u64, 52, 1023, next_u64 }
97+
float_impls! { f32, u32, 23, 127 }
98+
float_impls! { f64, u64, 52, 1023 }
6199

62100

63101
#[cfg(test)]
64102
mod tests {
65103
use Rng;
104+
use distributions::Open01;
66105
use mock::StepRng;
67106

68107
const EPSILON32: f32 = ::core::f32::EPSILON;
@@ -71,19 +110,34 @@ mod tests {
71110
#[test]
72111
fn floating_point_edge_cases() {
73112
let mut zeros = StepRng::new(0, 0);
74-
assert_eq!(zeros.gen::<f32>(), 0.0 + EPSILON32 / 2.0);
75-
assert_eq!(zeros.gen::<f64>(), 0.0 + EPSILON64 / 2.0);
113+
assert_eq!(zeros.gen::<f32>(), 0.0);
114+
assert_eq!(zeros.gen::<f64>(), 0.0);
76115

77-
let mut one = StepRng::new(1 << 9, 0);
78-
let one32 = one.gen::<f32>();
79-
assert!(EPSILON32 < one32 && one32 < EPSILON32 * 2.0);
116+
let mut one32 = StepRng::new(1 << 8, 0);
117+
assert_eq!(one32.gen::<f32>(), EPSILON32 / 2.0);
80118

81-
let mut one = StepRng::new(1 << 12, 0);
82-
let one64 = one.gen::<f64>();
83-
assert!(EPSILON64 < one64 && one64 < EPSILON64 * 2.0);
119+
let mut one64 = StepRng::new(1 << 11, 0);
120+
assert_eq!(one64.gen::<f64>(), EPSILON64 / 2.0);
84121

85122
let mut max = StepRng::new(!0, 0);
86123
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
87124
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
88125
}
126+
127+
#[test]
128+
fn open01_edge_cases() {
129+
let mut zeros = StepRng::new(0, 0);
130+
assert_eq!(zeros.sample::<f32, _>(Open01), 0.0 + EPSILON32 / 2.0);
131+
assert_eq!(zeros.sample::<f64, _>(Open01), 0.0 + EPSILON64 / 2.0);
132+
133+
let mut one32 = StepRng::new(1 << 9, 0);
134+
assert_eq!(one32.sample::<f32, _>(Open01), EPSILON32 / 2.0 * 3.0);
135+
136+
let mut one64 = StepRng::new(1 << 12, 0);
137+
assert_eq!(one64.sample::<f64, _>(Open01), EPSILON64 / 2.0 * 3.0);
138+
139+
let mut max = StepRng::new(!0, 0);
140+
assert_eq!(max.sample::<f32, _>(Open01), 1.0 - EPSILON32 / 2.0);
141+
assert_eq!(max.sample::<f64, _>(Open01), 1.0 - EPSILON64 / 2.0);
142+
}
89143
}

src/distributions/gamma.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use self::ChiSquaredRepr::*;
1515

1616
use Rng;
1717
use distributions::normal::StandardNormal;
18-
use distributions::{Distribution, Exp};
18+
use distributions::{Distribution, Exp, Open01};
1919

2020
/// The Gamma distribution `Gamma(shape, scale)` distribution.
2121
///
@@ -142,7 +142,7 @@ impl Distribution<f64> for Gamma {
142142
}
143143
impl Distribution<f64> for GammaSmallShape {
144144
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
145-
let u: f64 = rng.gen();
145+
let u: f64 = rng.sample(Open01);
146146

147147
self.large_shape.sample(rng) * u.powf(self.inv_shape)
148148
}
@@ -157,7 +157,7 @@ impl Distribution<f64> for GammaLargeShape {
157157
}
158158

159159
let v = v_cbrt * v_cbrt * v_cbrt;
160-
let u: f64 = rng.gen();
160+
let u: f64 = rng.sample(Open01);
161161

162162
let x_sqr = x * x;
163163
if u < 1.0 - 0.0331 * x_sqr * x_sqr ||

src/distributions/mod.rs

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use Rng;
2727

2828
pub use self::other::Alphanumeric;
2929
pub use self::uniform::Uniform;
30+
pub use self::float::Open01;
3031
#[deprecated(since="0.5.0", note="use Uniform instead")]
3132
pub use self::uniform::Uniform as Range;
3233
#[cfg(feature="std")]
@@ -247,7 +248,7 @@ impl<'a, D, R, T> Iterator for DistIter<'a, D, R, T>
247248
/// unassigned/reserved code points.
248249
/// * `bool`: Generates `false` or `true`, each with probability 0.5.
249250
/// * Floating point types (`f32` and `f64`): Uniformly distributed in the
250-
/// open range `(0, 1)`.
251+
/// half-open range `[0, 1)`.
251252
///
252253
/// The following aggregate types also implement the distribution `Standard` as
253254
/// long as their component types implement it:
@@ -263,7 +264,7 @@ impl<'a, D, R, T> Iterator for DistIter<'a, D, R, T>
263264
/// use rand::distributions::Standard;
264265
///
265266
/// let val: f32 = SmallRng::from_entropy().sample(Standard);
266-
/// println!("f32 from (0,1): {}", val);
267+
/// println!("f32 from [0, 1): {}", val);
267268
/// ```
268269
///
269270
/// With dynamic dispatch (type erasure of `Rng`):
@@ -275,42 +276,29 @@ impl<'a, D, R, T> Iterator for DistIter<'a, D, R, T>
275276
/// let mut rng = thread_rng();
276277
/// let erased_rng: &mut RngCore = &mut rng;
277278
/// let val: f32 = erased_rng.sample(Standard);
278-
/// println!("f32 from (0, 1): {}", val);
279+
/// println!("f32 from [0, 1): {}", val);
279280
/// ```
280281
///
281-
/// # Open interval for floats
282-
/// In theory it is possible to choose between an open interval `(0, 1)`, and
283-
/// the half-open intervals `[0, 1)` and `(0, 1]`. All can give a distribution
284-
/// with perfectly uniform intervals. Many libraries in other programming
285-
/// languages default to the closed-open interval `[0, 1)`. We choose here to go
286-
/// with *open*, with the arguments:
282+
/// # Floating point implementation
283+
/// The floating point implementations for `Standard` generate a random value in
284+
/// the half-open interval [0, 1).
287285
///
288-
/// - The chance to generate a specific value, like exactly 0.0, is *tiny*. No
289-
/// (or almost no) sensible code relies on an exact floating-point value to be
290-
/// generated with a very small chance (1 in 2<sup>23</sup> (approx. 8
291-
/// million) for `f32`, and 1 in 2<sup>52</sup> for `f64`). What is relied on
292-
/// is having a uniform distribution and a mean of `0.5`.
293-
/// - Several common algorithms rely on never seeing the value `0.0` generated,
294-
/// i.e. they rely on an open interval. For example when the logarithm of the
295-
/// value is taken, or used as a devisor.
286+
/// All values that can be generated are multiples of ε/2. For `f32` the 23 most
287+
/// significant random bits of an `u32` are used, for `f64` 53 from an `u64`.
288+
/// The conversion uses the common multiply-based approach.
296289
///
297-
/// In other words, the guarantee some value *could* be generated is less useful
298-
/// than the guarantee some value (`0.0`) is never generated. That makes an open
299-
/// interval a nicer choice.
290+
/// The `Open01` distribution provides an alternative: it generates values in
291+
/// the open interval (0, 1), with one less random bit. It uses a
292+
/// transmute-based method for the conversion to a floating point value, which
293+
/// may be slightly faster on some architectures.
300294
///
301-
/// Consider using `Rng::gen_range` if you really need a half-open interval (as
302-
/// the ranges use a half-open interval). It has the same performance. Example:
303-
///
304-
/// ```
305-
/// use rand::{thread_rng, Rng};
295+
/// `Rng::gen_range(0, 1)` also uses the transmute-based method, but produces
296+
/// values in a half-open interval just like `Standard`.
306297
///
307-
/// let mut rng = thread_rng();
308-
/// let val = rng.gen_range(0.0f32, 1.0);
309-
/// println!("f32 from [0, 1): {}", val);
310-
/// ```
298+
/// If you wish to sample from the (0, 1] half-open interval consider using
299+
/// `1.0 - rng.gen()`.
311300
///
312-
/// [`Exp1`]: struct.Exp1.html
313-
/// [`StandardNormal`]: struct.StandardNormal.html
301+
/// [`Open01`]: struct.Open01.html
314302
#[derive(Debug)]
315303
pub struct Standard;
316304

src/distributions/normal.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//! The normal and derived distributions.
1212
1313
use Rng;
14-
use distributions::{ziggurat, ziggurat_tables, Distribution};
14+
use distributions::{ziggurat, ziggurat_tables, Distribution, Open01};
1515

1616
/// Samples floating-point numbers according to the normal distribution
1717
/// `N(0, 1)` (a.k.a. a standard normal, or Gaussian). This is equivalent to
@@ -55,8 +55,8 @@ impl Distribution<f64> for StandardNormal {
5555
let mut y = 0.0f64;
5656

5757
while -2.0 * y < x * x {
58-
let x_: f64 = rng.gen();
59-
let y_: f64 = rng.gen();
58+
let x_: f64 = rng.sample(Open01);
59+
let y_: f64 = rng.sample(Open01);
6060

6161
x = x_.ln() / ziggurat_tables::ZIG_NORM_R;
6262
y = y_.ln();

0 commit comments

Comments
 (0)