diff --git a/src/bits.rs b/src/bits.rs index afefeeb..27f695e 100644 --- a/src/bits.rs +++ b/src/bits.rs @@ -175,7 +175,7 @@ pub const fn is_nan_bits(bits: u8) -> bool { } pub fn is_infinity_bits(bits: u8) -> bool { - F::OVERFLOW == Overflow::Infinity + F::HAS_INF && exponent_field::(bits) == F::MAX_EXPONENT_FIELD && mantissa_field::(bits) == 0 } diff --git a/src/format.rs b/src/format.rs index 977c9b5..282e8c9 100644 --- a/src/format.rs +++ b/src/format.rs @@ -60,4 +60,102 @@ pub trait Format: Copy + 'static { reason = "format exponent fields fit in eight storage bits" )] const MAX_EXPONENT_FIELD: u8 = ((1u16 << Self::EXPONENT_BITS) - 1) as u8; + + /// Returns `true` if this format can represent infinity. + /// + /// A format has infinity when its overflow behavior is + /// [`Overflow::Infinity`]. Formats with `Overflow::Nan` or + /// `Overflow::Saturate` do not represent infinity. + const HAS_INF: bool = matches!(Self::OVERFLOW, Overflow::Infinity); + + /// Returns `true` if this format can represent NaN values. + /// + /// A format has NaN if its NaN encoding is anything except + /// [`NanEncoding::None`]. + const HAS_NAN: bool = !matches!(Self::NAN, NanEncoding::None); + + /// Returns `true` if this format saturates on overflow. + const HAS_FINITE_ONLY: bool = matches!(Self::OVERFLOW, Overflow::Saturate); +} + +#[cfg(test)] +#[expect( + clippy::assertions_on_constants, + reason = "tests verify const trait associated constants" +)] +mod tests { + use super::*; + use crate::{ + Float4E2M1FnFormat, Float6E2M3FnFormat, Float6E3M2FnFormat, Float8E3M4Format, + Float8E4M3B11FnuzFormat, Float8E4M3FnFormat, Float8E4M3FnuzFormat, Float8E4M3Format, + Float8E5M2FnuzFormat, Float8E5M2Format, Float8E8M0FnuFormat, + }; + + // Single NaN with Unsigned zero (E4M3B11FNuz) — has NaN, no Inf, no saturate + #[test] + fn single_nan_unsigned_zero_b11_format() { + assert!(!::HAS_INF); + assert!(::HAS_NAN); + assert!(!::HAS_FINITE_ONLY); + } + + // IEEE-like formats (E5M2, E4M3, E3M4) — has Inf, has NaN, no saturate + #[test] + fn ieee_formats_has_inf_nan() { + assert!(::HAS_INF); + assert!(::HAS_NAN); + assert!(!::HAS_FINITE_ONLY); + + assert!(::HAS_INF); + assert!(::HAS_NAN); + assert!(!::HAS_FINITE_ONLY); + + assert!(::HAS_INF); + assert!(::HAS_NAN); + assert!(!::HAS_FINITE_ONLY); + } + + // Outer NaN formats (E4M3FN) — has NaN, no Inf, no saturate + #[test] + fn outer_nan_formats_has_nan() { + assert!(!::HAS_INF); + assert!(::HAS_NAN); + assert!(!::HAS_FINITE_ONLY); + } + + // Single NaN, Unsigned zero formats (E4M3FNuz, E5M2FNuz) — has NaN, no Inf + #[test] + fn single_nan_unsigned_zero_formats() { + assert!(!::HAS_INF); + assert!(::HAS_NAN); + assert!(!::HAS_FINITE_ONLY); + + assert!(!::HAS_INF); + assert!(::HAS_NAN); + assert!(!::HAS_FINITE_ONLY); + } + + // Single NaN, unsigned sign format (E8M0FNU) — has NaN, no Inf + #[test] + fn unsigned_e8m0_format() { + assert!(!::HAS_INF); + assert!(::HAS_NAN); + assert!(!::HAS_FINITE_ONLY); + } + + // Saturate formats (F4, F6) — no Inf, no NaN, finite only + #[test] + fn saturate_formats_finite_only() { + assert!(!::HAS_INF); + assert!(!::HAS_NAN); + assert!(::HAS_FINITE_ONLY); + + assert!(!::HAS_INF); + assert!(!::HAS_NAN); + assert!(::HAS_FINITE_ONLY); + + assert!(!::HAS_INF); + assert!(!::HAS_NAN); + assert!(::HAS_FINITE_ONLY); + } } diff --git a/src/formats.rs b/src/formats.rs index 92383dc..6d03ef7 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -274,6 +274,21 @@ macro_rules! define_format { self.0.to_ne_bytes() } + /// Returns `true` if this format can represent infinity. + pub const fn has_inf() -> bool { + <$format as crate::format::Format>::HAS_INF + } + + /// Returns `true` if this format can represent NaN values. + pub const fn has_nan() -> bool { + <$format as crate::format::Format>::HAS_NAN + } + + /// Returns `true` if this format saturates on overflow (finite-only). + pub const fn is_finite_only() -> bool { + <$format as crate::format::Format>::HAS_FINITE_ONLY + } + /// Converts an `f32` to this format. /// /// The result is rounded to the nearest representable value. Values outside the diff --git a/src/micro.rs b/src/micro.rs index 523c487..e14335d 100644 --- a/src/micro.rs +++ b/src/micro.rs @@ -186,7 +186,7 @@ impl MicroFloat { } return self; } - if F::NAN == NanEncoding::None + if !F::HAS_NAN && self.is_sign_negative() && self.classify() != FpCategory::Zero && !is_integer(n.to_f32()) @@ -197,10 +197,7 @@ impl MicroFloat { } pub fn sqrt(self) -> Self { - if F::NAN == NanEncoding::None - && self.is_sign_negative() - && self.classify() != FpCategory::Zero - { + if !F::HAS_NAN && self.is_sign_negative() && self.classify() != FpCategory::Zero { return Self::ZERO; } unary_result(self, libm::sqrtf(self.to_f32())) @@ -219,27 +216,21 @@ impl MicroFloat { } pub fn ln(self) -> Self { - if F::NAN == NanEncoding::None - && self.is_sign_negative() - && self.classify() != FpCategory::Zero - { + if !F::HAS_NAN && self.is_sign_negative() && self.classify() != FpCategory::Zero { return Self::ZERO; } unary_result(self, libm::logf(self.to_f32())) } pub fn ln_1p(self) -> Self { - if F::NAN == NanEncoding::None && self.to_f32() < -1.0 { + if !F::HAS_NAN && self.to_f32() < -1.0 { return Self::ZERO; } unary_result(self, libm::log1pf(self.to_f32())) } pub fn log2(self) -> Self { - if F::NAN == NanEncoding::None - && self.is_sign_negative() - && self.classify() != FpCategory::Zero - { + if !F::HAS_NAN && self.is_sign_negative() && self.classify() != FpCategory::Zero { return Self::ZERO; } unary_result(self, libm::log2f(self.to_f32())) diff --git a/tests/format_edges.rs b/tests/format_edges.rs index c945ee4..9b1fd66 100644 --- a/tests/format_edges.rs +++ b/tests/format_edges.rs @@ -1,4 +1,7 @@ -use microfloat::{f4e2m1fn, f8e4m3, f8e4m3b11fnuz, f8e4m3fn, f8e4m3fnuz, f8e8m0fnu}; +use microfloat::{ + f4e2m1fn, f6e2m3fn, f6e3m2fn, f8e3m4, f8e4m3, f8e4m3b11fnuz, f8e4m3fn, f8e4m3fnuz, f8e5m2, + f8e5m2fnuz, f8e8m0fnu, +}; #[test] fn special_constants_cover_format_modes() { @@ -38,3 +41,50 @@ fn clamp_panics_when_min_is_nan() { fn clamp_panics_when_max_is_nan() { let _ = std::panic::catch_unwind(|| f8e4m3::ZERO.clamp(f8e4m3::ONE, f8e4m3::NAN)); } + +#[test] +fn format_query_helpers() { + assert!(f8e3m4::has_inf()); + assert!(f8e3m4::has_nan()); + assert!(!f8e3m4::is_finite_only()); + + assert!(f8e4m3::has_inf()); + assert!(f8e4m3::has_nan()); + assert!(!f8e4m3::is_finite_only()); + + assert!(f8e5m2::has_inf()); + assert!(f8e5m2::has_nan()); + assert!(!f8e5m2::is_finite_only()); + + assert!(!f8e4m3fn::has_inf()); + assert!(f8e4m3fn::has_nan()); + assert!(!f8e4m3fn::is_finite_only()); + + assert!(!f8e4m3fnuz::has_inf()); + assert!(f8e4m3fnuz::has_nan()); + assert!(!f8e4m3fnuz::is_finite_only()); + + assert!(!f8e5m2fnuz::has_inf()); + assert!(f8e5m2fnuz::has_nan()); + assert!(!f8e5m2fnuz::is_finite_only()); + + assert!(!f8e4m3b11fnuz::has_inf()); + assert!(f8e4m3b11fnuz::has_nan()); + assert!(!f8e4m3b11fnuz::is_finite_only()); + + assert!(!f8e8m0fnu::has_inf()); + assert!(f8e8m0fnu::has_nan()); + assert!(!f8e8m0fnu::is_finite_only()); + + assert!(!f4e2m1fn::has_inf()); + assert!(!f4e2m1fn::has_nan()); + assert!(f4e2m1fn::is_finite_only()); + + assert!(!f6e2m3fn::has_inf()); + assert!(!f6e2m3fn::has_nan()); + assert!(f6e2m3fn::is_finite_only()); + + assert!(!f6e3m2fn::has_inf()); + assert!(!f6e3m2fn::has_nan()); + assert!(f6e3m2fn::is_finite_only()); +}