From cd830e77d7d4ebe09956be8733d06462be81fc84 Mon Sep 17 00:00:00 2001 From: Trevor Spiteri Date: Mon, 15 Jan 2018 19:04:07 +0100 Subject: [PATCH 1/3] Fix off-by-one error in mantissa_bits passed to float_impls --- src/rand_impls.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rand_impls.rs b/src/rand_impls.rs index 4b520b41bc5..7e72b0a0399 100644 --- a/src/rand_impls.rs +++ b/src/rand_impls.rs @@ -114,6 +114,7 @@ macro_rules! float_impls { mod $mod_name { use {Rand, Rng, Open01, Closed01}; + // 1.0 / epsilon const SCALE: $ty = (1u64 << $mantissa_bits) as $ty; impl Rand for $ty { @@ -148,8 +149,8 @@ macro_rules! float_impls { } } } -float_impls! { f64_rand_impls, f64, 53, next_f64 } -float_impls! { f32_rand_impls, f32, 24, next_f32 } +float_impls! { f64_rand_impls, f64, 52, next_f64 } +float_impls! { f32_rand_impls, f32, 23, next_f32 } impl Rand for char { #[inline] From cd18f4860cd7012a80fe6e3e940dd9ba0fe5bfac Mon Sep 17 00:00:00 2001 From: Trevor Spiteri Date: Mon, 15 Jan 2018 19:14:07 +0100 Subject: [PATCH 2/3] remove bias in Open01 --- src/rand_impls.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/rand_impls.rs b/src/rand_impls.rs index 7e72b0a0399..bfd59dfa675 100644 --- a/src/rand_impls.rs +++ b/src/rand_impls.rs @@ -131,11 +131,10 @@ macro_rules! float_impls { impl Rand for Open01<$ty> { #[inline] fn rand(rng: &mut R) -> Open01<$ty> { - // add a small amount (specifically 2 bits below - // the precision of f64/f32 at 1.0), so that small - // numbers are larger than 0, but large numbers - // aren't pushed to/above 1. - Open01(rng.$method_name() + 0.25 / SCALE) + // 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. + Open01(rng.$method_name() + 0.5 / SCALE) } } impl Rand for Closed01<$ty> { From a5da781586bc92c3fb0e48aa1180b56ca79a061a Mon Sep 17 00:00:00 2001 From: Trevor Spiteri Date: Fri, 2 Feb 2018 15:03:24 +0100 Subject: [PATCH 3/3] add tests for floating-point edge cases --- src/rand_impls.rs | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/rand_impls.rs b/src/rand_impls.rs index bfd59dfa675..0251022697d 100644 --- a/src/rand_impls.rs +++ b/src/rand_impls.rs @@ -269,9 +269,38 @@ mod tests { #[test] fn floating_point_edge_cases() { - // the test for exact equality is correct here. - assert!(ConstantRng(0xffff_ffff).gen::() != 1.0); - assert!(ConstantRng(0xffff_ffff_ffff_ffff).gen::() != 1.0); + const EPSILON32: f32 = 1.0 / (1u32 << 23) as f32; + const EPSILON64: f64 = 1.0 / (1u64 << 52) as f64; + + let mut zeros = ConstantRng(0); + let mut ones = ConstantRng(!0); + + let zero32 = zeros.gen::(); + let zero64 = zeros.gen::(); + let one32 = ones.gen::(); + let one64 = ones.gen::(); + assert_eq!(zero32, 0.0); + assert_eq!(zero64, 0.0); + assert!(1.0 - EPSILON32 <= one32 && one32 < 1.0); + assert!(1.0 - EPSILON64 <= one64 && one64 < 1.0); + + let Closed01(closed_zero32) = zeros.gen::>(); + let Closed01(closed_zero64) = zeros.gen::>(); + let Closed01(closed_one32) = ones.gen::>(); + let Closed01(closed_one64) = ones.gen::>(); + assert_eq!(closed_zero32, 0.0); + assert_eq!(closed_zero64, 0.0); + assert_eq!(closed_one32, 1.0); + assert_eq!(closed_one64, 1.0); + + let Open01(open_zero32) = zeros.gen::>(); + let Open01(open_zero64) = zeros.gen::>(); + let Open01(open_one32) = ones.gen::>(); + let Open01(open_one64) = ones.gen::>(); + assert!(0.0 < open_zero32 && open_zero32 <= EPSILON32); + assert!(0.0 < open_zero64 && open_zero64 <= EPSILON64); + assert!(1.0 - EPSILON32 <= open_one32 && open_one32 < 1.0); + assert!(1.0 - EPSILON64 <= open_one64 && open_one64 < 1.0); } #[test]