float_bits/
lib.rs

1//! Floats stored as raw bits, making them hashable and totally ordered.
2//!
3//! The types in this crate represent IEEE 754 binary floating point numbers, using unsigned
4//! integers to store the raw bits of the floats.  Some of these types represent float formats not
5//! supported by Rust, or only if specific Rust features are available and enabled.  A limited
6//! suite of operations are available that operate directly on the raw bit representation,
7//! bypassing the need for Rust support.
8//!
9//! # Example
10//!
11//! ```rust
12//! # use float_bits::F64;
13//!
14//! let x: f64 = 0.1;
15//! let y: F64 = x.into();
16//! let z: f64 = y.into();
17//! assert_eq!(x, z);
18//! assert_eq!(0x3fb999999999999a, y.to_bits());
19//! ```
20
21#![no_std]
22#![cfg_attr(feature = "f16", feature(f16))]
23#![cfg_attr(feature = "f128", feature(f128))]
24#![allow(missing_docs)]
25
26#[macro_use]
27mod macros;
28
29mod helpers;
30
31define! {
32    #[doc = "A newtype containing the raw bits of a Google BFloat16 floating point number."]
33    #[doc = ""]
34    #[doc = "Values of this type are hashable and have a well-defined total order: the one given "]
35    #[doc = "by [`Self::total_cmp`].  As a consequence, `+0.0` is not equal to `-0.0`, and NaN "]
36    #[doc = "compares equal to NaN if both NaN values have exactly the same bit pattern."]
37    pub struct BF16;
38    size 16 bits;
39    exp 8 bits;
40    repr u16 / i16;
41}
42
43define! {
44    #[doc = "A newtype containing the raw bits of an IEEE 754 binary16 floating point number."]
45    #[doc = ""]
46    #[doc = "Values of this type are hashable and have a well-defined total order: the one given "]
47    #[doc = "by [`Self::total_cmp`].  As a consequence, `+0.0` is not equal to `-0.0`, and NaN "]
48    #[doc = "compares equal to NaN if both NaN values have exactly the same bit pattern."]
49    #[doc = ""]
50    #[doc = "# Features"]
51    #[doc = ""]
52    #[doc = "Crate feature `f16` enables use of the Rust [`f16`] primitive type, which is a"]
53    #[doc = "nightly-only language feature that's only functional on certain architectures."]
54    pub struct F16;
55    size 16 bits;
56    exp 5 bits;
57    repr u16 / i16;
58    float f16 with feature "f16";
59}
60
61define! {
62    #[doc = "A newtype containing the raw bits of an IEEE 754 binary32 floating point number."]
63    #[doc = ""]
64    #[doc = "Values of this type are hashable and have a well-defined total order: the one given "]
65    #[doc = "by [`Self::total_cmp`].  As a consequence, `+0.0` is not equal to `-0.0`, and NaN "]
66    #[doc = "compares equal to NaN if both NaN values have exactly the same bit pattern."]
67    pub struct F32;
68    size 32 bits;
69    exp 8 bits;
70    repr u32 / i32;
71    float f32;
72}
73
74define! {
75    #[doc = "A newtype containing the raw bits of an IEEE 754 binary64 floating point number."]
76    #[doc = ""]
77    #[doc = "Values of this type are hashable and have a well-defined total order: the one given "]
78    #[doc = "by [`Self::total_cmp`].  As a consequence, `+0.0` is not equal to `-0.0`, and NaN "]
79    #[doc = "compares equal to NaN if both NaN values have exactly the same bit pattern."]
80    pub struct F64;
81    size 64 bits;
82    exp 11 bits;
83    repr u64 / i64;
84    float f64;
85}
86
87define! {
88    #[doc = "A newtype containing the raw bits of an IEEE 754 binary128 floating point number."]
89    #[doc = ""]
90    #[doc = "Values of this type are hashable and have a well-defined total order: the one given "]
91    #[doc = "by [`Self::total_cmp`].  As a consequence, `+0.0` is not equal to `-0.0`, and NaN "]
92    #[doc = "compares equal to NaN if both NaN values have exactly the same bit pattern."]
93    #[doc = ""]
94    #[doc = "# Features"]
95    #[doc = ""]
96    #[doc = "Crate feature `f128` enables use of the Rust [`f128`] primitive type, which is a"]
97    #[doc = "nightly-only language feature that's only functional on certain architectures."]
98    pub struct F128;
99    size 128 bits;
100    exp 15 bits;
101    repr u128 / i128;
102    float f128 with feature "f128";
103}
104
105impl core::fmt::Display for F64 {
106    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107        let val = self.to_float();
108        core::fmt::Display::fmt(&val, f)
109    }
110}
111
112impl core::str::FromStr for F64 {
113    type Err = core::num::ParseFloatError;
114    fn from_str(s: &str) -> Result<Self, Self::Err> {
115        Ok(Self::from_float(s.parse()?))
116    }
117}
118
119impl core::fmt::Display for F32 {
120    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121        let val = self.to_float();
122        core::fmt::Display::fmt(&val, f)
123    }
124}
125
126impl core::str::FromStr for F32 {
127    type Err = core::num::ParseFloatError;
128    fn from_str(s: &str) -> Result<Self, Self::Err> {
129        Ok(Self::from_float(s.parse()?))
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use core::num::FpCategory;
136
137    use super::*;
138    use crate::helpers::{C_INF, C_NAN, C_NORM, C_ZERO};
139
140    #[test]
141    fn f32_smoke_test() {
142        const P_ZERO: F32 = F32::ZERO;
143        const N_ZERO: F32 = F32::NEG_ZERO;
144        const P_TINY: F32 = F32::MIN_POSITIVE;
145        const N_TINY: F32 = F32::MAX_NEGATIVE;
146        const P_ONE: F32 = F32::ONE;
147        const N_ONE: F32 = F32::NEG_ONE;
148        const P_MAX: F32 = F32::MAX;
149        const N_MAX: F32 = F32::MIN;
150        const P_INF: F32 = F32::INFINITY;
151        const N_INF: F32 = F32::NEG_INFINITY;
152        const P_QNAN: F32 = F32::QNAN;
153        const P_SNAN: F32 = F32::SNAN;
154        const N_QNAN: F32 = F32::NEG_QNAN;
155        const N_SNAN: F32 = F32::NEG_SNAN;
156
157        type Row = (u32, FpCategory, bool, F32, f32);
158        const ROWS: [Row; 14] = [
159            (0x00000000, C_ZERO, false, P_ZERO, 0.0),
160            (0x80000000, C_ZERO, true, N_ZERO, -0.0),
161            (0x00800000, C_NORM, false, P_TINY, f32::MIN_POSITIVE),
162            (0x80800000, C_NORM, true, N_TINY, -f32::MIN_POSITIVE),
163            (0x3f800000, C_NORM, false, P_ONE, 1.0),
164            (0xbf800000, C_NORM, true, N_ONE, -1.0),
165            (0x7f7fffff, C_NORM, false, P_MAX, f32::MAX),
166            (0xff7fffff, C_NORM, true, N_MAX, -f32::MAX),
167            (0x7f800000, C_INF, false, P_INF, f32::INFINITY),
168            (0xff800000, C_INF, true, N_INF, f32::NEG_INFINITY),
169            (0x7f800001, C_NAN, false, P_SNAN, f32::NAN),
170            (0x7fc00001, C_NAN, false, P_QNAN, f32::NAN),
171            (0xff800001, C_NAN, true, N_SNAN, f32::NAN),
172            (0xffc00001, C_NAN, true, N_QNAN, f32::NAN),
173        ];
174        for (bits, class, neg, val, float) in ROWS {
175            assert_eq!(neg, val.is_sign_negative());
176            assert_eq!(class, val.classify());
177            assert_eq!(float.is_nan(), val.is_nan());
178            if !val.is_nan() {
179                assert_eq!(float, val.to_float());
180            }
181            assert_eq!(bits, val.to_bits());
182        }
183    }
184
185    #[test]
186    fn f64_smoke_test() {
187        const P_ZERO: F64 = F64::ZERO;
188        const N_ZERO: F64 = F64::NEG_ZERO;
189        const P_TINY: F64 = F64::MIN_POSITIVE;
190        const N_TINY: F64 = F64::MAX_NEGATIVE;
191        const P_ONE: F64 = F64::ONE;
192        const N_ONE: F64 = F64::NEG_ONE;
193        const P_MAX: F64 = F64::MAX;
194        const N_MAX: F64 = F64::MIN;
195        const P_INF: F64 = F64::INFINITY;
196        const N_INF: F64 = F64::NEG_INFINITY;
197        const P_QNAN: F64 = F64::QNAN;
198        const P_SNAN: F64 = F64::SNAN;
199        const N_QNAN: F64 = F64::NEG_QNAN;
200        const N_SNAN: F64 = F64::NEG_SNAN;
201
202        type Row = (u64, FpCategory, bool, F64, f64);
203        const ROWS: [Row; 14] = [
204            (0x0000000000000000, C_ZERO, false, P_ZERO, 0.0),
205            (0x8000000000000000, C_ZERO, true, N_ZERO, -0.0),
206            (0x0010000000000000, C_NORM, false, P_TINY, f64::MIN_POSITIVE),
207            (0x8010000000000000, C_NORM, true, N_TINY, -f64::MIN_POSITIVE),
208            (0x3ff0000000000000, C_NORM, false, P_ONE, 1.0),
209            (0xbff0000000000000, C_NORM, true, N_ONE, -1.0),
210            (0x7fefffffffffffff, C_NORM, false, P_MAX, f64::MAX),
211            (0xffefffffffffffff, C_NORM, true, N_MAX, -f64::MAX),
212            (0x7ff0000000000000, C_INF, false, P_INF, f64::INFINITY),
213            (0xfff0000000000000, C_INF, true, N_INF, f64::NEG_INFINITY),
214            (0x7ff0000000000001, C_NAN, false, P_SNAN, f64::NAN),
215            (0x7ff8000000000001, C_NAN, false, P_QNAN, f64::NAN),
216            (0xfff0000000000001, C_NAN, true, N_SNAN, f64::NAN),
217            (0xfff8000000000001, C_NAN, true, N_QNAN, f64::NAN),
218        ];
219        for (bits, class, neg, val, float) in ROWS {
220            assert_eq!(neg, val.is_sign_negative());
221            assert_eq!(class, val.classify());
222            assert_eq!(float.is_nan(), val.is_nan());
223            if !val.is_nan() {
224                assert_eq!(float, val.to_float());
225            }
226            assert_eq!(bits, val.to_bits());
227        }
228    }
229}