Skip to content

Commit 0950203

Browse files
authored
Merge pull request #420 from pitdicker/closed_open_float_conversion
Switch the `Standard` distribution for floats to [0, 1)
2 parents 30834a5 + 47e9640 commit 0950203

File tree

5 files changed

+169
-76
lines changed

5 files changed

+169
-76
lines changed

benches/distributions.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ 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);
101+
distr_float!(distr_openclosed01_f32, f32, OpenClosed01);
102+
distr_float!(distr_openclosed01_f64, f64, OpenClosed01);
99103

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

src/distributions/float.rs

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

17+
/// A distribution to sample floating point numbers uniformly in the half-open
18+
/// interval `(0, 1]`, i.e. including 1 but not 0.
19+
///
20+
/// All values that can be generated are of the form `n * ε/2`. For `f32`
21+
/// the 23 most significant random bits of a `u32` are used and for `f64` the
22+
/// 53 most significant bits of a `u64` are used. The conversion uses the
23+
/// multiplicative method.
24+
///
25+
/// See also: [`Standard`] which samples from `[0, 1)`, [`Open01`]
26+
/// which samples from `(0, 1)` and [`Uniform`] which samples from arbitrary
27+
/// ranges.
28+
///
29+
/// # Example
30+
/// ```rust
31+
/// use rand::{thread_rng, Rng};
32+
/// use rand::distributions::OpenClosed01;
33+
///
34+
/// let val: f32 = thread_rng().sample(OpenClosed01);
35+
/// println!("f32 from (0, 1): {}", val);
36+
/// ```
37+
///
38+
/// [`Standard`]: struct.Standard.html
39+
/// [`Open01`]: struct.Open01.html
40+
/// [`Uniform`]: uniform/struct.Uniform.html
41+
#[derive(Clone, Copy, Debug)]
42+
pub struct OpenClosed01;
43+
44+
/// A distribution to sample floating point numbers uniformly in the open
45+
/// interval `(0, 1)`, i.e. not including either endpoint.
46+
///
47+
/// All values that can be generated are of the form `n * ε + ε/2`. For `f32`
48+
/// the 22 most significant random bits of an `u32` are used, for `f64` 52 from
49+
/// an `u64`. The conversion uses a transmute-based method.
50+
///
51+
/// See also: [`Standard`] which samples from `[0, 1)`, [`OpenClosed01`]
52+
/// which samples from `(0, 1]` and [`Uniform`] which samples from arbitrary
53+
/// ranges.
54+
///
55+
/// # Example
56+
/// ```rust
57+
/// use rand::{thread_rng, Rng};
58+
/// use rand::distributions::Open01;
59+
///
60+
/// let val: f32 = thread_rng().sample(Open01);
61+
/// println!("f32 from (0, 1): {}", val);
62+
/// ```
63+
///
64+
/// [`Standard`]: struct.Standard.html
65+
/// [`OpenClosed01`]: struct.OpenClosed01.html
66+
/// [`Uniform`]: uniform/struct.Uniform.html
67+
#[derive(Clone, Copy, Debug)]
68+
pub struct Open01;
69+
70+
1771
pub(crate) trait IntoFloat {
1872
type F;
1973

@@ -29,8 +83,7 @@ pub(crate) trait IntoFloat {
2983
}
3084

3185
macro_rules! float_impls {
32-
($ty:ty, $uty:ty, $fraction_bits:expr, $exponent_bias:expr,
33-
$next_u:ident) => {
86+
($ty:ty, $uty:ty, $fraction_bits:expr, $exponent_bias:expr) => {
3487
impl IntoFloat for $uty {
3588
type F = $ty;
3689
#[inline(always)]
@@ -43,47 +96,111 @@ macro_rules! float_impls {
4396
}
4497

4598
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.
4899
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
100+
// Multiply-based method; 24/53 random bits; [0, 1) interval.
101+
// We use the most significant bits because for simple RNGs
102+
// those are usually more random.
103+
let float_size = mem::size_of::<$ty>() * 8;
104+
let precision = $fraction_bits + 1;
105+
let scale = 1.0 / ((1 as $uty << precision) as $ty);
106+
107+
let value: $uty = rng.gen();
108+
scale * (value >> (float_size - precision)) as $ty
109+
}
110+
}
111+
112+
impl Distribution<$ty> for OpenClosed01 {
113+
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
114+
// Multiply-based method; 24/53 random bits; (0, 1] interval.
115+
// We use the most significant bits because for simple RNGs
116+
// those are usually more random.
117+
let float_size = mem::size_of::<$ty>() * 8;
118+
let precision = $fraction_bits + 1;
119+
let scale = 1.0 / ((1 as $uty << precision) as $ty);
120+
121+
let value: $uty = rng.gen();
122+
let value = value >> (float_size - precision);
123+
// Add 1 to shift up; will not overflow because of right-shift:
124+
scale * (value + 1) as $ty
125+
}
126+
}
127+
128+
impl Distribution<$ty> for Open01 {
129+
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
130+
// Transmute-based method; 23/52 random bits; (0, 1) interval.
131+
// We use the most significant bits because for simple RNGs
132+
// those are usually more random.
49133
const EPSILON: $ty = 1.0 / (1u64 << $fraction_bits) as $ty;
50134
let float_size = mem::size_of::<$ty>() * 8;
51135

52-
let value = rng.$next_u();
136+
let value: $uty = rng.gen();
53137
let fraction = value >> (float_size - $fraction_bits);
54138
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
55139
}
56140
}
57141
}
58142
}
59-
float_impls! { f32, u32, 23, 127, next_u32 }
60-
float_impls! { f64, u64, 52, 1023, next_u64 }
143+
float_impls! { f32, u32, 23, 127 }
144+
float_impls! { f64, u64, 52, 1023 }
61145

62146

63147
#[cfg(test)]
64148
mod tests {
65149
use Rng;
150+
use distributions::{Open01, OpenClosed01};
66151
use mock::StepRng;
67152

68153
const EPSILON32: f32 = ::core::f32::EPSILON;
69154
const EPSILON64: f64 = ::core::f64::EPSILON;
70155

71156
#[test]
72-
fn floating_point_edge_cases() {
157+
fn standard_fp_edge_cases() {
73158
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);
159+
assert_eq!(zeros.gen::<f32>(), 0.0);
160+
assert_eq!(zeros.gen::<f64>(), 0.0);
76161

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

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

85168
let mut max = StepRng::new(!0, 0);
86169
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
87170
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
88171
}
172+
173+
#[test]
174+
fn openclosed01_edge_cases() {
175+
let mut zeros = StepRng::new(0, 0);
176+
assert_eq!(zeros.sample::<f32, _>(OpenClosed01), 0.0 + EPSILON32 / 2.0);
177+
assert_eq!(zeros.sample::<f64, _>(OpenClosed01), 0.0 + EPSILON64 / 2.0);
178+
179+
let mut one32 = StepRng::new(1 << 8, 0);
180+
assert_eq!(one32.sample::<f32, _>(OpenClosed01), EPSILON32);
181+
182+
let mut one64 = StepRng::new(1 << 11, 0);
183+
assert_eq!(one64.sample::<f64, _>(OpenClosed01), EPSILON64);
184+
185+
let mut max = StepRng::new(!0, 0);
186+
assert_eq!(max.sample::<f32, _>(OpenClosed01), 1.0);
187+
assert_eq!(max.sample::<f64, _>(OpenClosed01), 1.0);
188+
}
189+
190+
#[test]
191+
fn open01_edge_cases() {
192+
let mut zeros = StepRng::new(0, 0);
193+
assert_eq!(zeros.sample::<f32, _>(Open01), 0.0 + EPSILON32 / 2.0);
194+
assert_eq!(zeros.sample::<f64, _>(Open01), 0.0 + EPSILON64 / 2.0);
195+
196+
let mut one32 = StepRng::new(1 << 9, 0);
197+
assert_eq!(one32.sample::<f32, _>(Open01), EPSILON32 / 2.0 * 3.0);
198+
199+
let mut one64 = StepRng::new(1 << 12, 0);
200+
assert_eq!(one64.sample::<f64, _>(Open01), EPSILON64 / 2.0 * 3.0);
201+
202+
let mut max = StepRng::new(!0, 0);
203+
assert_eq!(max.sample::<f32, _>(Open01), 1.0 - EPSILON32 / 2.0);
204+
assert_eq!(max.sample::<f64, _>(Open01), 1.0 - EPSILON64 / 2.0);
205+
}
89206
}

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: 26 additions & 54 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::{OpenClosed01, Open01};
3031
#[deprecated(since="0.5.0", note="use Uniform instead")]
3132
pub use self::uniform::Uniform as Range;
3233
#[cfg(feature="std")]
@@ -227,16 +228,13 @@ impl<'a, D, R, T> Iterator for DistIter<'a, D, R, T>
227228
}
228229

229230

230-
/// A generic random value distribution. Generates values for various types
231-
/// with numerically uniform distribution.
231+
/// A generic random value distribution, implemented for many primitive types.
232+
/// Usually generates values with a numerically uniform distribution, and with a
233+
/// range appropriate to the type.
232234
///
233-
/// For floating-point numbers, this generates values from the open range
234-
/// `(0, 1)` (i.e. excluding 0.0 and 1.0).
235-
///
236235
/// ## Built-in Implementations
237236
///
238-
/// This crate implements the distribution `Standard` for various primitive
239-
/// types. Assuming the provided `Rng` is well-behaved, these implementations
237+
/// Assuming the provided `Rng` is well-behaved, these implementations
240238
/// generate values with the following ranges and distributions:
241239
///
242240
/// * Integers (`i32`, `u32`, `isize`, `usize`, etc.): Uniformly distributed
@@ -247,71 +245,45 @@ impl<'a, D, R, T> Iterator for DistIter<'a, D, R, T>
247245
/// unassigned/reserved code points.
248246
/// * `bool`: Generates `false` or `true`, each with probability 0.5.
249247
/// * Floating point types (`f32` and `f64`): Uniformly distributed in the
250-
/// open range `(0, 1)`.
248+
/// half-open range `[0, 1)`. See notes below.
251249
///
252250
/// The following aggregate types also implement the distribution `Standard` as
253251
/// long as their component types implement it:
254252
///
255253
/// * Tuples and arrays: Each element of the tuple or array is generated
256254
/// independently, using the `Standard` distribution recursively.
257-
/// * `Option<T>`: Returns `None` with probability 0.5; otherwise generates a
258-
/// random `T` and returns `Some(T)`.
255+
/// * `Option<T>` where `Standard` is implemented for `T`: Returns `None` with
256+
/// probability 0.5; otherwise generates a random `x: T` and returns `Some(x)`.
259257
///
260258
/// # Example
261259
/// ```rust
262260
/// use rand::{FromEntropy, SmallRng, Rng};
263261
/// use rand::distributions::Standard;
264262
///
265263
/// let val: f32 = SmallRng::from_entropy().sample(Standard);
266-
/// println!("f32 from (0,1): {}", val);
267-
/// ```
268-
///
269-
/// With dynamic dispatch (type erasure of `Rng`):
270-
///
271-
/// ```rust
272-
/// use rand::{thread_rng, Rng, RngCore};
273-
/// use rand::distributions::Standard;
274-
///
275-
/// let mut rng = thread_rng();
276-
/// let erased_rng: &mut RngCore = &mut rng;
277-
/// let val: f32 = erased_rng.sample(Standard);
278-
/// println!("f32 from (0, 1): {}", val);
264+
/// println!("f32 from [0, 1): {}", val);
279265
/// ```
280266
///
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:
267+
/// # Floating point implementation
268+
/// The floating point implementations for `Standard` generate a random value in
269+
/// the half-open interval `[0, 1)`, i.e. including 0 but not 1.
287270
///
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.
271+
/// All values that can be generated are of the form `n * ε/2`. For `f32`
272+
/// the 23 most significant random bits of a `u32` are used and for `f64` the
273+
/// 53 most significant bits of a `u64` are used. The conversion uses the
274+
/// multiplicative method: `(rng.gen::<$uty>() >> N) as $ty * (ε/2)`.
296275
///
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.
276+
/// See also: [`Open01`] which samples from `(0, 1)`, [`OpenClosed01`] which
277+
/// samples from `(0, 1]` and `Rng::gen_range(0, 1)` which also samples from
278+
/// `[0, 1)`. Note that `Open01` and `gen_range` (which uses [`Uniform`]) use
279+
/// transmute-based methods which yield 1 bit less precision but may perform
280+
/// faster on some architectures (on modern Intel CPUs all methods have
281+
/// approximately equal performance).
300282
///
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};
306-
///
307-
/// let mut rng = thread_rng();
308-
/// let val = rng.gen_range(0.0f32, 1.0);
309-
/// println!("f32 from [0, 1): {}", val);
310-
/// ```
311-
///
312-
/// [`Exp1`]: struct.Exp1.html
313-
/// [`StandardNormal`]: struct.StandardNormal.html
314-
#[derive(Debug)]
283+
/// [`Open01`]: struct.Open01.html
284+
/// [`OpenClosed01`]: struct.OpenClosed01.html
285+
/// [`Uniform`]: uniform/struct.Uniform.html
286+
#[derive(Clone, Copy, Debug)]
315287
pub struct Standard;
316288

317289
#[allow(deprecated)]

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)