Skip to content

Commit e49be7a

Browse files
committed
rand: Add next_f64/f32 to Rng.
Some random number generates output floating point numbers directly, so by providing these methods all the functionality in librand is available with high-performance for these things. An example of such an is dSFMT (Double precision SIMD-oriented Fast Mersenne Twister). The choice to use the open interval [0, 1) has backing elsewhere, e.g. GSL (GNU Scientific Library) uses this range, and dSFMT supports generating this natively (I believe the most natural range for that library is [1, 2), but that is not totally sensible from a user perspective, and would trip people up). Fixes rust-lang/rfcs#425.
1 parent 3327ecc commit e49be7a

File tree

3 files changed

+56
-14
lines changed

3 files changed

+56
-14
lines changed

src/librand/distributions/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ fn ziggurat<R:Rng>(
227227
// creating a f64), so we might as well reuse some to save
228228
// generating a whole extra random number. (Seems to be 15%
229229
// faster.)
230+
//
231+
// This unfortunately misses out on the benefits of direct
232+
// floating point generation if an RNG like dSMFT is
233+
// used. (That is, such RNGs create floats directly, highly
234+
// efficiently and overload next_f32/f64, so by not calling it
235+
// this may be slower than it would be otherwise.)
236+
// FIXME: investigate/optimise for the above.
230237
let bits: u64 = rng.gen();
231238
let i = (bits & 0xff) as uint;
232239
let f = (bits >> 11) as f64 / SCALE;

src/librand/lib.rs

+40
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,46 @@ pub trait Rng {
7878
(self.next_u32() as u64 << 32) | (self.next_u32() as u64)
7979
}
8080

81+
/// Return the next random f32 selected from the half-open
82+
/// interval `[0, 1)`.
83+
///
84+
/// By default this is implemented in terms of `next_u32`, but a
85+
/// random number generator which can generate numbers satisfying
86+
/// the requirements directly can overload this for performance.
87+
/// It is required that the return value lies in `[0, 1)`.
88+
///
89+
/// See `Closed01` for the closed interval `[0,1]`, and
90+
/// `Open01` for the open interval `(0,1)`.
91+
fn next_f32(&mut self) -> f32 {
92+
const MANTISSA_BITS: uint = 24;
93+
const IGNORED_BITS: uint = 8;
94+
const SCALE: f32 = (1u64 << MANTISSA_BITS) as f32;
95+
96+
// using any more than `MANTISSA_BITS` bits will
97+
// cause (e.g.) 0xffff_ffff to correspond to 1
98+
// exactly, so we need to drop some (8 for f32, 11
99+
// for f64) to guarantee the open end.
100+
(self.next_u32() >> IGNORED_BITS) as f32 / SCALE
101+
}
102+
103+
/// Return the next random f64 selected from the half-open
104+
/// interval `[0, 1)`.
105+
///
106+
/// By default this is implemented in terms of `next_u64`, but a
107+
/// random number generator which can generate numbers satisfying
108+
/// the requirements directly can overload this for performance.
109+
/// It is required that the return value lies in `[0, 1)`.
110+
///
111+
/// See `Closed01` for the closed interval `[0,1]`, and
112+
/// `Open01` for the open interval `(0,1)`.
113+
fn next_f64(&mut self) -> f64 {
114+
const MANTISSA_BITS: uint = 53;
115+
const IGNORED_BITS: uint = 11;
116+
const SCALE: f64 = (1u64 << MANTISSA_BITS) as f64;
117+
118+
(self.next_u64() >> IGNORED_BITS) as f64 / SCALE
119+
}
120+
81121
/// Fill `dest` with random data.
82122
///
83123
/// This has a default implementation in terms of `next_u64` and

src/librand/rand_impls.rs

+9-14
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ impl Rand for u64 {
9696
}
9797

9898
macro_rules! float_impls {
99-
($mod_name:ident, $ty:ty, $mantissa_bits:expr, $method_name:ident, $ignored_bits:expr) => {
99+
($mod_name:ident, $ty:ty, $mantissa_bits:expr, $method_name:ident) => {
100100
mod $mod_name {
101101
use {Rand, Rng, Open01, Closed01};
102102

103-
static SCALE: $ty = (1u64 << $mantissa_bits) as $ty;
103+
const SCALE: $ty = (1u64 << $mantissa_bits) as $ty;
104104

105105
impl Rand for $ty {
106106
/// Generate a floating point number in the half-open
@@ -110,11 +110,7 @@ macro_rules! float_impls {
110110
/// and `Open01` for the open interval `(0,1)`.
111111
#[inline]
112112
fn rand<R: Rng>(rng: &mut R) -> $ty {
113-
// using any more than `mantissa_bits` bits will
114-
// cause (e.g.) 0xffff_ffff to correspond to 1
115-
// exactly, so we need to drop some (8 for f32, 11
116-
// for f64) to guarantee the open end.
117-
(rng.$method_name() >> $ignored_bits) as $ty / SCALE
113+
rng.$method_name()
118114
}
119115
}
120116
impl Rand for Open01<$ty> {
@@ -124,23 +120,22 @@ macro_rules! float_impls {
124120
// the precision of f64/f32 at 1.0), so that small
125121
// numbers are larger than 0, but large numbers
126122
// aren't pushed to/above 1.
127-
Open01(((rng.$method_name() >> $ignored_bits) as $ty + 0.25) / SCALE)
123+
Open01(rng.$method_name() + 0.25 / SCALE)
128124
}
129125
}
130126
impl Rand for Closed01<$ty> {
131127
#[inline]
132128
fn rand<R: Rng>(rng: &mut R) -> Closed01<$ty> {
133-
// divide by the maximum value of the numerator to
134-
// get a non-zero probability of getting exactly
135-
// 1.0.
136-
Closed01((rng.$method_name() >> $ignored_bits) as $ty / (SCALE - 1.0))
129+
// rescale so that 1.0 - epsilon becomes 1.0
130+
// precisely.
131+
Closed01(rng.$method_name() * SCALE / (SCALE - 1.0))
137132
}
138133
}
139134
}
140135
}
141136
}
142-
float_impls! { f64_rand_impls, f64, 53, next_u64, 11 }
143-
float_impls! { f32_rand_impls, f32, 24, next_u32, 8 }
137+
float_impls! { f64_rand_impls, f64, 53, next_f64 }
138+
float_impls! { f32_rand_impls, f32, 24, next_f32 }
144139

145140
impl Rand for char {
146141
#[inline]

0 commit comments

Comments
 (0)