Skip to content

Commit 867de5c

Browse files
committed
Test and comment fixes
1 parent f089a53 commit 867de5c

File tree

1 file changed

+167
-110
lines changed

1 file changed

+167
-110
lines changed

src/distributions/float.rs

+167-110
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 }
@@ -399,115 +397,174 @@ mod tests {
399397
let mut r = ::test::rng(601);
400398

401399
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)
400+
($ty:ty, $uty:ty, $ity:ty, $extra:expr, $test_vals:expr) => {
401+
// Create a closure to make loop labels local
402+
(|| {
403+
let mut vals: Vec<$ty> =
404+
$test_vals.iter().cloned()
405+
.flat_map(|x| $extra.iter().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)
420+
}
420421
}
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)
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)
427+
}
427428
}
428-
}
429429

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
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+
const N_RUNS: usize = 10;
435+
const N_REPS_PER_RUN: usize = 1000;
436+
437+
if (b_bits.wrapping_sub(a_bits) as $uty) < 100 {
438+
// If a and b are "close enough", we can verify the full distribution
439+
let mut counts = Vec::<i32>::with_capacity((b_bits - a_bits) as usize);
440+
counts.resize((b_bits - a_bits) as usize, 0);
441+
'test_loop_exact: for test_run in 1..(N_RUNS+1) {
442+
for _ in 0..N_REPS_PER_RUN {
443+
let res = hp.sample(&mut r);
444+
counts[(to_signed_bits(res) - a_bits) as usize] += 1;
445+
}
446+
for (count, i) in counts.iter().zip(0 as $ity..) {
447+
let expected = (test_run * N_REPS_PER_RUN) as $ty *
448+
((from_signed_bits(a_bits + i + 1) -
449+
from_signed_bits(a_bits + i)) / (b - a));
450+
let err = (*count as $ty - expected) / expected;
451+
if err.abs() > 0.2 {
452+
if test_run < N_RUNS {
453+
continue 'test_loop_exact;
454+
}
455+
panic!(format!("Failed {}-bit exact test: a: 0x{:x}, b: 0x{:x}, err: {:.2}",
456+
::core::mem::size_of::<$ty>() * 8,
457+
a.to_bits(),
458+
b.to_bits(),
459+
err.abs()));
460+
}
461+
}
462+
}
453463
} 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
464+
// Otherwise divide range into 10 sections
465+
let step = if (b - a).is_finite() {
466+
(b - a) / 10.0
464467
} 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);
468+
b / 10.0 - a / 10.0
469+
};
470+
assert!(step.is_finite());
471+
let mut counts = Vec::<i32>::with_capacity(10);
472+
counts.resize(10, 0);
473+
474+
'test_loop_rough: for test_run in 1..(N_RUNS+1) {
475+
for _ in 0..N_REPS_PER_RUN {
476+
let res = hp.sample(&mut r);
477+
assert!(a <= res && res < b);
478+
let index = (res / step - a / step) as usize;
479+
counts[::core::cmp::min(index, 9)] += 1;
480+
}
481+
for count in &counts {
482+
let expected = (test_run * N_REPS_PER_RUN) as $ty / 10.0;
483+
let err = (*count as $ty - expected) / expected;
484+
if err.abs() > 0.2 {
485+
if test_run < N_RUNS {
486+
continue 'test_loop_rough;
487+
}
488+
panic!(format!("Failed {}-bit rough test: a: 0x{:x}, b: 0x{:x}, err: {:.2}",
489+
::core::mem::size_of::<$ty>() * 8,
490+
a.to_bits(),
491+
b.to_bits(),
492+
err.abs()));
493+
}
494+
}
495+
}
473496
}
474497
}
475498
}
476-
}
477-
}}
499+
})()
500+
}
478501
}
479502

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

0 commit comments

Comments
 (0)