#![allow(clippy::unusual_byte_groupings)]
pub const F32_EXP_BITS: u32 = 8;
pub const F32_MANTISSA_BITS: u32 = 23;
pub const F32_EXP_BIAS: i32 = 127;
pub const F32_EXP_FIELD_MAX: u32 = (1 << F32_EXP_BITS) - 1; pub const F32_IMPLICIT: u32 = 1 << F32_MANTISSA_BITS;
pub const F32_QUIET_NAN: u32 = (F32_EXP_FIELD_MAX << F32_MANTISSA_BITS) | (1 << (F32_MANTISSA_BITS - 1));
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SpecialEncoding {
Ieee,
FiniteNanOnly,
Finite,
Unchecked,
}
pub trait FloatSpec: Copy + 'static {
const BITS: u32;
const EXP_BITS: u32;
const MANTISSA_BITS: u32;
const EXP_BIAS: i32;
const SPECIAL: SpecialEncoding;
const HAS_SIGN: bool = true;
const SIGN_SHIFT: u32 = Self::EXP_BITS + Self::MANTISSA_BITS;
const STORAGE_MASK: u32 = (1u32 << Self::BITS) - 1;
const SIGN_MASK: u32 = (Self::HAS_SIGN as u32) << Self::SIGN_SHIFT;
const EXP_FIELD_MAX: u32 = (1u32 << Self::EXP_BITS) - 1;
const EXP_MASK: u32 = Self::EXP_FIELD_MAX << Self::MANTISSA_BITS;
const MANTISSA_MASK: u32 = (1u32 << Self::MANTISSA_BITS) - 1;
const MANTISSA_SHIFT: u32 = F32_MANTISSA_BITS - Self::MANTISSA_BITS;
const EXP_REBIAS: i32 = F32_EXP_BIAS - Self::EXP_BIAS;
const MAX_FINITE_EXP_FIELD: u32 = match Self::SPECIAL {
SpecialEncoding::Ieee | SpecialEncoding::Unchecked => Self::EXP_FIELD_MAX - 1,
SpecialEncoding::FiniteNanOnly | SpecialEncoding::Finite => Self::EXP_FIELD_MAX,
};
const MAX_FINITE_BITS: u32 = match Self::SPECIAL {
SpecialEncoding::Ieee => ((Self::EXP_FIELD_MAX - 1) << Self::MANTISSA_BITS) | Self::MANTISSA_MASK,
SpecialEncoding::FiniteNanOnly => Self::EXP_MASK | (Self::MANTISSA_MASK - 1),
SpecialEncoding::Finite | SpecialEncoding::Unchecked => Self::EXP_MASK | Self::MANTISSA_MASK,
};
const INFINITY_BITS: u32 = Self::EXP_MASK;
const NAN_BITS: u32 = match Self::SPECIAL {
SpecialEncoding::Ieee => Self::EXP_MASK | (1 << (Self::MANTISSA_BITS - 1)),
SpecialEncoding::FiniteNanOnly => Self::EXP_MASK | Self::MANTISSA_MASK,
SpecialEncoding::Finite | SpecialEncoding::Unchecked => 0,
};
const OVERFLOW_BITS: u32 = match Self::SPECIAL {
SpecialEncoding::Ieee => Self::INFINITY_BITS,
SpecialEncoding::FiniteNanOnly | SpecialEncoding::Finite => Self::MAX_FINITE_BITS,
SpecialEncoding::Unchecked => 0,
};
const NAN_OUT_BITS: u32 = match Self::SPECIAL {
SpecialEncoding::Ieee | SpecialEncoding::FiniteNanOnly => Self::NAN_BITS,
SpecialEncoding::Finite => Self::MAX_FINITE_BITS,
SpecialEncoding::Unchecked => 0,
};
#[inline]
fn unpack(packed: u32) -> f32 {
let sign_bit = if Self::HAS_SIGN {
(packed >> Self::SIGN_SHIFT) & 1
} else {
0
};
let exp = (packed >> Self::MANTISSA_BITS) & Self::EXP_FIELD_MAX;
let mant = packed & Self::MANTISSA_MASK;
let f32_sign = sign_bit << 31;
match Self::SPECIAL {
SpecialEncoding::Ieee if exp == Self::EXP_FIELD_MAX => {
let f32_mant = if mant == 0 {
0 } else {
(1 << (F32_MANTISSA_BITS - 1)) | (mant << Self::MANTISSA_SHIFT)
};
return f32::from_bits(f32_sign | (F32_EXP_FIELD_MAX << F32_MANTISSA_BITS) | f32_mant);
}
SpecialEncoding::FiniteNanOnly if exp == Self::EXP_FIELD_MAX && mant == Self::MANTISSA_MASK => {
return f32::from_bits(f32_sign | F32_QUIET_NAN);
}
_ => {}
}
let (implicit, e_eff): (u64, i32) = if exp == 0 { (0, 1) } else { (1, exp as i32) };
let significand = (mant as u64) + (implicit << Self::MANTISSA_BITS);
let pow = e_eff - Self::EXP_BIAS - Self::MANTISSA_BITS as i32;
let scale = f64::from_bits(((F64_EXP_BIAS + pow) as u64) << F64_MANTISSA_BITS);
let magnitude = significand as f64 * scale;
let value = if sign_bit != 0 { -magnitude } else { magnitude };
value as f32
}
#[inline]
fn pack(value: f32) -> u32 {
let bits = value.to_bits();
let sign = (bits >> 31) & 1;
let psign = if Self::HAS_SIGN { sign << Self::SIGN_SHIFT } else { 0 };
let f32_exp = ((bits >> F32_MANTISSA_BITS) & F32_EXP_FIELD_MAX) as i32;
let f32_mant = bits & (F32_IMPLICIT - 1);
if f32_exp == F32_EXP_FIELD_MAX as i32 {
if f32_mant != 0 {
return psign | Self::NAN_OUT_BITS;
}
return psign | Self::OVERFLOW_BITS;
}
if f32_exp == 0 {
return psign;
}
let significand = F32_IMPLICIT | f32_mant;
let mut e = f32_exp - Self::EXP_REBIAS;
let mut shift = Self::MANTISSA_SHIFT as i32;
if e <= 0 {
shift += 1 - e;
e = 0;
}
if shift >= 32 {
return psign; }
let shift = shift as u32;
let keep = significand >> shift;
let rem = significand & ((1u32 << shift) - 1);
let halfway = 1u32 << (shift - 1);
let round_up = rem > halfway || (rem == halfway && (keep & 1) == 1);
let q = keep + round_up as u32;
if e == 0 {
return psign | q;
}
let (mut e, mut frac) = (e, q & Self::MANTISSA_MASK);
if q >> Self::MANTISSA_BITS >= 2 {
e += 1;
frac = 0;
}
if e > Self::MAX_FINITE_EXP_FIELD as i32 {
return psign | Self::OVERFLOW_BITS;
}
let magnitude = ((e as u32) << Self::MANTISSA_BITS) | frac;
let magnitude = match Self::SPECIAL {
SpecialEncoding::FiniteNanOnly if magnitude > Self::MAX_FINITE_BITS => Self::MAX_FINITE_BITS,
_ => magnitude,
};
psign | magnitude
}
}
const F64_EXP_BIAS: i32 = 1023;
const F64_MANTISSA_BITS: u32 = 52;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Fp16;
impl FloatSpec for Fp16 {
const BITS: u32 = 16;
const EXP_BITS: u32 = 5;
const MANTISSA_BITS: u32 = 10;
const EXP_BIAS: i32 = 15;
const SPECIAL: SpecialEncoding = SpecialEncoding::Ieee;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Fp16Fast;
impl FloatSpec for Fp16Fast {
const BITS: u32 = 16;
const EXP_BITS: u32 = 5;
const MANTISSA_BITS: u32 = 10;
const EXP_BIAS: i32 = 15;
const SPECIAL: SpecialEncoding = SpecialEncoding::Unchecked;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Bf16;
impl FloatSpec for Bf16 {
const BITS: u32 = 16;
const EXP_BITS: u32 = 8;
const MANTISSA_BITS: u32 = 7;
const EXP_BIAS: i32 = 127;
const SPECIAL: SpecialEncoding = SpecialEncoding::Ieee;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Fp8E4M3;
impl FloatSpec for Fp8E4M3 {
const BITS: u32 = 8;
const EXP_BITS: u32 = 4;
const MANTISSA_BITS: u32 = 3;
const EXP_BIAS: i32 = 7;
const SPECIAL: SpecialEncoding = SpecialEncoding::FiniteNanOnly;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Fp8E5M2;
impl FloatSpec for Fp8E5M2 {
const BITS: u32 = 8;
const EXP_BITS: u32 = 5;
const MANTISSA_BITS: u32 = 2;
const EXP_BIAS: i32 = 15;
const SPECIAL: SpecialEncoding = SpecialEncoding::Ieee;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fp16_known_values() {
assert_eq!(Fp16::unpack(0x3C00), 1.0); assert_eq!(Fp16::unpack(0xC000), -2.0); assert_eq!(Fp16::unpack(0x7BFF), 65504.0); assert!(Fp16::unpack(0x7C00).is_infinite() && Fp16::unpack(0x7C00) > 0.0);
assert!(Fp16::unpack(0xFC00).is_infinite() && Fp16::unpack(0xFC00) < 0.0);
assert!(Fp16::unpack(0x7E00).is_nan());
assert_eq!(Fp16::unpack(0x0001), 2.0f32.powi(-24)); assert_eq!(Fp16::unpack(0x0000), 0.0);
assert_eq!(Fp16::pack(1.0), 0x3C00);
assert_eq!(Fp16::pack(-2.0), 0xC000);
assert_eq!(Fp16::pack(65504.0), 0x7BFF);
assert_eq!(Fp16::pack(f32::INFINITY), 0x7C00);
assert_eq!(Fp16::pack(1e30), 0x7C00); }
#[test]
fn fp16_fast_flushes_and_decodes_unchecked() {
assert_eq!(Fp16Fast::unpack(0x7C00), 65536.0);
assert_eq!(Fp16Fast::unpack(0x7E00), 98304.0);
assert_eq!(Fp16Fast::unpack(0x3C00), 1.0);
assert_eq!(Fp16Fast::pack(f32::INFINITY), 0x0000);
assert_eq!(Fp16Fast::pack(f32::NEG_INFINITY), 0x8000); assert_eq!(Fp16Fast::pack(f32::NAN), 0x0000);
assert_eq!(Fp16Fast::pack(1e30), 0x0000); assert_eq!(Fp16Fast::pack(1.0), 0x3C00);
assert_eq!(Fp16Fast::pack(-2.0), 0xC000);
assert_eq!(Fp16Fast::pack(65504.0), 0x7BFF);
}
#[test]
fn bf16_is_f32_high_bits() {
for &v in &[1.0f32, -2.5, 100.0, 0.0, 0.015625] {
let packed = (v.to_bits() >> 16) & 0xFFFF;
assert_eq!(Bf16::unpack(packed).to_bits(), v.to_bits() & 0xFFFF_0000);
}
assert!(Bf16::unpack(0x7F80).is_infinite());
assert!(Bf16::unpack(0x7FC0).is_nan());
}
#[test]
fn fp8_e4m3_saturates_and_has_no_inf() {
assert_eq!(Fp8E4M3::unpack(0x70), 128.0); assert_eq!(Fp8E4M3::unpack(0x78), 256.0); assert_eq!(Fp8E4M3::unpack(0x7E), 448.0); assert!(Fp8E4M3::unpack(0x7F).is_nan()); assert_eq!(Fp8E4M3::pack(1000.0), 0x7E); assert_eq!(Fp8E4M3::pack(f32::INFINITY), 0x7E); }
#[test]
fn fp8_e5m2_round_trips() {
for byte in 0u32..256 {
let v = Fp8E5M2::unpack(byte);
if v.is_nan() {
continue;
}
assert_eq!(Fp8E5M2::pack(v), byte, "e5m2 round-trip failed for {byte:#04x}");
}
}
}