-
-
Notifications
You must be signed in to change notification settings - Fork 456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Port new Range implementation and only have one uniform float distribution #274
Changes from 4 commits
741c310
526667e
68bb4aa
4a9c465
616861b
c90fee7
783277a
7b968e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,203 +14,86 @@ use core::mem; | |
use Rng; | ||
use distributions::{Distribution, Uniform}; | ||
|
||
|
||
/// A distribution to sample floating point numbers uniformly in the open | ||
/// interval `(0, 1)` (not including either endpoint). | ||
/// | ||
/// See also: [`Closed01`] for the closed `[0, 1]`; [`Uniform`] for the | ||
/// half-open `[0, 1)`. | ||
/// | ||
/// # Example | ||
/// ```rust | ||
/// use rand::{weak_rng, Rng}; | ||
/// use rand::distributions::Open01; | ||
/// | ||
/// let val: f32 = weak_rng().sample(Open01); | ||
/// println!("f32 from (0,1): {}", val); | ||
/// ``` | ||
/// | ||
/// [`Uniform`]: struct.Uniform.html | ||
/// [`Closed01`]: struct.Closed01.html | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Open01; | ||
|
||
/// A distribution to sample floating point numbers uniformly in the closed | ||
/// interval `[0, 1]` (including both endpoints). | ||
/// | ||
/// See also: [`Open01`] for the open `(0, 1)`; [`Uniform`] for the half-open | ||
/// `[0, 1)`. | ||
/// | ||
/// # Example | ||
/// ```rust | ||
/// use rand::{weak_rng, Rng}; | ||
/// use rand::distributions::Closed01; | ||
/// | ||
/// let val: f32 = weak_rng().sample(Closed01); | ||
/// println!("f32 from [0,1]: {}", val); | ||
/// ``` | ||
/// | ||
/// [`Uniform`]: struct.Uniform.html | ||
/// [`Open01`]: struct.Open01.html | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Closed01; | ||
|
||
|
||
// Return the next random f32 selected from the half-open | ||
// interval `[0, 1)`. | ||
// | ||
// This uses a technique described by Saito and Matsumoto at | ||
// MCQMC'08. Given that the IEEE floating point numbers are | ||
// uniformly distributed over [1,2), we generate a number in | ||
// this range and then offset it onto the range [0,1). Our | ||
// choice of bits (masking v. shifting) is arbitrary and | ||
// should be immaterial for high quality generators. For low | ||
// quality generators (ex. LCG), prefer bitshifting due to | ||
// correlation between sequential low order bits. | ||
// | ||
// See: | ||
// A PRNG specialized in double precision floating point numbers using | ||
// an affine transition | ||
// | ||
// * <http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/ARTICLES/dSFMT.pdf> | ||
// * <http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/dSFMT-slide-e.pdf> | ||
impl Distribution<f32> for Uniform { | ||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f32 { | ||
const UPPER_MASK: u32 = 0x3F800000; | ||
const LOWER_MASK: u32 = 0x7FFFFF; | ||
let tmp = UPPER_MASK | (rng.next_u32() & LOWER_MASK); | ||
let result: f32 = unsafe { mem::transmute(tmp) }; | ||
result - 1.0 | ||
} | ||
} | ||
impl Distribution<f64> for Uniform { | ||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 { | ||
const UPPER_MASK: u64 = 0x3FF0000000000000; | ||
const LOWER_MASK: u64 = 0xFFFFFFFFFFFFF; | ||
let tmp = UPPER_MASK | (rng.next_u64() & LOWER_MASK); | ||
let result: f64 = unsafe { mem::transmute(tmp) }; | ||
result - 1.0 | ||
} | ||
pub(crate) trait IntoFloat { | ||
type F; | ||
|
||
/// Helper method to combine the fraction and a contant exponent into a | ||
/// float. | ||
/// | ||
/// Only the least significant bits of `self` may be set, 23 for `f32` and | ||
/// 52 for `f64`. | ||
/// The resulting value will fall in a range that depends on the exponent. | ||
/// As an example the range with exponent 0 will be | ||
/// [2<sup>0</sup>..2<sup>1</sup>), which is [1..2). | ||
#[inline(always)] | ||
fn into_float_with_exponent(self, exponent: i32) -> Self::F; | ||
} | ||
|
||
macro_rules! float_impls { | ||
($mod_name:ident, $ty:ty, $mantissa_bits:expr) => { | ||
mod $mod_name { | ||
use Rng; | ||
use distributions::{Distribution}; | ||
use super::{Open01, Closed01}; | ||
|
||
const SCALE: $ty = (1u64 << $mantissa_bits) as $ty; | ||
|
||
impl Distribution<$ty> for Open01 { | ||
#[inline] | ||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty { | ||
// add 0.5 * epsilon, so that smallest number is | ||
// greater than 0, and largest number is still | ||
// less than 1, specifically 1 - 0.5 * epsilon. | ||
let x: $ty = rng.gen(); | ||
x + 0.5 / SCALE | ||
} | ||
($ty:ty, $uty:ty, $fraction_bits:expr, $exponent_bias:expr, | ||
$next_u:ident) => { | ||
impl IntoFloat for $uty { | ||
type F = $ty; | ||
#[inline(always)] | ||
fn into_float_with_exponent(self, exponent: i32) -> $ty { | ||
// The exponent is encoded using an offset-binary representation, | ||
// with the zero offset being 127 | ||
let exponent_bits = (($exponent_bias + exponent) as $uty) << $fraction_bits; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Equivalent to removed |
||
unsafe { mem::transmute(self | exponent_bits) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this transmute well-defined? Is it correct on BE? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The CI is happy... More seriously it should be ok on all architectures as long as we don't transmute into signalling NANs. |
||
} | ||
impl Distribution<$ty> for Closed01 { | ||
#[inline] | ||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty { | ||
// rescale so that 1.0 - epsilon becomes 1.0 | ||
// precisely. | ||
let x: $ty = rng.gen(); | ||
x * SCALE / (SCALE - 1.0) | ||
} | ||
} | ||
|
||
impl Distribution<$ty> for Uniform { | ||
/// Generate a floating point number in the open interval `(0, 1)` | ||
/// (not including either endpoint) with a uniform distribution. | ||
/// | ||
/// # Example | ||
/// ```rust | ||
/// use rand::{weak_rng, Rng}; | ||
/// use rand::distributions::Uniform; | ||
/// | ||
/// let val: f32 = weak_rng().sample(Uniform); | ||
/// println!("f32 from (0,1): {}", val); | ||
/// ``` | ||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty { | ||
const EPSILON: $ty = 1.0 / (1u64 << $fraction_bits) as $ty; | ||
let float_size = mem::size_of::<$ty>() * 8; | ||
|
||
let value = rng.$next_u(); | ||
let fraction = value >> (float_size - $fraction_bits); | ||
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If 1+ε is the smallest representable number above 1, then 1-ε/2 is representable; ok. This is the same adjustment as the |
||
} | ||
} | ||
} | ||
} | ||
float_impls! { f64_rand_impls, f64, 52 } | ||
float_impls! { f32_rand_impls, f32, 23 } | ||
float_impls! { f32, u32, 23, 127, next_u32 } | ||
float_impls! { f64, u64, 52, 1023, next_u64 } | ||
|
||
|
||
#[cfg(test)] | ||
mod tests { | ||
use Rng; | ||
use mock::StepRng; | ||
use distributions::{Open01, Closed01}; | ||
|
||
const EPSILON32: f32 = ::core::f32::EPSILON; | ||
const EPSILON64: f64 = ::core::f64::EPSILON; | ||
|
||
#[test] | ||
fn floating_point_edge_cases() { | ||
let mut zeros = StepRng::new(0, 0); | ||
assert_eq!(zeros.gen::<f32>(), 0.0); | ||
assert_eq!(zeros.gen::<f64>(), 0.0); | ||
|
||
let mut one = StepRng::new(1, 0); | ||
assert_eq!(one.gen::<f32>(), EPSILON32); | ||
assert_eq!(one.gen::<f64>(), EPSILON64); | ||
|
||
let mut max = StepRng::new(!0, 0); | ||
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32); | ||
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64); | ||
} | ||
assert_eq!(zeros.gen::<f32>(), 0.0 + EPSILON32 / 2.0); | ||
assert_eq!(zeros.gen::<f64>(), 0.0 + EPSILON64 / 2.0); | ||
|
||
#[test] | ||
fn fp_closed_edge_cases() { | ||
let mut zeros = StepRng::new(0, 0); | ||
assert_eq!(zeros.sample::<f32, _>(Closed01), 0.0); | ||
assert_eq!(zeros.sample::<f64, _>(Closed01), 0.0); | ||
|
||
let mut one = StepRng::new(1, 0); | ||
let one32 = one.sample::<f32, _>(Closed01); | ||
let one64 = one.sample::<f64, _>(Closed01); | ||
assert!(EPSILON32 < one32 && one32 < EPSILON32 * 1.01); | ||
assert!(EPSILON64 < one64 && one64 < EPSILON64 * 1.01); | ||
|
||
let mut max = StepRng::new(!0, 0); | ||
assert_eq!(max.sample::<f32, _>(Closed01), 1.0); | ||
assert_eq!(max.sample::<f64, _>(Closed01), 1.0); | ||
} | ||
|
||
#[test] | ||
fn fp_open_edge_cases() { | ||
let mut zeros = StepRng::new(0, 0); | ||
assert_eq!(zeros.sample::<f32, _>(Open01), 0.0 + EPSILON32 / 2.0); | ||
assert_eq!(zeros.sample::<f64, _>(Open01), 0.0 + EPSILON64 / 2.0); | ||
|
||
let mut one = StepRng::new(1, 0); | ||
let one32 = one.sample::<f32, _>(Open01); | ||
let one64 = one.sample::<f64, _>(Open01); | ||
let mut one = StepRng::new(1 << 9, 0); | ||
let one32 = one.gen::<f32>(); | ||
assert!(EPSILON32 < one32 && one32 < EPSILON32 * 2.0); | ||
assert!(EPSILON64 < one64 && one64 < EPSILON64 * 2.0); | ||
|
||
let mut max = StepRng::new(!0, 0); | ||
assert_eq!(max.sample::<f32, _>(Open01), 1.0 - EPSILON32 / 2.0); | ||
assert_eq!(max.sample::<f64, _>(Open01), 1.0 - EPSILON64 / 2.0); | ||
} | ||
|
||
#[test] | ||
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 = ::test::rng(510); | ||
for _ in 0..1_000 { | ||
// strict inequalities | ||
let f: f64 = rng.sample(Open01); | ||
assert!(0.0 < f && f < 1.0); | ||
|
||
let f: f32 = rng.sample(Open01); | ||
assert!(0.0 < f && f < 1.0); | ||
} | ||
} | ||
|
||
#[test] | ||
fn rand_closed() { | ||
let mut rng = ::test::rng(511); | ||
for _ in 0..1_000 { | ||
// strict inequalities | ||
let f: f64 = rng.sample(Closed01); | ||
assert!(0.0 <= f && f <= 1.0); | ||
let mut one = StepRng::new(1 << 12, 0); | ||
let one64 = one.gen::<f64>(); | ||
assert!(EPSILON64 < one64 && one64 < EPSILON64 * 2.0); | ||
|
||
let f: f32 = rng.sample(Closed01); | ||
assert!(0.0 <= f && f <= 1.0); | ||
} | ||
let mut max = StepRng::new(!0, 0); | ||
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0); | ||
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
127 is only correct for
f32
I think? Maybe reduce this doc.