diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f3def8..03bda19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Make the following methods `const` on all float types: - `abs()` + - `clamp()` - `classify()` - `classify_bits()` - `copysign()` @@ -23,7 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `is_infinity_bits()` - `is_nan()` - `is_normal()` + - `max()` + - `min()` - `signum()` + - `total_cmp()` ## [0.1.2](https://github.com/LDeakin/microfloat/releases/tag/v0.1.2) - 2026-04-29 diff --git a/src/bits.rs b/src/bits.rs index 76c1685..c562f03 100644 --- a/src/bits.rs +++ b/src/bits.rs @@ -292,12 +292,12 @@ pub const fn next_down_bits(bits: u8) -> u8 { } } -pub fn decode_f32(bits: u8) -> f32 { - if F::ZERO == ZeroMode::None { +pub const fn decode_f32(bits: u8) -> f32 { + if matches!(F::ZERO, ZeroMode::None) { return if bits == 0xff { f32::from_bits(0x7fc0_0000) } else { - exp2i(i32::from(bits) - F::EXPONENT_BIAS) + exp2i(bits as i32 - F::EXPONENT_BIAS) }; } if is_nan_bits::(bits) { @@ -325,16 +325,16 @@ pub fn decode_f32(bits: u8) -> f32 { return if sign < 0.0 { -0.0 } else { 0.0 }; } let exp = exponent_field::(bits); - let mant = f32::from(mantissa_field::(bits)); + let mant = mantissa_field::(bits) as f32; let scale = exp2i(if exp == 0 { 1 - F::EXPONENT_BIAS } else { - i32::from(exp) - F::EXPONENT_BIAS + exp as i32 - F::EXPONENT_BIAS }); let significand = if exp == 0 { - mant / exp2i(i32::from(F::MANTISSA_BITS)) + mant / exp2i(F::MANTISSA_BITS as i32) } else { - 1.0 + mant / exp2i(i32::from(F::MANTISSA_BITS)) + 1.0 + mant / exp2i(F::MANTISSA_BITS as i32) }; sign * significand * scale } @@ -558,7 +558,7 @@ pub const fn exp2i(exp: i32) -> f32 { } } -pub fn total_key(bits: u8) -> i16 { +pub const fn total_key(bits: u8) -> i16 { let widened = if F::STORAGE_BITS < 8 { (bits & F::STORAGE_MASK) << (8 - F::STORAGE_BITS) } else { @@ -570,9 +570,9 @@ pub fn total_key(bits: u8) -> i16 { )] let signed = widened as i8; if signed < 0 { - i16::from(!widened) + (!widened) as i16 } else { - i16::from(widened | 0x80) + (widened | 0x80) as i16 } } diff --git a/src/formats.rs b/src/formats.rs index 2310364..b3efcfc 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -516,14 +516,14 @@ macro_rules! define_format { /// Returns the minimum of `self` and `other`, ignoring NaN when possible. /// /// If exactly one argument is NaN, the other argument is returned. - pub fn min(self, other: Self) -> Self { + pub const fn min(self, other: Self) -> Self { Self(self.0.min(other.0)) } /// Returns the maximum of `self` and `other`, ignoring NaN when possible. /// /// If exactly one argument is NaN, the other argument is returned. - pub fn max(self, other: Self) -> Self { + pub const fn max(self, other: Self) -> Self { Self(self.0.max(other.0)) } @@ -532,14 +532,14 @@ macro_rules! define_format { /// # Panics /// /// Panics if `min` or `max` is `NaN`, or if `min > max`. - pub fn clamp(self, min: Self, max: Self) -> Self { + pub const fn clamp(self, min: Self, max: Self) -> Self { Self(self.0.clamp(min.0, max.0)) } /// Returns a total ordering over all bit patterns in this format. /// /// The ordering distinguishes signed zeros and orders NaN values consistently. - pub fn total_cmp(&self, other: &Self) -> core::cmp::Ordering { + pub const fn total_cmp(&self, other: &Self) -> core::cmp::Ordering { self.0.total_cmp(&other.0) } diff --git a/src/micro.rs b/src/micro.rs index 344e1ad..9976e74 100644 --- a/src/micro.rs +++ b/src/micro.rs @@ -325,24 +325,24 @@ impl MicroFloat { unary_result(self, libm::tanhf(self.to_f32())) } - pub fn min(self, other: Self) -> Self { + pub const fn min(self, other: Self) -> Self { if self.is_nan() && other.is_nan() { self } else if self.is_nan() { other - } else if other.is_nan() || self < other { + } else if other.is_nan() || const_is_lt(self, other) { self } else { other } } - pub fn max(self, other: Self) -> Self { + pub const fn max(self, other: Self) -> Self { if self.is_nan() && other.is_nan() { self } else if self.is_nan() { other - } else if other.is_nan() || self > other { + } else if other.is_nan() || const_is_gt(self, other) { self } else { other @@ -354,15 +354,18 @@ impl MicroFloat { /// # Panics /// /// Panics if `min > max`, `min` is NaN, or `max` is `NaN`. - pub fn clamp(self, min: Self, max: Self) -> Self { + pub const fn clamp(self, min: Self, max: Self) -> Self { assert!( !min.is_nan() && !max.is_nan(), "`min` and `max` must not be `NaN`" ); - assert!(min <= max, "`min` must be less than or equal to `max`"); - if self < min { + assert!( + !const_is_gt(min, max), + "`min` must be less than or equal to `max`" + ); + if const_is_lt(self, min) { min - } else if self > max { + } else if const_is_gt(self, max) { max } else { self @@ -373,8 +376,16 @@ impl MicroFloat { clippy::trivially_copy_pass_by_ref, reason = "signature matches f32::total_cmp for API compatibility" )] - pub fn total_cmp(&self, other: &Self) -> Ordering { - total_key::(self.bits).cmp(&total_key::(other.bits)) + pub const fn total_cmp(&self, other: &Self) -> Ordering { + let lhs = total_key::(self.bits); + let rhs = total_key::(other.bits); + if lhs < rhs { + Ordering::Less + } else if lhs > rhs { + Ordering::Greater + } else { + Ordering::Equal + } } } @@ -386,6 +397,22 @@ fn unary_result(input: MicroFloat, result: f32) -> MicroFloat { } } +const fn const_is_lt(lhs: MicroFloat, rhs: MicroFloat) -> bool { + if matches!(lhs.classify(), FpCategory::Zero) && matches!(rhs.classify(), FpCategory::Zero) { + false + } else { + decode_f32::(lhs.bits) < decode_f32::(rhs.bits) + } +} + +const fn const_is_gt(lhs: MicroFloat, rhs: MicroFloat) -> bool { + if matches!(lhs.classify(), FpCategory::Zero) && matches!(rhs.classify(), FpCategory::Zero) { + false + } else { + decode_f32::(lhs.bits) > decode_f32::(rhs.bits) + } +} + fn is_odd_integer(value: f32) -> bool { if !value.is_finite() || !is_integer(value) { return false; diff --git a/tests/const_methods.rs b/tests/const_methods.rs index 437e396..a64e4a0 100644 --- a/tests/const_methods.rs +++ b/tests/const_methods.rs @@ -31,6 +31,10 @@ macro_rules! assert_const_methods_compile { let _ = value.abs(); let _ = value.next_up(); let _ = value.next_down(); + let _ = value.min(sign); + let _ = value.max(sign); + let _ = value.clamp(value, value); + let _ = value.total_cmp(&sign); }; )* };