Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benches/distributions.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,8 @@ distr!(distr_uniform_codepoint, char, Standard);

distr_float!(distr_uniform_f32, f32, Standard);
distr_float!(distr_uniform_f64, f64, Standard);
distr_float!(distr_high_precision_f32, f32, HighPrecision01);
distr_float!(distr_high_precision_f64, f64, HighPrecision01);

// distributions
distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56));
Expand Down
133 changes: 131 additions & 2 deletions src/distributions/float.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,10 +10,37 @@

//! Basic floating-point number distributions

use core::mem;
use core::{cmp, mem};
use Rng;
use distributions::{Distribution, Standard};

/// Generate a floating point number in the half-open interval `[0, 1)` with a
/// uniform distribution, with as much precision as the floating-point type
/// can represent, including sub-normals.
///
/// Technically 0 is representable, but the probability of occurrence is
/// remote (1 in 2^149 for `f32` or 1 in 2^1074 for `f64`).
///
/// This is different from `Uniform` in that it uses as many random bits as
/// required to get high precision close to 0. Normally only a single call to
/// the source RNG is required (32 bits for `f32` or 64 bits for `f64`); 1 in
/// 2^9 (`f32`) or 2^12 (`f64`) samples need an extra call; of these 1 in 2^32
/// or 1 in 2^64 require a third call, etc.; i.e. even for `f32` a third call is
/// almost impossible to observe with an unbiased RNG. Due to the extra logic
/// there is some performance overhead relative to `Uniform`; this is more
/// significant for `f32` than for `f64`.
///
/// # Example
/// ```rust
/// use rand::{NewRng, SmallRng, Rng};
/// use rand::distributions::HighPrecision01;
///
/// let val: f32 = SmallRng::new().sample(HighPrecision01);
/// println!("f32 from [0,1): {}", val);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct HighPrecision01;

pub(crate) trait IntoFloat {
type F;

Expand DownExpand Up@@ -54,6 +81,66 @@ macro_rules! float_impls {
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
}
}

impl Distribution<$ty> for HighPrecision01 {
/// Generate a floating point number in the half-open interval
/// `[0, 1)` with a uniform distribution. See [`HighPrecision01`].
///
/// # Algorithm
/// (Note: this description used values that apply to `f32` to
/// illustrate the algorithm).
///
/// The trick to generate a uniform distribution over [0,1) is to
/// set the exponent to the -log2 of the remaining random bits. A
/// simpler alternative to -log2 is to count the number of trailing
/// zeros in the random bits. In the case where all bits are zero,
/// we simply generate a new random number and add the number of
/// trailing zeros to the previous count (up to maximum exponent).
///
/// Each exponent is responsible for a piece of the distribution
/// between [0,1). We take the above exponent, add 1 and negate;
/// thus with probability 1/2 we have exponent -1 which fills the
/// range [0.5,1); with probability 1/4 we have exponent -2 which
/// fills the range [0.25,0.5), etc. If the exponent reaches the
/// minimum allowed, the floating-point format drops the implied
/// fraction bit, thus allowing numbers down to 0 to be sampled.
///
/// [`HighPrecision01`]: struct.HighPrecision01.html
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
// Unusual case. Separate function to allow inlining of rest.
#[inline(never)]
fn fallback<R: Rng + ?Sized>(mut exp: i32, fraction: $uty, rng: &mut R) -> $ty {
// Performance impact of code here is negligible.
let bits = rng.gen::<$uty>();
exp += bits.trailing_zeros() as i32;
// If RNG were guaranteed unbiased we could skip the
// check against exp; unfortunately it may be.
// Worst case ("zeros" RNG) has recursion depth 16.
if bits == 0 && exp < $exponent_bias {
return fallback(exp, fraction, rng);
}
exp = cmp::min(exp, $exponent_bias);
fraction.into_float_with_exponent(-exp)
}

let fraction_mask = (1 << $fraction_bits) - 1;
let value = rng.$next_u();

let fraction = value & fraction_mask;
let remaining = value >> $fraction_bits;
if remaining == 0 {
// exp is compile-time constant so this reduces to a function call:
let size_bits = (mem::size_of::<$ty>() * 8) as i32;
let exp = (size_bits - $fraction_bits as i32) + 1;
return fallback(exp, fraction, rng);
}

// Usual case: exponent from -1 to -9 (f32) or -12 (f64)
let exp = remaining.trailing_zeros() as i32 + 1;
fraction.into_float_with_exponent(-exp)
}
}
}
}
float_impls! { f32, u32, 23, 127, next_u32 }
Expand All@@ -62,7 +149,8 @@ float_impls! { f64, u64, 52, 1023, next_u64 }

#[cfg(test)]
mod tests {
use Rng;
use {Rng};
use distributions::HighPrecision01;
use mock::StepRng;

const EPSILON32: f32 = ::core::f32::EPSILON;
Expand All@@ -86,4 +174,45 @@ mod tests {
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
}

#[test]
fn high_precision_01_edge_cases() {
// Test that the distribution is a half-open range over [0,1).
// These constants happen to generate the lowest and highest floats in
// the range.
let mut zeros = StepRng::new(0, 0);
assert_eq!(zeros.sample::<f32, _>(HighPrecision01), 0.0);
assert_eq!(zeros.sample::<f64, _>(HighPrecision01), 0.0);

let mut ones = StepRng::new(0xffff_ffff_ffff_ffff, 0);
assert_eq!(ones.sample::<f32, _>(HighPrecision01), 0.99999994);
assert_eq!(ones.sample::<f64, _>(HighPrecision01), 0.9999999999999999);
}

#[cfg(feature="std")] mod mean {
use {Rng, SmallRng, SeedableRng, thread_rng};
use distributions::{Uniform, HighPrecision01};

macro_rules! test_mean {
($name:ident, $ty:ty, $distr:expr) => {
#[test]
fn $name() {
// TODO: no need to &mut here:
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut total: $ty = 0.0;
const N: u32 = 1_000_000;
for _ in 0..N {
total += rng.sample::<$ty, _>($distr);
}
let avg = total / (N as $ty);
//println!("average over {} samples: {}", N, avg);
assert!(0.499 < avg && avg < 0.501);
}
} }

test_mean!(test_mean_f32, f32, Uniform);
test_mean!(test_mean_f64, f64, Uniform);
test_mean!(test_mean_high_f32, f32, HighPrecision01);
test_mean!(test_mean_high_f64, f64, HighPrecision01);
}
}
1 change: 1 addition & 0 deletions src/distributions/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@
use Rng;

pub use self::other::Alphanumeric;
pub use self::float::HighPrecision01;
pub use self::range::Range;
#[cfg(feature="std")]
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benches/distributions.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,8 @@ distr!(distr_uniform_codepoint, char, Standard);

distr_float!(distr_uniform_f32, f32, Standard);
distr_float!(distr_uniform_f64, f64, Standard);
distr_float!(distr_high_precision_f32, f32, HighPrecision01);
distr_float!(distr_high_precision_f64, f64, HighPrecision01);

// distributions
distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56));
Expand Down
133 changes: 131 additions & 2 deletions src/distributions/float.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,10 +10,37 @@

//! Basic floating-point number distributions

use core::mem;
use core::{cmp, mem};
use Rng;
use distributions::{Distribution, Standard};

/// Generate a floating point number in the half-open interval `[0, 1)` with a
/// uniform distribution, with as much precision as the floating-point type
/// can represent, including sub-normals.
///
/// Technically 0 is representable, but the probability of occurrence is
/// remote (1 in 2^149 for `f32` or 1 in 2^1074 for `f64`).
///
/// This is different from `Uniform` in that it uses as many random bits as
/// required to get high precision close to 0. Normally only a single call to
/// the source RNG is required (32 bits for `f32` or 64 bits for `f64`); 1 in
/// 2^9 (`f32`) or 2^12 (`f64`) samples need an extra call; of these 1 in 2^32
/// or 1 in 2^64 require a third call, etc.; i.e. even for `f32` a third call is
/// almost impossible to observe with an unbiased RNG. Due to the extra logic
/// there is some performance overhead relative to `Uniform`; this is more
/// significant for `f32` than for `f64`.
///
/// # Example
/// ```rust
/// use rand::{NewRng, SmallRng, Rng};
/// use rand::distributions::HighPrecision01;
///
/// let val: f32 = SmallRng::new().sample(HighPrecision01);
/// println!("f32 from [0,1): {}", val);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct HighPrecision01;

pub(crate) trait IntoFloat {
type F;

Expand DownExpand Up@@ -54,6 +81,66 @@ macro_rules! float_impls {
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
}
}

impl Distribution<$ty> for HighPrecision01 {
/// Generate a floating point number in the half-open interval
/// `[0, 1)` with a uniform distribution. See [`HighPrecision01`].
///
/// # Algorithm
/// (Note: this description used values that apply to `f32` to
/// illustrate the algorithm).
///
/// The trick to generate a uniform distribution over [0,1) is to
/// set the exponent to the -log2 of the remaining random bits. A
/// simpler alternative to -log2 is to count the number of trailing
/// zeros in the random bits. In the case where all bits are zero,
/// we simply generate a new random number and add the number of
/// trailing zeros to the previous count (up to maximum exponent).
///
/// Each exponent is responsible for a piece of the distribution
/// between [0,1). We take the above exponent, add 1 and negate;
/// thus with probability 1/2 we have exponent -1 which fills the
/// range [0.5,1); with probability 1/4 we have exponent -2 which
/// fills the range [0.25,0.5), etc. If the exponent reaches the
/// minimum allowed, the floating-point format drops the implied
/// fraction bit, thus allowing numbers down to 0 to be sampled.
///
/// [`HighPrecision01`]: struct.HighPrecision01.html
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
// Unusual case. Separate function to allow inlining of rest.
#[inline(never)]
fn fallback<R: Rng + ?Sized>(mut exp: i32, fraction: $uty, rng: &mut R) -> $ty {
// Performance impact of code here is negligible.
let bits = rng.gen::<$uty>();
exp += bits.trailing_zeros() as i32;
// If RNG were guaranteed unbiased we could skip the
// check against exp; unfortunately it may be.
// Worst case ("zeros" RNG) has recursion depth 16.
if bits == 0 && exp < $exponent_bias {
return fallback(exp, fraction, rng);
}
exp = cmp::min(exp, $exponent_bias);
fraction.into_float_with_exponent(-exp)
}

let fraction_mask = (1 << $fraction_bits) - 1;
let value = rng.$next_u();

let fraction = value & fraction_mask;
let remaining = value >> $fraction_bits;
if remaining == 0 {
// exp is compile-time constant so this reduces to a function call:
let size_bits = (mem::size_of::<$ty>() * 8) as i32;
let exp = (size_bits - $fraction_bits as i32) + 1;
return fallback(exp, fraction, rng);
}

// Usual case: exponent from -1 to -9 (f32) or -12 (f64)
let exp = remaining.trailing_zeros() as i32 + 1;
fraction.into_float_with_exponent(-exp)
}
}
}
}
float_impls! { f32, u32, 23, 127, next_u32 }
Expand All@@ -62,7 +149,8 @@ float_impls! { f64, u64, 52, 1023, next_u64 }

#[cfg(test)]
mod tests {
use Rng;
use {Rng};
use distributions::HighPrecision01;
use mock::StepRng;

const EPSILON32: f32 = ::core::f32::EPSILON;
Expand All@@ -86,4 +174,45 @@ mod tests {
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
}

#[test]
fn high_precision_01_edge_cases() {
// Test that the distribution is a half-open range over [0,1).
// These constants happen to generate the lowest and highest floats in
// the range.
let mut zeros = StepRng::new(0, 0);
assert_eq!(zeros.sample::<f32, _>(HighPrecision01), 0.0);
assert_eq!(zeros.sample::<f64, _>(HighPrecision01), 0.0);

let mut ones = StepRng::new(0xffff_ffff_ffff_ffff, 0);
assert_eq!(ones.sample::<f32, _>(HighPrecision01), 0.99999994);
assert_eq!(ones.sample::<f64, _>(HighPrecision01), 0.9999999999999999);
}

#[cfg(feature="std")] mod mean {
use {Rng, SmallRng, SeedableRng, thread_rng};
use distributions::{Uniform, HighPrecision01};

macro_rules! test_mean {
($name:ident, $ty:ty, $distr:expr) => {
#[test]
fn $name() {
// TODO: no need to &mut here:
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut total: $ty = 0.0;
const N: u32 = 1_000_000;
for _ in 0..N {
total += rng.sample::<$ty, _>($distr);
}
let avg = total / (N as $ty);
//println!("average over {} samples: {}", N, avg);
assert!(0.499 < avg && avg < 0.501);
}
} }

test_mean!(test_mean_f32, f32, Uniform);
test_mean!(test_mean_f64, f64, Uniform);
test_mean!(test_mean_high_f32, f32, HighPrecision01);
test_mean!(test_mean_high_f64, f64, HighPrecision01);
}
}
1 change: 1 addition & 0 deletions src/distributions/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@
use Rng;

pub use self::other::Alphanumeric;
pub use self::float::HighPrecision01;
pub use self::range::Range;
#[cfg(feature="std")]
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benches/distributions.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,8 @@ distr!(distr_uniform_codepoint, char, Standard);

distr_float!(distr_uniform_f32, f32, Standard);
distr_float!(distr_uniform_f64, f64, Standard);
distr_float!(distr_high_precision_f32, f32, HighPrecision01);
distr_float!(distr_high_precision_f64, f64, HighPrecision01);

// distributions
distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56));
Expand Down
133 changes: 131 additions & 2 deletions src/distributions/float.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,10 +10,37 @@

//! Basic floating-point number distributions

use core::mem;
use core::{cmp, mem};
use Rng;
use distributions::{Distribution, Standard};

/// Generate a floating point number in the half-open interval `[0, 1)` with a
/// uniform distribution, with as much precision as the floating-point type
/// can represent, including sub-normals.
///
/// Technically 0 is representable, but the probability of occurrence is
/// remote (1 in 2^149 for `f32` or 1 in 2^1074 for `f64`).
///
/// This is different from `Uniform` in that it uses as many random bits as
/// required to get high precision close to 0. Normally only a single call to
/// the source RNG is required (32 bits for `f32` or 64 bits for `f64`); 1 in
/// 2^9 (`f32`) or 2^12 (`f64`) samples need an extra call; of these 1 in 2^32
/// or 1 in 2^64 require a third call, etc.; i.e. even for `f32` a third call is
/// almost impossible to observe with an unbiased RNG. Due to the extra logic
/// there is some performance overhead relative to `Uniform`; this is more
/// significant for `f32` than for `f64`.
///
/// # Example
/// ```rust
/// use rand::{NewRng, SmallRng, Rng};
/// use rand::distributions::HighPrecision01;
///
/// let val: f32 = SmallRng::new().sample(HighPrecision01);
/// println!("f32 from [0,1): {}", val);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct HighPrecision01;

pub(crate) trait IntoFloat {
type F;

Expand DownExpand Up@@ -54,6 +81,66 @@ macro_rules! float_impls {
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
}
}

impl Distribution<$ty> for HighPrecision01 {
/// Generate a floating point number in the half-open interval
/// `[0, 1)` with a uniform distribution. See [`HighPrecision01`].
///
/// # Algorithm
/// (Note: this description used values that apply to `f32` to
/// illustrate the algorithm).
///
/// The trick to generate a uniform distribution over [0,1) is to
/// set the exponent to the -log2 of the remaining random bits. A
/// simpler alternative to -log2 is to count the number of trailing
/// zeros in the random bits. In the case where all bits are zero,
/// we simply generate a new random number and add the number of
/// trailing zeros to the previous count (up to maximum exponent).
///
/// Each exponent is responsible for a piece of the distribution
/// between [0,1). We take the above exponent, add 1 and negate;
/// thus with probability 1/2 we have exponent -1 which fills the
/// range [0.5,1); with probability 1/4 we have exponent -2 which
/// fills the range [0.25,0.5), etc. If the exponent reaches the
/// minimum allowed, the floating-point format drops the implied
/// fraction bit, thus allowing numbers down to 0 to be sampled.
///
/// [`HighPrecision01`]: struct.HighPrecision01.html
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
// Unusual case. Separate function to allow inlining of rest.
#[inline(never)]
fn fallback<R: Rng + ?Sized>(mut exp: i32, fraction: $uty, rng: &mut R) -> $ty {
// Performance impact of code here is negligible.
let bits = rng.gen::<$uty>();
exp += bits.trailing_zeros() as i32;
// If RNG were guaranteed unbiased we could skip the
// check against exp; unfortunately it may be.
// Worst case ("zeros" RNG) has recursion depth 16.
if bits == 0 && exp < $exponent_bias {
return fallback(exp, fraction, rng);
}
exp = cmp::min(exp, $exponent_bias);
fraction.into_float_with_exponent(-exp)
}

let fraction_mask = (1 << $fraction_bits) - 1;
let value = rng.$next_u();

let fraction = value & fraction_mask;
let remaining = value >> $fraction_bits;
if remaining == 0 {
// exp is compile-time constant so this reduces to a function call:
let size_bits = (mem::size_of::<$ty>() * 8) as i32;
let exp = (size_bits - $fraction_bits as i32) + 1;
return fallback(exp, fraction, rng);
}

// Usual case: exponent from -1 to -9 (f32) or -12 (f64)
let exp = remaining.trailing_zeros() as i32 + 1;
fraction.into_float_with_exponent(-exp)
}
}
}
}
float_impls! { f32, u32, 23, 127, next_u32 }
Expand All@@ -62,7 +149,8 @@ float_impls! { f64, u64, 52, 1023, next_u64 }

#[cfg(test)]
mod tests {
use Rng;
use {Rng};
use distributions::HighPrecision01;
use mock::StepRng;

const EPSILON32: f32 = ::core::f32::EPSILON;
Expand All@@ -86,4 +174,45 @@ mod tests {
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
}

#[test]
fn high_precision_01_edge_cases() {
// Test that the distribution is a half-open range over [0,1).
// These constants happen to generate the lowest and highest floats in
// the range.
let mut zeros = StepRng::new(0, 0);
assert_eq!(zeros.sample::<f32, _>(HighPrecision01), 0.0);
assert_eq!(zeros.sample::<f64, _>(HighPrecision01), 0.0);

let mut ones = StepRng::new(0xffff_ffff_ffff_ffff, 0);
assert_eq!(ones.sample::<f32, _>(HighPrecision01), 0.99999994);
assert_eq!(ones.sample::<f64, _>(HighPrecision01), 0.9999999999999999);
}

#[cfg(feature="std")] mod mean {
use {Rng, SmallRng, SeedableRng, thread_rng};
use distributions::{Uniform, HighPrecision01};

macro_rules! test_mean {
($name:ident, $ty:ty, $distr:expr) => {
#[test]
fn $name() {
// TODO: no need to &mut here:
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut total: $ty = 0.0;
const N: u32 = 1_000_000;
for _ in 0..N {
total += rng.sample::<$ty, _>($distr);
}
let avg = total / (N as $ty);
//println!("average over {} samples: {}", N, avg);
assert!(0.499 < avg && avg < 0.501);
}
} }

test_mean!(test_mean_f32, f32, Uniform);
test_mean!(test_mean_f64, f64, Uniform);
test_mean!(test_mean_high_f32, f32, HighPrecision01);
test_mean!(test_mean_high_f64, f64, HighPrecision01);
}
}
1 change: 1 addition & 0 deletions src/distributions/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@
use Rng;

pub use self::other::Alphanumeric;
pub use self::float::HighPrecision01;
pub use self::range::Range;
#[cfg(feature="std")]
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benches/distributions.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,8 @@ distr!(distr_uniform_codepoint, char, Standard);

distr_float!(distr_uniform_f32, f32, Standard);
distr_float!(distr_uniform_f64, f64, Standard);
distr_float!(distr_high_precision_f32, f32, HighPrecision01);
distr_float!(distr_high_precision_f64, f64, HighPrecision01);

// distributions
distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56));
Expand Down
133 changes: 131 additions & 2 deletions src/distributions/float.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,10 +10,37 @@

//! Basic floating-point number distributions

use core::mem;
use core::{cmp, mem};
use Rng;
use distributions::{Distribution, Standard};

/// Generate a floating point number in the half-open interval `[0, 1)` with a
/// uniform distribution, with as much precision as the floating-point type
/// can represent, including sub-normals.
///
/// Technically 0 is representable, but the probability of occurrence is
/// remote (1 in 2^149 for `f32` or 1 in 2^1074 for `f64`).
///
/// This is different from `Uniform` in that it uses as many random bits as
/// required to get high precision close to 0. Normally only a single call to
/// the source RNG is required (32 bits for `f32` or 64 bits for `f64`); 1 in
/// 2^9 (`f32`) or 2^12 (`f64`) samples need an extra call; of these 1 in 2^32
/// or 1 in 2^64 require a third call, etc.; i.e. even for `f32` a third call is
/// almost impossible to observe with an unbiased RNG. Due to the extra logic
/// there is some performance overhead relative to `Uniform`; this is more
/// significant for `f32` than for `f64`.
///
/// # Example
/// ```rust
/// use rand::{NewRng, SmallRng, Rng};
/// use rand::distributions::HighPrecision01;
///
/// let val: f32 = SmallRng::new().sample(HighPrecision01);
/// println!("f32 from [0,1): {}", val);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct HighPrecision01;

pub(crate) trait IntoFloat {
type F;

Expand DownExpand Up@@ -54,6 +81,66 @@ macro_rules! float_impls {
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
}
}

impl Distribution<$ty> for HighPrecision01 {
/// Generate a floating point number in the half-open interval
/// `[0, 1)` with a uniform distribution. See [`HighPrecision01`].
///
/// # Algorithm
/// (Note: this description used values that apply to `f32` to
/// illustrate the algorithm).
///
/// The trick to generate a uniform distribution over [0,1) is to
/// set the exponent to the -log2 of the remaining random bits. A
/// simpler alternative to -log2 is to count the number of trailing
/// zeros in the random bits. In the case where all bits are zero,
/// we simply generate a new random number and add the number of
/// trailing zeros to the previous count (up to maximum exponent).
///
/// Each exponent is responsible for a piece of the distribution
/// between [0,1). We take the above exponent, add 1 and negate;
/// thus with probability 1/2 we have exponent -1 which fills the
/// range [0.5,1); with probability 1/4 we have exponent -2 which
/// fills the range [0.25,0.5), etc. If the exponent reaches the
/// minimum allowed, the floating-point format drops the implied
/// fraction bit, thus allowing numbers down to 0 to be sampled.
///
/// [`HighPrecision01`]: struct.HighPrecision01.html
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
// Unusual case. Separate function to allow inlining of rest.
#[inline(never)]
fn fallback<R: Rng + ?Sized>(mut exp: i32, fraction: $uty, rng: &mut R) -> $ty {
// Performance impact of code here is negligible.
let bits = rng.gen::<$uty>();
exp += bits.trailing_zeros() as i32;
// If RNG were guaranteed unbiased we could skip the
// check against exp; unfortunately it may be.
// Worst case ("zeros" RNG) has recursion depth 16.
if bits == 0 && exp < $exponent_bias {
return fallback(exp, fraction, rng);
}
exp = cmp::min(exp, $exponent_bias);
fraction.into_float_with_exponent(-exp)
}

let fraction_mask = (1 << $fraction_bits) - 1;
let value = rng.$next_u();

let fraction = value & fraction_mask;
let remaining = value >> $fraction_bits;
if remaining == 0 {
// exp is compile-time constant so this reduces to a function call:
let size_bits = (mem::size_of::<$ty>() * 8) as i32;
let exp = (size_bits - $fraction_bits as i32) + 1;
return fallback(exp, fraction, rng);
}

// Usual case: exponent from -1 to -9 (f32) or -12 (f64)
let exp = remaining.trailing_zeros() as i32 + 1;
fraction.into_float_with_exponent(-exp)
}
}
}
}
float_impls! { f32, u32, 23, 127, next_u32 }
Expand All@@ -62,7 +149,8 @@ float_impls! { f64, u64, 52, 1023, next_u64 }

#[cfg(test)]
mod tests {
use Rng;
use {Rng};
use distributions::HighPrecision01;
use mock::StepRng;

const EPSILON32: f32 = ::core::f32::EPSILON;
Expand All@@ -86,4 +174,45 @@ mod tests {
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
}

#[test]
fn high_precision_01_edge_cases() {
// Test that the distribution is a half-open range over [0,1).
// These constants happen to generate the lowest and highest floats in
// the range.
let mut zeros = StepRng::new(0, 0);
assert_eq!(zeros.sample::<f32, _>(HighPrecision01), 0.0);
assert_eq!(zeros.sample::<f64, _>(HighPrecision01), 0.0);

let mut ones = StepRng::new(0xffff_ffff_ffff_ffff, 0);
assert_eq!(ones.sample::<f32, _>(HighPrecision01), 0.99999994);
assert_eq!(ones.sample::<f64, _>(HighPrecision01), 0.9999999999999999);
}

#[cfg(feature="std")] mod mean {
use {Rng, SmallRng, SeedableRng, thread_rng};
use distributions::{Uniform, HighPrecision01};

macro_rules! test_mean {
($name:ident, $ty:ty, $distr:expr) => {
#[test]
fn $name() {
// TODO: no need to &mut here:
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut total: $ty = 0.0;
const N: u32 = 1_000_000;
for _ in 0..N {
total += rng.sample::<$ty, _>($distr);
}
let avg = total / (N as $ty);
//println!("average over {} samples: {}", N, avg);
assert!(0.499 < avg && avg < 0.501);
}
} }

test_mean!(test_mean_f32, f32, Uniform);
test_mean!(test_mean_f64, f64, Uniform);
test_mean!(test_mean_high_f32, f32, HighPrecision01);
test_mean!(test_mean_high_f64, f64, HighPrecision01);
}
}
1 change: 1 addition & 0 deletions src/distributions/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@
use Rng;

pub use self::other::Alphanumeric;
pub use self::float::HighPrecision01;
pub use self::range::Range;
#[cfg(feature="std")]
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benches/distributions.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,8 @@ distr!(distr_uniform_codepoint, char, Standard);

distr_float!(distr_uniform_f32, f32, Standard);
distr_float!(distr_uniform_f64, f64, Standard);
distr_float!(distr_high_precision_f32, f32, HighPrecision01);
distr_float!(distr_high_precision_f64, f64, HighPrecision01);

// distributions
distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56));
Expand Down
133 changes: 131 additions & 2 deletions src/distributions/float.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,10 +10,37 @@

//! Basic floating-point number distributions

use core::mem;
use core::{cmp, mem};
use Rng;
use distributions::{Distribution, Standard};

/// Generate a floating point number in the half-open interval `[0, 1)` with a
/// uniform distribution, with as much precision as the floating-point type
/// can represent, including sub-normals.
///
/// Technically 0 is representable, but the probability of occurrence is
/// remote (1 in 2^149 for `f32` or 1 in 2^1074 for `f64`).
///
/// This is different from `Uniform` in that it uses as many random bits as
/// required to get high precision close to 0. Normally only a single call to
/// the source RNG is required (32 bits for `f32` or 64 bits for `f64`); 1 in
/// 2^9 (`f32`) or 2^12 (`f64`) samples need an extra call; of these 1 in 2^32
/// or 1 in 2^64 require a third call, etc.; i.e. even for `f32` a third call is
/// almost impossible to observe with an unbiased RNG. Due to the extra logic
/// there is some performance overhead relative to `Uniform`; this is more
/// significant for `f32` than for `f64`.
///
/// # Example
/// ```rust
/// use rand::{NewRng, SmallRng, Rng};
/// use rand::distributions::HighPrecision01;
///
/// let val: f32 = SmallRng::new().sample(HighPrecision01);
/// println!("f32 from [0,1): {}", val);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct HighPrecision01;

pub(crate) trait IntoFloat {
type F;

Expand DownExpand Up@@ -54,6 +81,66 @@ macro_rules! float_impls {
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
}
}

impl Distribution<$ty> for HighPrecision01 {
/// Generate a floating point number in the half-open interval
/// `[0, 1)` with a uniform distribution. See [`HighPrecision01`].
///
/// # Algorithm
/// (Note: this description used values that apply to `f32` to
/// illustrate the algorithm).
///
/// The trick to generate a uniform distribution over [0,1) is to
/// set the exponent to the -log2 of the remaining random bits. A
/// simpler alternative to -log2 is to count the number of trailing
/// zeros in the random bits. In the case where all bits are zero,
/// we simply generate a new random number and add the number of
/// trailing zeros to the previous count (up to maximum exponent).
///
/// Each exponent is responsible for a piece of the distribution
/// between [0,1). We take the above exponent, add 1 and negate;
/// thus with probability 1/2 we have exponent -1 which fills the
/// range [0.5,1); with probability 1/4 we have exponent -2 which
/// fills the range [0.25,0.5), etc. If the exponent reaches the
/// minimum allowed, the floating-point format drops the implied
/// fraction bit, thus allowing numbers down to 0 to be sampled.
///
/// [`HighPrecision01`]: struct.HighPrecision01.html
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
// Unusual case. Separate function to allow inlining of rest.
#[inline(never)]
fn fallback<R: Rng + ?Sized>(mut exp: i32, fraction: $uty, rng: &mut R) -> $ty {
// Performance impact of code here is negligible.
let bits = rng.gen::<$uty>();
exp += bits.trailing_zeros() as i32;
// If RNG were guaranteed unbiased we could skip the
// check against exp; unfortunately it may be.
// Worst case ("zeros" RNG) has recursion depth 16.
if bits == 0 && exp < $exponent_bias {
return fallback(exp, fraction, rng);
}
exp = cmp::min(exp, $exponent_bias);
fraction.into_float_with_exponent(-exp)
}

let fraction_mask = (1 << $fraction_bits) - 1;
let value = rng.$next_u();

let fraction = value & fraction_mask;
let remaining = value >> $fraction_bits;
if remaining == 0 {
// exp is compile-time constant so this reduces to a function call:
let size_bits = (mem::size_of::<$ty>() * 8) as i32;
let exp = (size_bits - $fraction_bits as i32) + 1;
return fallback(exp, fraction, rng);
}

// Usual case: exponent from -1 to -9 (f32) or -12 (f64)
let exp = remaining.trailing_zeros() as i32 + 1;
fraction.into_float_with_exponent(-exp)
}
}
}
}
float_impls! { f32, u32, 23, 127, next_u32 }
Expand All@@ -62,7 +149,8 @@ float_impls! { f64, u64, 52, 1023, next_u64 }

#[cfg(test)]
mod tests {
use Rng;
use {Rng};
use distributions::HighPrecision01;
use mock::StepRng;

const EPSILON32: f32 = ::core::f32::EPSILON;
Expand All@@ -86,4 +174,45 @@ mod tests {
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
}

#[test]
fn high_precision_01_edge_cases() {
// Test that the distribution is a half-open range over [0,1).
// These constants happen to generate the lowest and highest floats in
// the range.
let mut zeros = StepRng::new(0, 0);
assert_eq!(zeros.sample::<f32, _>(HighPrecision01), 0.0);
assert_eq!(zeros.sample::<f64, _>(HighPrecision01), 0.0);

let mut ones = StepRng::new(0xffff_ffff_ffff_ffff, 0);
assert_eq!(ones.sample::<f32, _>(HighPrecision01), 0.99999994);
assert_eq!(ones.sample::<f64, _>(HighPrecision01), 0.9999999999999999);
}

#[cfg(feature="std")] mod mean {
use {Rng, SmallRng, SeedableRng, thread_rng};
use distributions::{Uniform, HighPrecision01};

macro_rules! test_mean {
($name:ident, $ty:ty, $distr:expr) => {
#[test]
fn $name() {
// TODO: no need to &mut here:
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut total: $ty = 0.0;
const N: u32 = 1_000_000;
for _ in 0..N {
total += rng.sample::<$ty, _>($distr);
}
let avg = total / (N as $ty);
//println!("average over {} samples: {}", N, avg);
assert!(0.499 < avg && avg < 0.501);
}
} }

test_mean!(test_mean_f32, f32, Uniform);
test_mean!(test_mean_f64, f64, Uniform);
test_mean!(test_mean_high_f32, f32, HighPrecision01);
test_mean!(test_mean_high_f64, f64, HighPrecision01);
}
}
1 change: 1 addition & 0 deletions src/distributions/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@
use Rng;

pub use self::other::Alphanumeric;
pub use self::float::HighPrecision01;
pub use self::range::Range;
#[cfg(feature="std")]
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benches/distributions.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,8 @@ distr!(distr_uniform_codepoint, char, Standard);

distr_float!(distr_uniform_f32, f32, Standard);
distr_float!(distr_uniform_f64, f64, Standard);
distr_float!(distr_high_precision_f32, f32, HighPrecision01);
distr_float!(distr_high_precision_f64, f64, HighPrecision01);

// distributions
distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56));
Expand Down
133 changes: 131 additions & 2 deletions src/distributions/float.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,10 +10,37 @@

//! Basic floating-point number distributions

use core::mem;
use core::{cmp, mem};
use Rng;
use distributions::{Distribution, Standard};

/// Generate a floating point number in the half-open interval `[0, 1)` with a
/// uniform distribution, with as much precision as the floating-point type
/// can represent, including sub-normals.
///
/// Technically 0 is representable, but the probability of occurrence is
/// remote (1 in 2^149 for `f32` or 1 in 2^1074 for `f64`).
///
/// This is different from `Uniform` in that it uses as many random bits as
/// required to get high precision close to 0. Normally only a single call to
/// the source RNG is required (32 bits for `f32` or 64 bits for `f64`); 1 in
/// 2^9 (`f32`) or 2^12 (`f64`) samples need an extra call; of these 1 in 2^32
/// or 1 in 2^64 require a third call, etc.; i.e. even for `f32` a third call is
/// almost impossible to observe with an unbiased RNG. Due to the extra logic
/// there is some performance overhead relative to `Uniform`; this is more
/// significant for `f32` than for `f64`.
///
/// # Example
/// ```rust
/// use rand::{NewRng, SmallRng, Rng};
/// use rand::distributions::HighPrecision01;
///
/// let val: f32 = SmallRng::new().sample(HighPrecision01);
/// println!("f32 from [0,1): {}", val);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct HighPrecision01;

pub(crate) trait IntoFloat {
type F;

Expand DownExpand Up@@ -54,6 +81,66 @@ macro_rules! float_impls {
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
}
}

impl Distribution<$ty> for HighPrecision01 {
/// Generate a floating point number in the half-open interval
/// `[0, 1)` with a uniform distribution. See [`HighPrecision01`].
///
/// # Algorithm
/// (Note: this description used values that apply to `f32` to
/// illustrate the algorithm).
///
/// The trick to generate a uniform distribution over [0,1) is to
/// set the exponent to the -log2 of the remaining random bits. A
/// simpler alternative to -log2 is to count the number of trailing
/// zeros in the random bits. In the case where all bits are zero,
/// we simply generate a new random number and add the number of
/// trailing zeros to the previous count (up to maximum exponent).
///
/// Each exponent is responsible for a piece of the distribution
/// between [0,1). We take the above exponent, add 1 and negate;
/// thus with probability 1/2 we have exponent -1 which fills the
/// range [0.5,1); with probability 1/4 we have exponent -2 which
/// fills the range [0.25,0.5), etc. If the exponent reaches the
/// minimum allowed, the floating-point format drops the implied
/// fraction bit, thus allowing numbers down to 0 to be sampled.
///
/// [`HighPrecision01`]: struct.HighPrecision01.html
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
// Unusual case. Separate function to allow inlining of rest.
#[inline(never)]
fn fallback<R: Rng + ?Sized>(mut exp: i32, fraction: $uty, rng: &mut R) -> $ty {
// Performance impact of code here is negligible.
let bits = rng.gen::<$uty>();
exp += bits.trailing_zeros() as i32;
// If RNG were guaranteed unbiased we could skip the
// check against exp; unfortunately it may be.
// Worst case ("zeros" RNG) has recursion depth 16.
if bits == 0 && exp < $exponent_bias {
return fallback(exp, fraction, rng);
}
exp = cmp::min(exp, $exponent_bias);
fraction.into_float_with_exponent(-exp)
}

let fraction_mask = (1 << $fraction_bits) - 1;
let value = rng.$next_u();

let fraction = value & fraction_mask;
let remaining = value >> $fraction_bits;
if remaining == 0 {
// exp is compile-time constant so this reduces to a function call:
let size_bits = (mem::size_of::<$ty>() * 8) as i32;
let exp = (size_bits - $fraction_bits as i32) + 1;
return fallback(exp, fraction, rng);
}

// Usual case: exponent from -1 to -9 (f32) or -12 (f64)
let exp = remaining.trailing_zeros() as i32 + 1;
fraction.into_float_with_exponent(-exp)
}
}
}
}
float_impls! { f32, u32, 23, 127, next_u32 }
Expand All@@ -62,7 +149,8 @@ float_impls! { f64, u64, 52, 1023, next_u64 }

#[cfg(test)]
mod tests {
use Rng;
use {Rng};
use distributions::HighPrecision01;
use mock::StepRng;

const EPSILON32: f32 = ::core::f32::EPSILON;
Expand All@@ -86,4 +174,45 @@ mod tests {
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
}

#[test]
fn high_precision_01_edge_cases() {
// Test that the distribution is a half-open range over [0,1).
// These constants happen to generate the lowest and highest floats in
// the range.
let mut zeros = StepRng::new(0, 0);
assert_eq!(zeros.sample::<f32, _>(HighPrecision01), 0.0);
assert_eq!(zeros.sample::<f64, _>(HighPrecision01), 0.0);

let mut ones = StepRng::new(0xffff_ffff_ffff_ffff, 0);
assert_eq!(ones.sample::<f32, _>(HighPrecision01), 0.99999994);
assert_eq!(ones.sample::<f64, _>(HighPrecision01), 0.9999999999999999);
}

#[cfg(feature="std")] mod mean {
use {Rng, SmallRng, SeedableRng, thread_rng};
use distributions::{Uniform, HighPrecision01};

macro_rules! test_mean {
($name:ident, $ty:ty, $distr:expr) => {
#[test]
fn $name() {
// TODO: no need to &mut here:
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut total: $ty = 0.0;
const N: u32 = 1_000_000;
for _ in 0..N {
total += rng.sample::<$ty, _>($distr);
}
let avg = total / (N as $ty);
//println!("average over {} samples: {}", N, avg);
assert!(0.499 < avg && avg < 0.501);
}
} }

test_mean!(test_mean_f32, f32, Uniform);
test_mean!(test_mean_f64, f64, Uniform);
test_mean!(test_mean_high_f32, f32, HighPrecision01);
test_mean!(test_mean_high_f64, f64, HighPrecision01);
}
}
1 change: 1 addition & 0 deletions src/distributions/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@
use Rng;

pub use self::other::Alphanumeric;
pub use self::float::HighPrecision01;
pub use self::range::Range;
#[cfg(feature="std")]
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benches/distributions.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,8 @@ distr!(distr_uniform_codepoint, char, Standard);

distr_float!(distr_uniform_f32, f32, Standard);
distr_float!(distr_uniform_f64, f64, Standard);
distr_float!(distr_high_precision_f32, f32, HighPrecision01);
distr_float!(distr_high_precision_f64, f64, HighPrecision01);

// distributions
distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56));
Expand Down
133 changes: 131 additions & 2 deletions src/distributions/float.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,10 +10,37 @@

//! Basic floating-point number distributions

use core::mem;
use core::{cmp, mem};
use Rng;
use distributions::{Distribution, Standard};

/// Generate a floating point number in the half-open interval `[0, 1)` with a
/// uniform distribution, with as much precision as the floating-point type
/// can represent, including sub-normals.
///
/// Technically 0 is representable, but the probability of occurrence is
/// remote (1 in 2^149 for `f32` or 1 in 2^1074 for `f64`).
///
/// This is different from `Uniform` in that it uses as many random bits as
/// required to get high precision close to 0. Normally only a single call to
/// the source RNG is required (32 bits for `f32` or 64 bits for `f64`); 1 in
/// 2^9 (`f32`) or 2^12 (`f64`) samples need an extra call; of these 1 in 2^32
/// or 1 in 2^64 require a third call, etc.; i.e. even for `f32` a third call is
/// almost impossible to observe with an unbiased RNG. Due to the extra logic
/// there is some performance overhead relative to `Uniform`; this is more
/// significant for `f32` than for `f64`.
///
/// # Example
/// ```rust
/// use rand::{NewRng, SmallRng, Rng};
/// use rand::distributions::HighPrecision01;
///
/// let val: f32 = SmallRng::new().sample(HighPrecision01);
/// println!("f32 from [0,1): {}", val);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct HighPrecision01;

pub(crate) trait IntoFloat {
type F;

Expand DownExpand Up@@ -54,6 +81,66 @@ macro_rules! float_impls {
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
}
}

impl Distribution<$ty> for HighPrecision01 {
/// Generate a floating point number in the half-open interval
/// `[0, 1)` with a uniform distribution. See [`HighPrecision01`].
///
/// # Algorithm
/// (Note: this description used values that apply to `f32` to
/// illustrate the algorithm).
///
/// The trick to generate a uniform distribution over [0,1) is to
/// set the exponent to the -log2 of the remaining random bits. A
/// simpler alternative to -log2 is to count the number of trailing
/// zeros in the random bits. In the case where all bits are zero,
/// we simply generate a new random number and add the number of
/// trailing zeros to the previous count (up to maximum exponent).
///
/// Each exponent is responsible for a piece of the distribution
/// between [0,1). We take the above exponent, add 1 and negate;
/// thus with probability 1/2 we have exponent -1 which fills the
/// range [0.5,1); with probability 1/4 we have exponent -2 which
/// fills the range [0.25,0.5), etc. If the exponent reaches the
/// minimum allowed, the floating-point format drops the implied
/// fraction bit, thus allowing numbers down to 0 to be sampled.
///
/// [`HighPrecision01`]: struct.HighPrecision01.html
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
// Unusual case. Separate function to allow inlining of rest.
#[inline(never)]
fn fallback<R: Rng + ?Sized>(mut exp: i32, fraction: $uty, rng: &mut R) -> $ty {
// Performance impact of code here is negligible.
let bits = rng.gen::<$uty>();
exp += bits.trailing_zeros() as i32;
// If RNG were guaranteed unbiased we could skip the
// check against exp; unfortunately it may be.
// Worst case ("zeros" RNG) has recursion depth 16.
if bits == 0 && exp < $exponent_bias {
return fallback(exp, fraction, rng);
}
exp = cmp::min(exp, $exponent_bias);
fraction.into_float_with_exponent(-exp)
}

let fraction_mask = (1 << $fraction_bits) - 1;
let value = rng.$next_u();

let fraction = value & fraction_mask;
let remaining = value >> $fraction_bits;
if remaining == 0 {
// exp is compile-time constant so this reduces to a function call:
let size_bits = (mem::size_of::<$ty>() * 8) as i32;
let exp = (size_bits - $fraction_bits as i32) + 1;
return fallback(exp, fraction, rng);
}

// Usual case: exponent from -1 to -9 (f32) or -12 (f64)
let exp = remaining.trailing_zeros() as i32 + 1;
fraction.into_float_with_exponent(-exp)
}
}
}
}
float_impls! { f32, u32, 23, 127, next_u32 }
Expand All@@ -62,7 +149,8 @@ float_impls! { f64, u64, 52, 1023, next_u64 }

#[cfg(test)]
mod tests {
use Rng;
use {Rng};
use distributions::HighPrecision01;
use mock::StepRng;

const EPSILON32: f32 = ::core::f32::EPSILON;
Expand All@@ -86,4 +174,45 @@ mod tests {
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
}

#[test]
fn high_precision_01_edge_cases() {
// Test that the distribution is a half-open range over [0,1).
// These constants happen to generate the lowest and highest floats in
// the range.
let mut zeros = StepRng::new(0, 0);
assert_eq!(zeros.sample::<f32, _>(HighPrecision01), 0.0);
assert_eq!(zeros.sample::<f64, _>(HighPrecision01), 0.0);

let mut ones = StepRng::new(0xffff_ffff_ffff_ffff, 0);
assert_eq!(ones.sample::<f32, _>(HighPrecision01), 0.99999994);
assert_eq!(ones.sample::<f64, _>(HighPrecision01), 0.9999999999999999);
}

#[cfg(feature="std")] mod mean {
use {Rng, SmallRng, SeedableRng, thread_rng};
use distributions::{Uniform, HighPrecision01};

macro_rules! test_mean {
($name:ident, $ty:ty, $distr:expr) => {
#[test]
fn $name() {
// TODO: no need to &mut here:
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut total: $ty = 0.0;
const N: u32 = 1_000_000;
for _ in 0..N {
total += rng.sample::<$ty, _>($distr);
}
let avg = total / (N as $ty);
//println!("average over {} samples: {}", N, avg);
assert!(0.499 < avg && avg < 0.501);
}
} }

test_mean!(test_mean_f32, f32, Uniform);
test_mean!(test_mean_f64, f64, Uniform);
test_mean!(test_mean_high_f32, f32, HighPrecision01);
test_mean!(test_mean_high_f64, f64, HighPrecision01);
}
}
1 change: 1 addition & 0 deletions src/distributions/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@
use Rng;

pub use self::other::Alphanumeric;
pub use self::float::HighPrecision01;
pub use self::range::Range;
#[cfg(feature="std")]
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benches/distributions.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,8 @@ distr!(distr_uniform_codepoint, char, Standard);

distr_float!(distr_uniform_f32, f32, Standard);
distr_float!(distr_uniform_f64, f64, Standard);
distr_float!(distr_high_precision_f32, f32, HighPrecision01);
distr_float!(distr_high_precision_f64, f64, HighPrecision01);

// distributions
distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56));
Expand Down
133 changes: 131 additions & 2 deletions src/distributions/float.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,10 +10,37 @@

//! Basic floating-point number distributions

use core::mem;
use core::{cmp, mem};
use Rng;
use distributions::{Distribution, Standard};

/// Generate a floating point number in the half-open interval `[0, 1)` with a
/// uniform distribution, with as much precision as the floating-point type
/// can represent, including sub-normals.
///
/// Technically 0 is representable, but the probability of occurrence is
/// remote (1 in 2^149 for `f32` or 1 in 2^1074 for `f64`).
///
/// This is different from `Uniform` in that it uses as many random bits as
/// required to get high precision close to 0. Normally only a single call to
/// the source RNG is required (32 bits for `f32` or 64 bits for `f64`); 1 in
/// 2^9 (`f32`) or 2^12 (`f64`) samples need an extra call; of these 1 in 2^32
/// or 1 in 2^64 require a third call, etc.; i.e. even for `f32` a third call is
/// almost impossible to observe with an unbiased RNG. Due to the extra logic
/// there is some performance overhead relative to `Uniform`; this is more
/// significant for `f32` than for `f64`.
///
/// # Example
/// ```rust
/// use rand::{NewRng, SmallRng, Rng};
/// use rand::distributions::HighPrecision01;
///
/// let val: f32 = SmallRng::new().sample(HighPrecision01);
/// println!("f32 from [0,1): {}", val);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct HighPrecision01;

pub(crate) trait IntoFloat {
type F;

Expand DownExpand Up@@ -54,6 +81,66 @@ macro_rules! float_impls {
fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0)
}
}

impl Distribution<$ty> for HighPrecision01 {
/// Generate a floating point number in the half-open interval
/// `[0, 1)` with a uniform distribution. See [`HighPrecision01`].
///
/// # Algorithm
/// (Note: this description used values that apply to `f32` to
/// illustrate the algorithm).
///
/// The trick to generate a uniform distribution over [0,1) is to
/// set the exponent to the -log2 of the remaining random bits. A
/// simpler alternative to -log2 is to count the number of trailing
/// zeros in the random bits. In the case where all bits are zero,
/// we simply generate a new random number and add the number of
/// trailing zeros to the previous count (up to maximum exponent).
///
/// Each exponent is responsible for a piece of the distribution
/// between [0,1). We take the above exponent, add 1 and negate;
/// thus with probability 1/2 we have exponent -1 which fills the
/// range [0.5,1); with probability 1/4 we have exponent -2 which
/// fills the range [0.25,0.5), etc. If the exponent reaches the
/// minimum allowed, the floating-point format drops the implied
/// fraction bit, thus allowing numbers down to 0 to be sampled.
///
/// [`HighPrecision01`]: struct.HighPrecision01.html
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $ty {
// Unusual case. Separate function to allow inlining of rest.
#[inline(never)]
fn fallback<R: Rng + ?Sized>(mut exp: i32, fraction: $uty, rng: &mut R) -> $ty {
// Performance impact of code here is negligible.
let bits = rng.gen::<$uty>();
exp += bits.trailing_zeros() as i32;
// If RNG were guaranteed unbiased we could skip the
// check against exp; unfortunately it may be.
// Worst case ("zeros" RNG) has recursion depth 16.
if bits == 0 && exp < $exponent_bias {
return fallback(exp, fraction, rng);
}
exp = cmp::min(exp, $exponent_bias);
fraction.into_float_with_exponent(-exp)
}

let fraction_mask = (1 << $fraction_bits) - 1;
let value = rng.$next_u();

let fraction = value & fraction_mask;
let remaining = value >> $fraction_bits;
if remaining == 0 {
// exp is compile-time constant so this reduces to a function call:
let size_bits = (mem::size_of::<$ty>() * 8) as i32;
let exp = (size_bits - $fraction_bits as i32) + 1;
return fallback(exp, fraction, rng);
}

// Usual case: exponent from -1 to -9 (f32) or -12 (f64)
let exp = remaining.trailing_zeros() as i32 + 1;
fraction.into_float_with_exponent(-exp)
}
}
}
}
float_impls! { f32, u32, 23, 127, next_u32 }
Expand All@@ -62,7 +149,8 @@ float_impls! { f64, u64, 52, 1023, next_u64 }

#[cfg(test)]
mod tests {
use Rng;
use {Rng};
use distributions::HighPrecision01;
use mock::StepRng;

const EPSILON32: f32 = ::core::f32::EPSILON;
Expand All@@ -86,4 +174,45 @@ mod tests {
assert_eq!(max.gen::<f32>(), 1.0 - EPSILON32 / 2.0);
assert_eq!(max.gen::<f64>(), 1.0 - EPSILON64 / 2.0);
}

#[test]
fn high_precision_01_edge_cases() {
// Test that the distribution is a half-open range over [0,1).
// These constants happen to generate the lowest and highest floats in
// the range.
let mut zeros = StepRng::new(0, 0);
assert_eq!(zeros.sample::<f32, _>(HighPrecision01), 0.0);
assert_eq!(zeros.sample::<f64, _>(HighPrecision01), 0.0);

let mut ones = StepRng::new(0xffff_ffff_ffff_ffff, 0);
assert_eq!(ones.sample::<f32, _>(HighPrecision01), 0.99999994);
assert_eq!(ones.sample::<f64, _>(HighPrecision01), 0.9999999999999999);
}

#[cfg(feature="std")] mod mean {
use {Rng, SmallRng, SeedableRng, thread_rng};
use distributions::{Uniform, HighPrecision01};

macro_rules! test_mean {
($name:ident, $ty:ty, $distr:expr) => {
#[test]
fn $name() {
// TODO: no need to &mut here:
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut total: $ty = 0.0;
const N: u32 = 1_000_000;
for _ in 0..N {
total += rng.sample::<$ty, _>($distr);
}
let avg = total / (N as $ty);
//println!("average over {} samples: {}", N, avg);
assert!(0.499 < avg && avg < 0.501);
}
} }

test_mean!(test_mean_f32, f32, Uniform);
test_mean!(test_mean_f64, f64, Uniform);
test_mean!(test_mean_high_f32, f32, HighPrecision01);
test_mean!(test_mean_high_f64, f64, HighPrecision01);
}
}
1 change: 1 addition & 0 deletions src/distributions/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@
use Rng;

pub use self::other::Alphanumeric;
pub use self::float::HighPrecision01;
pub use self::range::Range;
#[cfg(feature="std")]
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
Expand Down