use half::f16;
#[inline]
#[must_use]
pub fn f32_to_f16(value: f32) -> u16 {
f16::from_f32(value).to_bits()
}
#[inline]
#[must_use]
pub fn f16_to_f32(bits: u16) -> f32 {
f16::from_bits(bits).to_f32()
}
#[cfg(test)]
mod tests {
use super::{f16_to_f32, f32_to_f16};
fn trueno_ref_f32_to_f16(x: f32) -> u16 {
let bits = x.to_bits();
let sign = ((bits >> 16) & 0x8000) as u16;
let exponent = ((bits >> 23) & 0xFF) as i32;
let mantissa = bits & 0x007F_FFFF;
if exponent == 255 {
if mantissa == 0 {
return sign | 0x7C00;
}
return sign | 0x7C00 | ((mantissa >> 13) as u16).max(1);
}
let new_exp = exponent - 112;
if new_exp >= 31 {
return sign | 0x7C00;
}
if new_exp <= 0 {
if new_exp < -10 {
return sign;
}
let mant = (mantissa | 0x0080_0000) >> (1 - new_exp + 13);
return sign | mant as u16;
}
let round_bit = (mantissa >> 12) & 1;
let mant16 = ((mantissa >> 13) as u16) + round_bit as u16;
sign | ((new_exp as u16) << 10) | (mant16 & 0x03FF)
}
#[test]
fn test_f16_roundtrip_representable() {
for &v in &[0.0_f32, 1.0, -1.0, 0.5, 2.0, -2.5, 100.0, 0.001] {
let back = f16_to_f32(f32_to_f16(v));
assert!(
(back - v).abs() <= v.abs() * 1e-2 + 1e-3,
"v={v} back={back}"
);
}
}
#[test]
fn test_f16_zero_and_neg_zero() {
assert_eq!(f32_to_f16(0.0), 0x0000);
assert_eq!(f32_to_f16(-0.0), 0x8000);
}
#[test]
fn test_f16_parity_with_trueno_ref_known_divergence() {
for &v in &[
0.0_f32, -0.0, 1.0, -1.0, 0.5, -0.5, 2.0, 256.0, -256.0, 65504.0, -65504.0,
] {
assert_eq!(
f32_to_f16(v),
trueno_ref_f32_to_f16(v),
"exact-representable v={v}"
);
}
for &v in &[f32::INFINITY, f32::NEG_INFINITY] {
assert_eq!(f32_to_f16(v), trueno_ref_f32_to_f16(v), "special v={v}");
}
let mut v = -300.0_f32;
let mut diffs = 0usize;
while v < 300.0 {
if !v.is_nan() && f32_to_f16(v) != trueno_ref_f32_to_f16(v) {
diffs += 1;
}
v += 0.013;
}
assert_eq!(
diffs, 31,
"f16 half-vs-trueno divergence count drifted (was 31)"
);
}
}