No need for mangling bits:
#[derive(PartialEq, std::fmt::Debug)]
enum Direction { Equal, Up, Down }
fn get_rounding_direction(v: f64) -> Direction {
match v.partial_cmp(&(v as f32 as f64)) {
Some(Ordering::Greater) => Direction::Down,
Some(Ordering::Less) => Direction::Up,
_ => Direction::Equal
}
}
And some tests to check correctness.
#[cfg(test)]
#[test]
fn test_get_rounding_direction() {
// check that the f64 one step below 2 casts to exactly 2
assert_eq!(get_rounding_direction(1.9999999999999998), Direction::Up);
// check edge cases
assert_eq!(get_rounding_direction(f64::NAN), Direction::Equal);
assert_eq!(get_rounding_direction(f64::NEG_INFINITY), Direction::Equal);
assert_eq!(get_rounding_direction(f64::MIN), Direction::Down);
assert_eq!(get_rounding_direction(-f64::MIN_POSITIVE), Direction::Up);
assert_eq!(get_rounding_direction(-0.), Direction::Equal);
assert_eq!(get_rounding_direction(0.), Direction::Equal);
assert_eq!(get_rounding_direction(f64::MIN_POSITIVE), Direction::Down);
assert_eq!(get_rounding_direction(f64::MAX), Direction::Up);
assert_eq!(get_rounding_direction(f64::INFINITY), Direction::Equal);
// for all other f32
for u32_bits in 1..f32::INFINITY.to_bits() - 1 {
let f64_value = f32::from_bits(u32_bits) as f64;
let u64_bits = f64_value.to_bits();
if u32_bits % 100_000_000 == 0 {
println!("checkpoint every 600 million tests: {}", f64_value);
}
// check that the f64 equivalent to the current f32 casts to a value that is equivalent
assert_eq!(get_rounding_direction(f64_value), Direction::Equal, "at {}, {}", u32_bits, f64_value);
// check that the f64 one step below the f64 equivalent to the current f32 casts to a value that is one step greater
assert_eq!(get_rounding_direction(f64::from_bits(u64_bits - 1)), Direction::Up, "at {}, {}", u32_bits, f64_value);
// check that the f64 one step above the f64 equivalent to the current f32 casts to a value that is one step less
assert_eq!(get_rounding_direction(f64::from_bits(u64_bits + 1)), Direction::Down, "at {}, {}", u32_bits, f64_value);
// same checks for negative numbers
let u64_bits = (-f64_value).to_bits();
assert_eq!(get_rounding_direction(f64_value), Direction::Equal, "at {}, {}", u32_bits, f64_value);
assert_eq!(get_rounding_direction(f64::from_bits(u64_bits - 1)), Direction::Down, "at {}, {}", u32_bits, f64_value);
assert_eq!(get_rounding_direction(f64::from_bits(u64_bits + 1)), Direction::Up, "at {}, {}", u32_bits, f64_value);
}
}
To specifically cast with rounding towards infinity:
fn cast_toward_inf(vf64: f64) -> f32 {
let vf32 = vf64 as f32;
if vf64 > vf32 as f64 { f32::from_bits(vf32.to_bits() + 1) } else { vf32 }
}
It's possible to determine the rounding primarily from the 28th bit (first bit of the truncated portion of the mantissa), but handling edge cases introduces significant complexity.