Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`
Expand All @@ -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

Expand Down
20 changes: 10 additions & 10 deletions src/bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,12 @@ pub const fn next_down_bits<F: Format>(bits: u8) -> u8 {
}
}

pub fn decode_f32<F: Format>(bits: u8) -> f32 {
if F::ZERO == ZeroMode::None {
pub const fn decode_f32<F: Format>(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::<F>(bits) {
Expand Down Expand Up @@ -325,16 +325,16 @@ pub fn decode_f32<F: Format>(bits: u8) -> f32 {
return if sign < 0.0 { -0.0 } else { 0.0 };
}
let exp = exponent_field::<F>(bits);
let mant = f32::from(mantissa_field::<F>(bits));
let mant = mantissa_field::<F>(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
}
Expand Down Expand Up @@ -558,7 +558,7 @@ pub const fn exp2i(exp: i32) -> f32 {
}
}

pub fn total_key<F: Format>(bits: u8) -> i16 {
pub const fn total_key<F: Format>(bits: u8) -> i16 {
let widened = if F::STORAGE_BITS < 8 {
(bits & F::STORAGE_MASK) << (8 - F::STORAGE_BITS)
} else {
Expand All @@ -570,9 +570,9 @@ pub fn total_key<F: Format>(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
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand All @@ -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)
}

Expand Down
47 changes: 37 additions & 10 deletions src/micro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,24 +325,24 @@ impl<F: Format> MicroFloat<F> {
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
Expand All @@ -354,15 +354,18 @@ impl<F: Format> MicroFloat<F> {
/// # 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
Expand All @@ -373,8 +376,16 @@ impl<F: Format> MicroFloat<F> {
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::<F>(self.bits).cmp(&total_key::<F>(other.bits))
pub const fn total_cmp(&self, other: &Self) -> Ordering {
let lhs = total_key::<F>(self.bits);
let rhs = total_key::<F>(other.bits);
if lhs < rhs {
Ordering::Less
} else if lhs > rhs {
Ordering::Greater
} else {
Ordering::Equal
}
}
}

Expand All @@ -386,6 +397,22 @@ fn unary_result<F: Format>(input: MicroFloat<F>, result: f32) -> MicroFloat<F> {
}
}

const fn const_is_lt<F: Format>(lhs: MicroFloat<F>, rhs: MicroFloat<F>) -> bool {
if matches!(lhs.classify(), FpCategory::Zero) && matches!(rhs.classify(), FpCategory::Zero) {
false
} else {
decode_f32::<F>(lhs.bits) < decode_f32::<F>(rhs.bits)
}
}

const fn const_is_gt<F: Format>(lhs: MicroFloat<F>, rhs: MicroFloat<F>) -> bool {
if matches!(lhs.classify(), FpCategory::Zero) && matches!(rhs.classify(), FpCategory::Zero) {
false
} else {
decode_f32::<F>(lhs.bits) > decode_f32::<F>(rhs.bits)
}
}

fn is_odd_integer(value: f32) -> bool {
if !value.is_finite() || !is_integer(value) {
return false;
Expand Down
4 changes: 4 additions & 0 deletions tests/const_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
)*
};
Expand Down