#[inline]
const fn round_to_even(kept: u32, round_bit: bool, sticky: bool) -> u32 {
if round_bit && (sticky || (kept & 1 == 1)) {
kept + 1
} else {
kept
}
}
#[must_use]
pub fn round_to_bf16(x: f32) -> f32 {
let bits = x.to_bits();
if (bits & 0x7F80_0000) == 0x7F80_0000 && (bits & 0x007F_FFFF) != 0 {
return f32::from_bits(bits | 0x0040_0000); }
let kept = bits >> 16; let round_bit = (bits & 0x0000_8000) != 0;
let sticky = (bits & 0x0000_7FFF) != 0;
let rounded = round_to_even(kept, round_bit, sticky);
f32::from_bits(rounded << 16)
}
#[must_use]
pub fn round_to_f16(x: f32) -> f32 {
f16_bits_to_f32(f32_to_f16_bits(x))
}
#[must_use]
pub fn f32_to_f16_bits(x: f32) -> u16 {
let bits = x.to_bits();
let sign = ((bits >> 16) & 0x8000) as u16; let exp = ((bits >> 23) & 0xFF) as i32; let mant = bits & 0x007F_FFFF;
if exp == 0xFF {
return if mant != 0 {
sign | 0x7E00
} else {
sign | 0x7C00
};
}
let unbiased = exp - 127;
let f16_exp = unbiased + 15;
if f16_exp >= 0x1F {
return sign | 0x7C00;
}
if f16_exp <= 0 {
if f16_exp < -10 {
return sign;
}
let significand = mant | 0x0080_0000;
let shift = 14 - f16_exp; debug_assert!((14..=24).contains(&shift));
let kept = significand >> shift;
let round_bit = (significand >> (shift - 1)) & 1 == 1;
let sticky = (significand & ((1u32 << (shift - 1)) - 1)) != 0;
let mant16 = round_to_even(kept, round_bit, sticky) as u16;
return sign | mant16;
}
let kept = mant >> 13;
let round_bit = (mant & 0x0000_1000) != 0;
let sticky = (mant & 0x0000_0FFF) != 0;
let mant16 = round_to_even(kept, round_bit, sticky);
let packed = ((f16_exp as u16) << 10) | (mant16 as u16 & 0x03FF);
let carry = (mant16 >> 10) as u16; sign | (packed + (carry << 10))
}
#[must_use]
pub fn f16_bits_to_f32(h: u16) -> f32 {
let sign = ((h & 0x8000) as u32) << 16;
let exp = ((h >> 10) & 0x1F) as u32;
let mant = (h & 0x03FF) as u32;
if exp == 0 {
if mant == 0 {
return f32::from_bits(sign);
}
let mut m = mant;
let mut s: u32 = 0;
while (m & 0x0400) == 0 {
m <<= 1;
s += 1;
}
m &= 0x03FF; let f32_exp = 113 - s; return f32::from_bits(sign | (f32_exp << 23) | (m << 13));
}
if exp == 0x1F {
return if mant == 0 {
f32::from_bits(sign | 0x7F80_0000)
} else {
f32::from_bits(sign | 0x7FC0_0000) };
}
let f32_exp = exp + (127 - 15);
f32::from_bits(sign | (f32_exp << 23) | (mant << 13))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bf16_exact_for_representable_values() {
for &v in &[0.0f32, 1.0, -1.0, 2.0, 0.5, -0.25, 256.0, 1.5, 3.0, 100.0] {
assert_eq!(round_to_bf16(v), v, "bf16 round-trip changed {v}");
}
}
#[test]
fn bf16_is_top_16_bits_with_rounding() {
let x = f32::from_bits(0x3F80_0000 | 0x0000_8000); let r = round_to_bf16(x);
assert_eq!(r, 1.0, "exact tie should round to even (1.0)");
let x2 = f32::from_bits(0x3F80_0000 | 0x0000_8001);
let up = round_to_bf16(x2);
assert_eq!(up, f32::from_bits(0x3F81_0000));
assert!(up > 1.0);
}
#[test]
fn bf16_rounding_can_carry_into_exponent() {
let almost_two = f32::from_bits(0x3FFF_FFFF); assert_eq!(round_to_bf16(almost_two), 2.0);
}
#[test]
fn bf16_preserves_inf_and_nan() {
assert_eq!(round_to_bf16(f32::INFINITY), f32::INFINITY);
assert_eq!(round_to_bf16(f32::NEG_INFINITY), f32::NEG_INFINITY);
assert!(round_to_bf16(f32::NAN).is_nan());
}
#[test]
fn f16_exact_for_representable_values() {
for &v in &[
0.0f32, 1.0, -1.0, 2.0, 0.5, -0.25, 0.125, 1.5, 3.0, 10.0, 1024.0, -2048.0,
] {
assert_eq!(round_to_f16(v), v, "f16 round-trip changed {v}");
}
}
#[test]
fn f16_known_bit_patterns() {
assert_eq!(f32_to_f16_bits(1.0), 0x3C00);
assert_eq!(f32_to_f16_bits(2.0), 0x4000);
assert_eq!(f32_to_f16_bits(-2.0), 0xC000);
assert_eq!(f32_to_f16_bits(0.5), 0x3800);
assert_eq!(f32_to_f16_bits(0.0), 0x0000);
assert_eq!(f16_bits_to_f32(0x3C00), 1.0);
assert_eq!(f16_bits_to_f32(0x4000), 2.0);
assert_eq!(f16_bits_to_f32(0x3800), 0.5);
}
#[test]
fn f16_overflow_saturates_to_infinity() {
assert_eq!(round_to_f16(70_000.0), f32::INFINITY);
assert_eq!(round_to_f16(-70_000.0), f32::NEG_INFINITY);
assert_eq!(round_to_f16(65_504.0), 65_504.0);
}
#[test]
fn f16_smallest_normal_and_subnormals() {
let min_normal = f32::from_bits(0x3880_0000); assert_eq!(round_to_f16(min_normal), min_normal);
assert_eq!(f32_to_f16_bits(min_normal), 0x0400);
let min_sub = (2.0f32).powi(-24);
assert_eq!(f32_to_f16_bits(min_sub), 0x0001);
assert_eq!(round_to_f16(min_sub), min_sub);
let tiny = (2.0f32).powi(-30);
assert_eq!(round_to_f16(tiny), 0.0);
}
#[test]
fn f16_subnormal_round_to_even() {
let half_ulp = (2.0f32).powi(-25);
assert_eq!(round_to_f16(half_ulp), 0.0);
let above = f32::from_bits(half_ulp.to_bits() + 1);
assert_eq!(round_to_f16(above), (2.0f32).powi(-24));
}
#[test]
fn f16_rounding_carry_into_exponent() {
let almost_one = f32::from_bits(0x3F7F_FFFF);
assert_eq!(round_to_f16(almost_one), 1.0);
}
#[test]
fn f16_preserves_inf_and_nan() {
assert_eq!(round_to_f16(f32::INFINITY), f32::INFINITY);
assert_eq!(round_to_f16(f32::NEG_INFINITY), f32::NEG_INFINITY);
assert!(round_to_f16(f32::NAN).is_nan());
}
#[test]
fn f16_negative_subnormal_round_trip() {
let v = -(2.0f32).powi(-24);
assert_eq!(round_to_f16(v), v);
assert_eq!(f32_to_f16_bits(v), 0x8001);
}
}