Skip to content

Commit ff821e2

Browse files
committed
Test and comment fixes
1 parent f089a53 commit ff821e2

File tree

1 file changed

+168
-112
lines changed

1 file changed

+168
-112
lines changed

src/distributions/float.rs

+168-112
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,19 @@ pub struct HighPrecision<F> where F: HPFloatHelper {
7777
low_as_int: F::SignedInt,
7878
high_as_int: F::SignedInt,
7979
exponent: u16,
80-
distribution: F::SignedIntDistribution,
80+
mantissa_distribution: F::SignedIntDistribution,
8181
}
8282

8383
impl<F: HPFloatHelper> HighPrecision<F> {
8484
/// Create a new HighPrecision distribution. Sampling from this
85-
/// distribution will return values `>= low` and `< high`.
85+
/// distribution will return values `>= low` and `< high`.
8686
pub fn new(low: F, high: F) -> Self {
8787
let parsed = F::parse_new(low, high);
8888
HighPrecision {
8989
low_as_int: parsed.0,
9090
high_as_int: parsed.1,
9191
exponent: parsed.2,
92-
distribution: parsed.3,
92+
mantissa_distribution: parsed.3,
9393
}
9494
}
9595
}
@@ -239,7 +239,7 @@ macro_rules! float_impls {
239239
if bits >= ::core::mem::size_of::<$uty>() as $uty * 8 { (-1 as $ity) as $uty } else { (1 as $uty << bits) - 1 }
240240
}
241241
loop {
242-
let signed_mant = self.distribution.sample(rng);
242+
let signed_mant = self.mantissa_distribution.sample(rng);
243243
// Operate on the absolute value so that we can count bit-sizes
244244
// correctly
245245
let is_neg = signed_mant < 0;
@@ -302,6 +302,10 @@ macro_rules! float_impls {
302302
return <$ty>::from_bits(res);
303303
}
304304

305+
// If not start over. We're avoiding reusing any of the previous
306+
// computation in order to avoid introducing bias, and to keep
307+
// things simple since this should be rare.
308+
305309
// Assert that we got here due to rounding
306310
#[cfg(debug_assertions)]
307311
{
@@ -318,15 +322,9 @@ macro_rules! float_impls {
318322
assert!(mant_high & bitmask(exp_low - exp_high) != 0);
319323
}
320324
}
321-
322-
// If not start over. We're avoiding reusing any of the previous
323-
// computation in order to avoid introducing bias, and to keep
324-
// things simple since this should be rare.
325325
}
326326
}
327327
}
328-
329-
330328
}
331329
}
332330
float_impls! { f32, u32, i32, 23, 8, 127 }
@@ -392,122 +390,180 @@ mod tests {
392390
assert_eq!(max.sample::<f64, _>(Open01), 1.0 - EPSILON64 / 2.0);
393391
}
394392

395-
#[cfg(feature = "alloc")]
393+
#[cfg(feature = "std")]
396394
#[test]
397395
fn test_highprecision() {
398-
399396
let mut r = ::test::rng(601);
400397

401398
macro_rules! float_test {
402-
($ty:ty, $uty:ty, $ity:ty, $test_vals:expr) => {{
403-
let mut vals: Vec<$ty> =
404-
$test_vals.iter().cloned()
405-
.flat_map(|x| (-2 as $ity..3).map(move |y| x + y))
406-
.map(|x| <$ty>::from_bits(x as $uty))
407-
.flat_map(|x| vec![x, -x].into_iter())
408-
.filter(|x| x.is_finite())
409-
.collect();
410-
vals.sort_by(|a, b| a.partial_cmp(b).unwrap());
411-
vals.dedup();
412-
413-
for a in vals.iter().cloned() {
414-
for b in vals.iter().cloned().filter(|&b| b > a) {
415-
fn to_signed_bits(val: $ty) -> $ity {
416-
if val >= 0.0 {
417-
val.to_bits() as $ity
418-
} else {
419-
-((-val).to_bits() as $ity)
399+
($ty:ty, $uty:ty, $ity:ty, $extra:expr, $test_vals:expr) => {
400+
// Create a closure to make loop labels local
401+
(|| {
402+
let mut vals: Vec<$ty> =
403+
$test_vals.iter().cloned()
404+
.flat_map(|x| $extra.iter().map(move |y| x + y))
405+
.map(|x| <$ty>::from_bits(x as $uty))
406+
.flat_map(|x| vec![x, -x].into_iter())
407+
.filter(|x| x.is_finite())
408+
.collect();
409+
vals.sort_by(|a, b| a.partial_cmp(b).unwrap());
410+
vals.dedup();
411+
412+
for a in vals.iter().cloned() {
413+
for b in vals.iter().cloned().filter(|&b| b > a) {
414+
fn to_signed_bits(val: $ty) -> $ity {
415+
if val >= 0.0 {
416+
val.to_bits() as $ity
417+
} else {
418+
-((-val).to_bits() as $ity)
419+
}
420420
}
421-
}
422-
fn from_signed_bits(val: $ity) -> $ty {
423-
if val >= 0 {
424-
<$ty>::from_bits(val as $uty)
425-
} else {
426-
-<$ty>::from_bits(-val as $uty)
421+
fn from_signed_bits(val: $ity) -> $ty {
422+
if val >= 0 {
423+
<$ty>::from_bits(val as $uty)
424+
} else {
425+
-<$ty>::from_bits(-val as $uty)
426+
}
427427
}
428-
}
429428

430-
let hp = HighPrecision::new(a, b);
431-
let a_bits = to_signed_bits(a);
432-
let b_bits = to_signed_bits(b);
433-
434-
// If a and b are "close enough", we can verify the full distribution
435-
if (b_bits.wrapping_sub(a_bits) as $uty) < 100 {
436-
let mut counts = Vec::<i32>::with_capacity((b_bits - a_bits) as usize);
437-
counts.resize((b_bits - a_bits) as usize, 0);
438-
for _ in 0..1000 {
439-
let res = hp.sample(&mut r);
440-
counts[(to_signed_bits(res) - a_bits) as usize] += 1;
441-
}
442-
for (count, i) in counts.iter().zip(0 as $ity..) {
443-
let expected = 1000.0 as $ty *
444-
(from_signed_bits(a_bits + i + 1) -
445-
from_signed_bits(a_bits + i)) / (b - a);
446-
let err = (*count as $ty - expected) / expected;
447-
assert!(err.abs() <= 0.2);
448-
}
449-
} else {
450-
// Rough estimate that the distribution is correct
451-
let step = if (b - a).is_finite() {
452-
(b - a) / 10.0
429+
let hp = HighPrecision::new(a, b);
430+
let a_bits = to_signed_bits(a);
431+
let b_bits = to_signed_bits(b);
432+
433+
const N_RUNS: usize = 10;
434+
const N_REPS_PER_RUN: usize = 1000;
435+
436+
if (b_bits.wrapping_sub(a_bits) as $uty) < 100 {
437+
// If a and b are "close enough", we can verify the full distribution
438+
let mut counts = Vec::<i32>::with_capacity((b_bits - a_bits) as usize);
439+
counts.resize((b_bits - a_bits) as usize, 0);
440+
'test_loop_exact: for test_run in 1..(N_RUNS+1) {
441+
for _ in 0..N_REPS_PER_RUN {
442+
let res = hp.sample(&mut r);
443+
counts[(to_signed_bits(res) - a_bits) as usize] += 1;
444+
}
445+
for (count, i) in counts.iter().zip(0 as $ity..) {
446+
let expected = (test_run * N_REPS_PER_RUN) as $ty *
447+
((from_signed_bits(a_bits + i + 1) -
448+
from_signed_bits(a_bits + i)) / (b - a));
449+
let err = (*count as $ty - expected) / expected;
450+
if err.abs() > 0.2 {
451+
if test_run < N_RUNS {
452+
continue 'test_loop_exact;
453+
}
454+
panic!(format!("Failed {}-bit exact test: a: 0x{:x}, b: 0x{:x}, err: {:.2}",
455+
::core::mem::size_of::<$ty>() * 8,
456+
a.to_bits(),
457+
b.to_bits(),
458+
err.abs()));
459+
}
460+
}
461+
}
453462
} else {
454-
b / 10.0 - a / 10.0
455-
};
456-
assert!(step.is_finite());
457-
let mut counts = Vec::<i32>::with_capacity(10);
458-
counts.resize(10, 0);
459-
for _ in 0..3000 {
460-
let res = hp.sample(&mut r);
461-
assert!(a <= res && res < b);
462-
let index = if (res - a).is_finite() {
463-
(res - a) / step
463+
// Otherwise divide range into 10 sections
464+
let step = if (b - a).is_finite() {
465+
(b - a) / 10.0
464466
} else {
465-
res / step - a / step
466-
} as usize;
467-
counts[::core::cmp::min(index, 9)] += 1;
468-
}
469-
for count in &counts {
470-
let expected = 3000.0 as $ty / 10.0;
471-
let err = (*count as $ty - expected) / expected;
472-
assert!(err.abs() <= 0.25);
467+
b / 10.0 - a / 10.0
468+
};
469+
assert!(step.is_finite());
470+
let mut counts = Vec::<i32>::with_capacity(10);
471+
counts.resize(10, 0);
472+
473+
'test_loop_rough: for test_run in 1..(N_RUNS+1) {
474+
for _ in 0..N_REPS_PER_RUN {
475+
let res = hp.sample(&mut r);
476+
assert!(a <= res && res < b);
477+
let index = (res / step - a / step) as usize;
478+
counts[::core::cmp::min(index, 9)] += 1;
479+
}
480+
for count in &counts {
481+
let expected = (test_run * N_REPS_PER_RUN) as $ty / 10.0;
482+
let err = (*count as $ty - expected) / expected;
483+
if err.abs() > 0.2 {
484+
if test_run < N_RUNS {
485+
continue 'test_loop_rough;
486+
}
487+
panic!(format!("Failed {}-bit rough test: a: 0x{:x}, b: 0x{:x}, err: {:.2}",
488+
::core::mem::size_of::<$ty>() * 8,
489+
a.to_bits(),
490+
b.to_bits(),
491+
err.abs()));
492+
}
493+
}
494+
}
473495
}
474496
}
475497
}
476-
}
477-
}}
498+
})()
499+
}
478500
}
479501

480-
float_test!(f64, u64, i64,
481-
[0i64,
482-
0x0000_0f00_0000_0000,
483-
0x0001_0000_0000_0000,
484-
0x0004_0000_0000_0000,
485-
0x0008_0000_0000_0000,
486-
0x0010_0000_0000_0000,
487-
0x0020_0000_0000_0000,
488-
0x0040_0000_0000_0000,
489-
0x0100_0000_0000_0000,
490-
0x00cd_ef12_3456_789a,
491-
0x0100_ffff_ffff_ffff,
492-
0x010f_ffff_ffff_ffff,
493-
0x0400_1234_5678_abcd,
494-
0x7fef_ffff_ffff_ffff,
495-
]);
496-
float_test!(f32, u32, i32,
497-
[0i32,
498-
0x000f_0000,
499-
0x0008_0000,
500-
0x0020_0000,
501-
0x0040_0000,
502-
0x0080_0000,
503-
0x0100_0000,
504-
0x0200_0000,
505-
0x0800_0000,
506-
0x5678_abcd,
507-
0x0807_ffff,
508-
0x087f_ffff,
509-
0x4012_3456,
510-
0x7f7f_ffff,
511-
]);
502+
const SLOW_TESTS: bool = false;
503+
if SLOW_TESTS {
504+
// These test cases are commented out since they
505+
// take too long to run.
506+
float_test!(f64, u64, i64,
507+
[-5, -1, 0, 1, 7],
508+
[0i64,
509+
0x0000_0f00_0000_0000,
510+
0x0001_0000_0000_0000,
511+
0x0004_0000_0000_0000,
512+
0x0008_0000_0000_0000,
513+
0x0010_0000_0000_0000,
514+
0x0020_0000_0000_0000,
515+
0x0040_0000_0000_0000,
516+
0x0100_0000_0000_0000,
517+
0x00cd_ef12_3456_789a,
518+
0x0100_ffff_ffff_ffff,
519+
0x010f_ffff_ffff_ffff,
520+
0x0400_1234_5678_abcd,
521+
0x7fef_ffff_ffff_ffff,
522+
]);
523+
float_test!(f32, u32, i32,
524+
[-5, -1, 0, 1, 7],
525+
[0i32,
526+
0x000f_0000,
527+
0x0008_0000,
528+
0x0020_0000,
529+
0x0040_0000,
530+
0x0080_0000,
531+
0x0100_0000,
532+
0x0200_0000,
533+
0x0800_0000,
534+
0x5678_abcd,
535+
0x0807_ffff,
536+
0x087f_ffff,
537+
0x4012_3456,
538+
0x7f7f_ffff,
539+
]);
540+
} else {
541+
float_test!(f64, u64, i64,
542+
[0],
543+
[0i64,
544+
1,
545+
0x0000_0f00_0000_0000,
546+
0x0000_0f00_0000_0005,
547+
0x000f_ffff_ffff_fffd,
548+
0x0010_0000_0000_0000,
549+
0x0040_0000_0000_0000,
550+
0x0100_ffff_ffff_ffff,
551+
0x0101_0000_0000_0004,
552+
0x7fef_ffff_ffff_ffff,
553+
]);
554+
float_test!(f32, u32, i32,
555+
[0],
556+
[0i32,
557+
1,
558+
0x000f_0000,
559+
0x000f_0005,
560+
0x007f_fffd,
561+
0x0080_0000,
562+
0x0200_0000,
563+
0x0807_ffff,
564+
0x0808_0004,
565+
0x7f7f_ffff,
566+
]);
567+
}
512568
}
513569
}

0 commit comments

Comments
 (0)