Skip to content

Commit

Permalink
Refactor unit tests with function test_integer_decode
Browse files Browse the repository at this point in the history
  • Loading branch information
mtilda committed Jun 27, 2024
1 parent ea713d5 commit 24606e0
Showing 1 changed file with 76 additions and 84 deletions.
160 changes: 76 additions & 84 deletions src/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2397,108 +2397,100 @@ mod tests {
check::<f64>(1e-12);
}

/// Create a function to test `Float::integer_decode` for a given float type (`f32` or `f64`) and tolerance.
///
/// # Examples
/// ```rs
/// check_integer_decode!(check, f32, 1e-6);
/// check(sign_f * 0.0_f32, 0x000000, -150, sign);
/// ```
macro_rules! check_integer_decode {
(
$func_name:ident,
$float_type:ty,
$tolerance:expr
) => {
/// Test the behavior of `Float::integer_decode(input)`
fn $func_name(input: $float_type, mantissa: u64, exponent: i16, sign: i8) {
let output = crate::float::Float::integer_decode(input);

assert!(
(mantissa, exponent, sign) == output,
"unexpected output of `Float::integer_decode({0:e})`
\texpected: {1}
\t found: {2}",
input,
std::format!("({:#x} {} {})", mantissa, exponent, sign),
std::format!("({:#x} {} {})", output.0, output.1, output.2),
);

// Destructure output and values cast as float
let mantissa_f = output.0 as $float_type;
let exponent_f = output.1 as $float_type;
let sign_f = output.2 as $float_type;

// Recover input using equation: sign * mantissa * 2^exponent
let recovered_input = sign_f * mantissa_f * exponent_f.exp2();
let deviation = recovered_input - input;

assert!(
recovered_input == input || deviation.abs() < $tolerance,
"failed to recover input of `Float::integer_decode({0:e})`
\t expected: ~ {:e}
\t found: {:e}
\tdeviation: {:+e}
\ttolerance: < {:e}",
input,
recovered_input,
deviation,
$tolerance
);
}
};
/// Test the behavior of `Float::integer_decode` with the `given` input and `expected` output values.
fn test_integer_decode<T>(given: T, expected: (u64, i16, i8))
where
T: crate::float::Float + core::fmt::LowerExp + core::fmt::Debug,
{
use crate::float::Float;

let found = Float::integer_decode(given);

assert!(
expected == found,
"unexpected output of `Float::integer_decode({0:e})`
\texpected: ({1:#x} {2} {3})
\t found: {4:#x} {5} {6}",
given,
expected.0,
expected.1,
expected.2,
found.0,
found.1,
found.2
);

// Destructure the `found` output and cast values as float.
let mantissa_f = T::from(found.0).unwrap();
let exponent_f = T::from(found.1).unwrap();
let sign_f = T::from(found.2).unwrap();

// Recover the `given` input using equation: sign * mantissa * 2^exponent.
let recovered = sign_f * mantissa_f * exponent_f.exp2();
let deviation = recovered - given;
let tolerance = T::from(1e-6).unwrap();

assert_eq!(T::one(), tolerance.signum(), "tolerance must be positive");
assert!(
recovered == given || deviation.abs() < tolerance,
"absolute deviation must not exceed tolerance`
\t given: {:+e}
\trecovered: {:+e}
\tdeviation: {:+e}
\ttolerance: <{:e}",
given,
recovered,
deviation,
tolerance
);
}

#[test]
#[cfg(any(feature = "std", feature = "libm"))]
fn integer_decode_f32() {
check_integer_decode!(check, f32, 1e-6);

for sign in [1, -1] {
let sign_f = sign as f32;
check(sign_f * 0.0__f32, 0x000000, -150, sign);
check(sign_f * f32::MIN_POSITIVE, 0x800000, -149, sign);
check(sign_f * 0.25_f32, 0x800000, -25, sign);
check(sign_f * 0.5__f32, 0x800000, -24, sign);
check(sign_f * 1____f32, 0x800000, -23, sign);
check(sign_f * 1.5__f32, 0xc00000, -23, sign);
check(sign_f * 2____f32, 0x800000, -22, sign);
check(sign_f * 2.5__f32, 0xa00000, -22, sign);
check(sign_f * 3____f32, 0xc00000, -22, sign);
check(sign_f * 4____f32, 0x800000, -21, sign);
check(sign_f * 5____f32, 0xa00000, -21, sign);
check(sign_f * 42___f32, 0xa80000, -18, sign);
check(sign_f * f32::MAX, 0xffffff, 104, sign);
check(sign_f * f32::INFINITY, 0x800000, 105, sign);
test_integer_decode(sign_f * 0.0__f32, (0x000000, -150, sign));
test_integer_decode(sign_f * f32::MIN_POSITIVE, (0x800000, -149, sign));
test_integer_decode(sign_f * 0.25_f32, (0x800000, -25, sign));
test_integer_decode(sign_f * 0.5__f32, (0x800000, -24, sign));
test_integer_decode(sign_f * 1____f32, (0x800000, -23, sign));
test_integer_decode(sign_f * 1.5__f32, (0xc00000, -23, sign));
test_integer_decode(sign_f * 2____f32, (0x800000, -22, sign));
test_integer_decode(sign_f * 2.5__f32, (0xa00000, -22, sign));
test_integer_decode(sign_f * 3____f32, (0xc00000, -22, sign));
test_integer_decode(sign_f * 4____f32, (0x800000, -21, sign));
test_integer_decode(sign_f * 5____f32, (0xa00000, -21, sign));
test_integer_decode(sign_f * 42___f32, (0xa80000, -18, sign));
test_integer_decode(sign_f * f32::MAX, (0xffffff, 104, sign));
test_integer_decode(sign_f * f32::INFINITY, (0x800000, 105, sign));
}
// FIXME: Unclear if we should be able to recover NaN inputs
// check(f32::NAN, 0xc00000, 105, 1);
// check_integer_decode(f32::NAN, (0xc00000, 105, 1));
}

#[test]
#[cfg(any(feature = "std", feature = "libm"))]
fn integer_decode_f64() {
check_integer_decode!(check, f64, 1e-6);

for sign in [1, -1] {
let sign_f = sign as f64;
check(sign_f * 0.0__f64, 0x00000000000000, -1075, sign);
check(sign_f * f64::MIN_POSITIVE, 0x10000000000000, -1074, sign);
check(sign_f * 0.25_f64, 0x10000000000000, -54, sign);
check(sign_f * 0.5__f64, 0x10000000000000, -53, sign);
check(sign_f * 1____f64, 0x10000000000000, -52, sign);
check(sign_f * 1.5__f64, 0x18000000000000, -52, sign);
check(sign_f * 2____f64, 0x10000000000000, -51, sign);
check(sign_f * 2.5__f64, 0x14000000000000, -51, sign);
check(sign_f * 3____f64, 0x18000000000000, -51, sign);
check(sign_f * 4____f64, 0x10000000000000, -50, sign);
check(sign_f * 5____f64, 0x14000000000000, -50, sign);
check(sign_f * 42___f64, 0x15000000000000, -47, sign);
check(sign_f * f64::MAX, 0x1fffffffffffff, 971, sign);
check(sign_f * f64::INFINITY, 0x10000000000000, 972, sign);
test_integer_decode(sign_f * 0.0__f64, (0x00000000000000, -1075, sign));
test_integer_decode(sign_f * f64::MIN_POSITIVE, (0x10000000000000, -1074, sign));
test_integer_decode(sign_f * 0.25_f64, (0x10000000000000, -54, sign));
test_integer_decode(sign_f * 0.5__f64, (0x10000000000000, -53, sign));
test_integer_decode(sign_f * 1____f64, (0x10000000000000, -52, sign));
test_integer_decode(sign_f * 1.5__f64, (0x18000000000000, -52, sign));
test_integer_decode(sign_f * 2____f64, (0x10000000000000, -51, sign));
test_integer_decode(sign_f * 2.5__f64, (0x14000000000000, -51, sign));
test_integer_decode(sign_f * 3____f64, (0x18000000000000, -51, sign));
test_integer_decode(sign_f * 4____f64, (0x10000000000000, -50, sign));
test_integer_decode(sign_f * 5____f64, (0x14000000000000, -50, sign));
test_integer_decode(sign_f * 42___f64, (0x15000000000000, -47, sign));
test_integer_decode(sign_f * f64::MAX, (0x1fffffffffffff, 971, sign));
test_integer_decode(sign_f * f64::INFINITY, (0x10000000000000, 972, sign));
}
// FIXME: Unclear if we should be able to recover NaN inputs
// check(f64::NAN, 0x18000000000000, 972, 1);
// check_integer_decode(f64::NAN, (0x18000000000000, 972, 1));
}

#[test]
Expand Down

0 comments on commit 24606e0

Please sign in to comment.