Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit 430fe28

Browse files
committed
Add biteq and exp_unbiased to Float
These are two convenience methods. Additionally, add tests for the trait methods, and an `assert_biteq!` macro to check and print the output.
1 parent d5ebe5d commit 430fe28

File tree

3 files changed

+143
-10
lines changed

3 files changed

+143
-10
lines changed

src/math/support/float_traits.rs

+117-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::{fmt, mem, ops};
22

3-
use super::int_traits::{CastInto, Int, MinInt};
3+
use super::int_traits::{CastFrom, CastInto, Int, MinInt};
44

55
/// Trait for some basic operations on floats
66
#[allow(dead_code)]
@@ -73,11 +73,18 @@ pub trait Float:
7373
self.to_bits().signed()
7474
}
7575

76+
/// Check bitwise equality.
77+
fn biteq(self, rhs: Self) -> bool {
78+
self.to_bits() == rhs.to_bits()
79+
}
80+
7681
/// Checks if two floats have the same bit representation. *Except* for NaNs! NaN can be
77-
/// represented in multiple different ways. This method returns `true` if two NaNs are
78-
/// compared.
82+
/// represented in multiple different ways.
83+
///
84+
/// This method returns `true` if two NaNs are compared. Use [`biteq`](Self::biteq) instead
85+
/// if `NaN` should not be treated separately.
7986
fn eq_repr(self, rhs: Self) -> bool {
80-
if self.is_nan() && rhs.is_nan() { true } else { self.to_bits() == rhs.to_bits() }
87+
if self.is_nan() && rhs.is_nan() { true } else { self.biteq(rhs) }
8188
}
8289

8390
/// Returns true if the value is NaN.
@@ -94,17 +101,22 @@ pub trait Float:
94101
(self.to_bits() & Self::EXP_MASK) == Self::Int::ZERO
95102
}
96103

97-
/// Returns the exponent, not adjusting for bias.
104+
/// Returns the exponent, not adjusting for bias, not accounting for subnormals or zero.
98105
fn exp(self) -> i32 {
99106
((self.to_bits() & Self::EXP_MASK) >> Self::SIG_BITS).cast()
100107
}
101108

109+
/// Extract the exponent and adjust it for bias, not accounting for subnormals or zero.
110+
fn exp_unbiased(self) -> i32 {
111+
self.exp() - (Self::EXP_BIAS as i32)
112+
}
113+
102114
/// Returns the significand with no implicit bit (or the "fractional" part)
103115
fn frac(self) -> Self::Int {
104116
self.to_bits() & Self::SIG_MASK
105117
}
106118

107-
/// Returns the significand with implicit bit
119+
/// Returns the significand with implicit bit.
108120
fn imp_frac(self) -> Self::Int {
109121
self.frac() | Self::IMPLICIT_BIT
110122
}
@@ -113,11 +125,11 @@ pub trait Float:
113125
fn from_bits(a: Self::Int) -> Self;
114126

115127
/// Constructs a `Self` from its parts. Inputs are treated as bits and shifted into position.
116-
fn from_parts(negative: bool, exponent: Self::Int, significand: Self::Int) -> Self {
128+
fn from_parts(negative: bool, exponent: i32, significand: Self::Int) -> Self {
117129
let sign = if negative { Self::Int::ONE } else { Self::Int::ZERO };
118130
Self::from_bits(
119131
(sign << (Self::BITS - 1))
120-
| ((exponent << Self::SIG_BITS) & Self::EXP_MASK)
132+
| (Self::Int::cast_from(exponent as u32 & Self::EXP_MAX) << Self::SIG_BITS)
121133
| (significand & Self::SIG_MASK),
122134
)
123135
}
@@ -239,3 +251,100 @@ pub const fn f64_from_bits(bits: u64) -> f64 {
239251
// SAFETY: POD cast with no preconditions
240252
unsafe { mem::transmute::<u64, f64>(bits) }
241253
}
254+
255+
#[cfg(test)]
256+
mod tests {
257+
use super::*;
258+
259+
#[test]
260+
#[cfg(f16_enabled)]
261+
fn check_f16() {
262+
// Constants
263+
assert_eq!(f16::EXP_MAX, 0b11111);
264+
assert_eq!(f16::EXP_BIAS, 15);
265+
266+
// `exp_unbiased`
267+
assert_eq!(f16::FRAC_PI_2.exp_unbiased(), 0);
268+
assert_eq!((1.0f16 / 2.0).exp_unbiased(), -1);
269+
assert_eq!(f16::MAX.exp_unbiased(), 15);
270+
assert_eq!(f16::MIN.exp_unbiased(), 15);
271+
assert_eq!(f16::MIN_POSITIVE.exp_unbiased(), -14);
272+
// This is a convenience method and not ldexp, `exp_unbiased` does not return correct
273+
// results for zero and subnormals.
274+
assert_eq!(f16::ZERO.exp_unbiased(), -15);
275+
assert_eq!(f16::from_bits(0x1).exp_unbiased(), -15);
276+
277+
// `from_parts`
278+
assert_biteq!(f16::from_parts(true, f16::EXP_BIAS as i32, 0), -1.0f16);
279+
assert_biteq!(f16::from_parts(false, 0, 1), f16::from_bits(0x1));
280+
}
281+
282+
#[test]
283+
fn check_f32() {
284+
// Constants
285+
assert_eq!(f32::EXP_MAX, 0b11111111);
286+
assert_eq!(f32::EXP_BIAS, 127);
287+
288+
// `exp_unbiased`
289+
assert_eq!(f32::FRAC_PI_2.exp_unbiased(), 0);
290+
assert_eq!((1.0f32 / 2.0).exp_unbiased(), -1);
291+
assert_eq!(f32::MAX.exp_unbiased(), 127);
292+
assert_eq!(f32::MIN.exp_unbiased(), 127);
293+
assert_eq!(f32::MIN_POSITIVE.exp_unbiased(), -126);
294+
// This is a convenience method and not ldexp, `exp_unbiased` does not return correct
295+
// results for zero and subnormals.
296+
assert_eq!(f32::ZERO.exp_unbiased(), -127);
297+
assert_eq!(f32::from_bits(0x1).exp_unbiased(), -127);
298+
299+
// `from_parts`
300+
assert_biteq!(f32::from_parts(true, f32::EXP_BIAS as i32, 0), -1.0f32);
301+
assert_biteq!(f32::from_parts(false, 10 + f32::EXP_BIAS as i32, 0), hf32!("0x1p10"));
302+
assert_biteq!(f32::from_parts(false, 0, 1), f32::from_bits(0x1));
303+
}
304+
305+
#[test]
306+
fn check_f64() {
307+
// Constants
308+
assert_eq!(f64::EXP_MAX, 0b11111111111);
309+
assert_eq!(f64::EXP_BIAS, 1023);
310+
311+
// `exp_unbiased`
312+
assert_eq!(f64::FRAC_PI_2.exp_unbiased(), 0);
313+
assert_eq!((1.0f64 / 2.0).exp_unbiased(), -1);
314+
assert_eq!(f64::MAX.exp_unbiased(), 1023);
315+
assert_eq!(f64::MIN.exp_unbiased(), 1023);
316+
assert_eq!(f64::MIN_POSITIVE.exp_unbiased(), -1022);
317+
// This is a convenience method and not ldexp, `exp_unbiased` does not return correct
318+
// results for zero and subnormals.
319+
assert_eq!(f64::ZERO.exp_unbiased(), -1023);
320+
assert_eq!(f64::from_bits(0x1).exp_unbiased(), -1023);
321+
322+
// `from_parts`
323+
assert_biteq!(f64::from_parts(true, f64::EXP_BIAS as i32, 0), -1.0f64);
324+
assert_biteq!(f64::from_parts(false, 10 + f64::EXP_BIAS as i32, 0), hf64!("0x1p10"));
325+
assert_biteq!(f64::from_parts(false, 0, 1), f64::from_bits(0x1));
326+
}
327+
328+
#[test]
329+
#[cfg(f128_enabled)]
330+
fn check_f128() {
331+
// Constants
332+
assert_eq!(f128::EXP_MAX, 0b111111111111111);
333+
assert_eq!(f128::EXP_BIAS, 16383);
334+
335+
// `exp_unbiased`
336+
assert_eq!(f128::FRAC_PI_2.exp_unbiased(), 0);
337+
assert_eq!((1.0f128 / 2.0).exp_unbiased(), -1);
338+
assert_eq!(f128::MAX.exp_unbiased(), 16383);
339+
assert_eq!(f128::MIN.exp_unbiased(), 16383);
340+
assert_eq!(f128::MIN_POSITIVE.exp_unbiased(), -16382);
341+
// This is a convenience method and not ldexp, `exp_unbiased` does not return correct
342+
// results for zero and subnormals.
343+
assert_eq!(f128::ZERO.exp_unbiased(), -16383);
344+
assert_eq!(f128::from_bits(0x1).exp_unbiased(), -16383);
345+
346+
// `from_parts`
347+
assert_biteq!(f128::from_parts(true, f128::EXP_BIAS as i32, 0), -1.0f128);
348+
assert_biteq!(f128::from_parts(false, 0, 1), f128::from_bits(0x1));
349+
}
350+
}

src/math/support/int_traits.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,14 @@ pub trait Int:
5454
+ ops::BitXor<Output = Self>
5555
+ ops::BitAnd<Output = Self>
5656
+ cmp::Ord
57-
+ CastInto<usize>
58-
+ CastInto<i32>
5957
+ CastFrom<i32>
58+
+ CastFrom<u32>
6059
+ CastFrom<u8>
60+
+ CastFrom<usize>
61+
+ CastInto<i32>
62+
+ CastInto<u32>
63+
+ CastInto<u8>
64+
+ CastInto<usize>
6165
{
6266
fn signed(self) -> OtherSign<Self::Unsigned>;
6367
fn unsigned(self) -> Self::Unsigned;

src/math/support/macros.rs

+20
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,23 @@ macro_rules! hf64 {
106106
X
107107
}};
108108
}
109+
110+
/// Assert `F::biteq` with better messages.
111+
#[cfg(test)]
112+
macro_rules! assert_biteq {
113+
($left:expr, $right:expr, $($arg:tt)*) => {{
114+
let bits = ($left.to_bits() * 0).leading_zeros(); // hack to get the width from the value
115+
assert!(
116+
$left.biteq($right),
117+
"\nl: {l:?} ({lb:#0width$x})\nr: {r:?} ({rb:#0width$x})",
118+
l = $left,
119+
lb = $left.to_bits(),
120+
r = $right,
121+
rb = $right.to_bits(),
122+
width = ((bits / 4) + 2) as usize
123+
);
124+
}};
125+
($left:expr, $right:expr $(,)?) => {
126+
assert_biteq!($left, $right,)
127+
};
128+
}

0 commit comments

Comments
 (0)