#![allow(clippy::float_cmp)]
use microfloat::f8e5m2;
use std::num::FpCategory;
const fn e5m2_bits(sign: u8, exp: u8, mant: u8) -> u8 {
(sign << 7) | ((exp & 0x1F) << 2) | (mant & 0x3)
}
#[test]
fn e5m2_bias_is_15() {
assert_eq!(f8e5m2::ONE.to_bits(), e5m2_bits(0b0, 0b01111, 0b00));
assert_eq!(
f8e5m2::from_f32(2.0).to_bits(),
e5m2_bits(0b0, 0b10000, 0b00)
);
assert_eq!(
f8e5m2::from_f32(0.5).to_bits(),
e5m2_bits(0b0, 0b01110, 0b00)
);
}
#[test]
fn e5m2_exponent_parameters() {
assert_eq!(f8e5m2::ONE.to_bits(), e5m2_bits(0b0, 0b01111, 0b00));
}
#[test]
fn e5m2_zeros() {
assert_eq!(f8e5m2::ZERO.to_bits(), e5m2_bits(0b0, 0b00000, 0b00));
assert_eq!(f8e5m2::NEG_ZERO.to_bits(), e5m2_bits(0b1, 0b00000, 0b00));
assert!(f8e5m2::ZERO.to_f32() == 0.0);
assert!(f8e5m2::NEG_ZERO.is_sign_negative());
}
#[test]
fn e5m2_max_normal_number() {
let max_normal = f8e5m2::from_f32(57344.0);
assert_eq!(max_normal.to_f32(), 57344.0);
assert_eq!(max_normal.to_bits(), e5m2_bits(0b0, 0b11110, 0b11));
}
#[test]
fn e5m2_exact_max_normal_57344() {
let bits = e5m2_bits(0b0, 0b11110, 0b11);
let val = f8e5m2::from_bits(bits);
assert_eq!(val.to_f32(), 57344.0, "max finite should be exactly 57344");
}
#[test]
fn e5m2_min_normal_number() {
let min_normal = f8e5m2::from_f32(2.0f32.powi(-14));
assert_eq!(min_normal.to_f32(), 2.0f32.powi(-14));
assert_eq!(min_normal.to_bits(), e5m2_bits(0b0, 0b00001, 0b00));
}
#[test]
fn e5m2_exact_min_normal_value() {
let bits = e5m2_bits(0b0, 0b00001, 0b00);
let val = f8e5m2::from_bits(bits);
assert_eq!(
val.to_f32(),
2.0_f32.powi(-14),
"min normal should be exactly 2^-14"
);
}
#[test]
fn e5m2_max_subnormal_number() {
let bits = e5m2_bits(0b0, 0b00000, 0b11);
let max_sub = f8e5m2::from_bits(bits);
let expected = 0.75 * 2.0_f32.powi(-14);
assert_eq!(max_sub.to_f32(), expected);
}
#[test]
fn e5m2_exact_max_subnormal() {
let bits = e5m2_bits(0b0, 0b00000, 0b11);
let val = f8e5m2::from_bits(bits);
let expected = 3.0 * 2.0_f32.powi(-16);
assert_eq!(
val.to_f32(),
expected,
"max subnormal should be exactly 3*2^-16"
);
}
#[test]
fn e5m2_min_subnormal_number() {
let bits = e5m2_bits(0b0, 0b00000, 0b01);
let min_sub = f8e5m2::from_bits(bits);
assert_eq!(min_sub.to_f32(), 2.0_f32.powi(-16));
}
#[test]
fn e5m2_exact_min_subnormal() {
let bits = e5m2_bits(0b0, 0b00000, 0b01);
let val = f8e5m2::from_bits(bits);
assert_eq!(
val.to_f32(),
2.0_f32.powi(-16),
"min subnormal should be exactly 2^-16"
);
}
#[test]
fn e5m2_infinity() {
assert!(f8e5m2::has_inf());
assert_eq!(f8e5m2::INFINITY.to_bits(), e5m2_bits(0b0, 0b11111, 0b00));
assert_eq!(
f8e5m2::NEG_INFINITY.to_bits(),
e5m2_bits(0b1, 0b11111, 0b00)
);
assert!(f8e5m2::INFINITY.to_f32().is_infinite());
assert!(f8e5m2::NEG_INFINITY.to_f32().is_infinite());
assert!(f8e5m2::NEG_INFINITY.to_f32().is_sign_negative());
}
#[test]
fn e5m2_infinity_bit_pattern() {
assert_eq!(f8e5m2::INFINITY.to_bits(), e5m2_bits(0b0, 0b11111, 0b00));
assert_eq!(
f8e5m2::NEG_INFINITY.to_bits(),
e5m2_bits(0b1, 0b11111, 0b00)
);
}
#[test]
fn e5m2_nan() {
assert!(f8e5m2::has_nan());
let nan_bits = e5m2_bits(0b0, 0b11111, 0b10);
let nan = f8e5m2::from_bits(nan_bits);
assert!(nan.to_f32().is_nan());
for bits in [
e5m2_bits(0b0, 0b11111, 0b01),
e5m2_bits(0b0, 0b11111, 0b10),
e5m2_bits(0b0, 0b11111, 0b11),
] {
let val = f8e5m2::from_bits(bits);
assert!(val.to_f32().is_nan(), "bits={bits:02x} should be NaN");
}
}
#[test]
fn e5m2_nan_bit_pattern() {
assert!(f8e5m2::NAN.to_f32().is_nan());
for bits in [
e5m2_bits(0b0, 0b11111, 0b01),
e5m2_bits(0b0, 0b11111, 0b10),
e5m2_bits(0b0, 0b11111, 0b11),
] {
assert!(f8e5m2::from_bits(bits).to_f32().is_nan());
}
for bits in [
e5m2_bits(0b1, 0b11111, 0b01),
e5m2_bits(0b1, 0b11111, 0b10),
e5m2_bits(0b1, 0b11111, 0b11),
] {
assert!(f8e5m2::from_bits(bits).to_f32().is_nan());
}
}
#[test]
fn e5m2_dynamic_range_32_binades() {
assert!(f8e5m2::from_f32(57344.0).to_bits() == e5m2_bits(0b0, 0b11110, 0b11));
assert!(f8e5m2::from_f32(2.0f32.powi(-16)).to_bits() == e5m2_bits(0b0, 0b00000, 0b01));
}
#[test]
fn e5m2_bit_layout() {
assert_eq!(f8e5m2::ONE.to_bits(), e5m2_bits(0b0, 0b01111, 0b00));
assert_eq!(
f8e5m2::from_f32(-1.0).to_bits(),
e5m2_bits(0b1, 0b01111, 0b00)
);
assert_eq!(
f8e5m2::from_f32(57344.0).to_bits(),
e5m2_bits(0b0, 0b11110, 0b11)
);
assert_eq!(
f8e5m2::from_f32(-57344.0).to_bits(),
e5m2_bits(0b1, 0b11110, 0b11)
);
}
#[test]
fn e5m2_roundtrip_normal_values() {
let vals = [
1.0, -1.0, 2.0, -2.0, 4.0, -4.0, 0.5, -0.5, 0.25, -0.25, 8.0, -8.0, 16.0, -16.0, 32.0,
-32.0, 64.0, -64.0, 128.0, -128.0, 1024.0, -1024.0, 4096.0, -4096.0, 8192.0, -8192.0,
16384.0, -16384.0, 32768.0, -32768.0, 57344.0, -57344.0,
];
for &v in &vals {
let encoded = f8e5m2::from_f32(v);
let decoded = encoded.to_f32();
assert!(
(decoded - v).abs() / v.abs() < 1e-2,
"e5m2 roundtrip failed: {v} -> {decoded} (error {:.4}%)",
(decoded - v).abs() / v.abs() * 100.0
);
}
}
#[test]
fn e5m2_roundtrip_subnormal_values() {
let vals = [
2.0_f32.powi(-16), 2.0_f32.powi(-15),
3.0 * 2.0_f32.powi(-16),
3.0 * 2.0_f32.powi(-15), ];
for &v in &vals {
let encoded = f8e5m2::from_f32(v);
let decoded = encoded.to_f32();
assert!(
(decoded - v).abs() < 1e-14,
"e5m2 subnormal roundtrip failed: {v} -> {decoded}"
);
}
}
#[test]
fn e5m2_overflow_to_infinity() {
assert_eq!(
f8e5m2::from_f32(57345.0).to_bits(),
e5m2_bits(0b0, 0b11110, 0b11)
); assert!(f8e5m2::from_f32(f32::INFINITY).is_infinite());
}
#[test]
fn e5m2_underflow_to_subnormal() {
let tiny = 0.5 * 2.0_f32.powi(-16);
let encoded = f8e5m2::from_f32(tiny);
assert!(
encoded.to_bits() == e5m2_bits(0b0, 0b00000, 0b01)
|| encoded.to_bits() == e5m2_bits(0b0, 0b00000, 0b00)
);
}
#[test]
fn e5m2_classify_zero() {
assert_eq!(f8e5m2::ZERO.classify(), FpCategory::Zero);
assert_eq!(f8e5m2::NEG_ZERO.classify(), FpCategory::Zero);
assert_eq!(
f8e5m2::from_bits(e5m2_bits(0b0, 0b00000, 0b01)).classify(),
FpCategory::Subnormal
);
}
#[test]
fn e5m2_classify_subnormal() {
assert_eq!(
f8e5m2::from_bits(e5m2_bits(0b0, 0b00000, 0b01)).classify(),
FpCategory::Subnormal
);
assert_eq!(
f8e5m2::from_bits(e5m2_bits(0b0, 0b00000, 0b11)).classify(),
FpCategory::Subnormal
);
assert_eq!(
f8e5m2::from_bits(e5m2_bits(0b0, 0b00001, 0b00)).classify(),
FpCategory::Normal
);
assert_eq!(f8e5m2::ZERO.classify(), FpCategory::Zero);
}
#[test]
fn e5m2_classify_special() {
assert!(f8e5m2::INFINITY.is_infinite());
assert!(f8e5m2::NEG_INFINITY.is_infinite());
assert!(f8e5m2::NAN.is_nan());
assert!(!f8e5m2::ONE.is_infinite());
assert!(!f8e5m2::ONE.is_nan());
}