Skip to main content

unsigned_float/
lib.rs

1//! Unsigned floating-point formats for values that can never be negative.
2//!
3//! This crate provides compact unsigned float newtypes with IEEE-like exponent
4//! and mantissa fields, but no sign bit. The missing sign bit can be spent on
5//! precision or range, removes negative zero, and makes total ordering a raw
6//! unsigned integer comparison.
7//!
8//! The ergonomic aliases [`Uf8`], [`Uf16`], and [`Uf32`] point at the default
9//! concrete layouts [`Uf8E4M4`], [`Uf16E5M11`], and [`Uf32E8M24`]. Alternate
10//! layouts such as [`Uf8E5M3`] and [`Uf16E6M10`] are exported as distinct types
11//! so their range and precision tradeoffs stay explicit.
12//! With the `f128` feature enabled, `Uf64` is also available and promotes
13//! through nightly primitive `f128`.
14//!
15//! # Conversions
16//!
17//! Explicit constructors such as [`Uf8::from_f32`] encode the input into the
18//! target format. Negative native values become NaN, and overflow becomes
19//! infinity.
20//!
21//! Use [`TryFrom`] when invalid or unrepresentable inputs should be rejected:
22//!
23//! ```
24//! use unsigned_float::{ConversionError, Uf16};
25//!
26//! assert_eq!(Uf16::try_from(42_u32), Ok(Uf16::from_f32(42.0)));
27//! assert_eq!(Uf16::try_from(-1_i32), Err(ConversionError::Negative));
28//! ```
29//!
30//! # Exponents
31//!
32//! Use [`PowUf`] to raise native floats to unsigned-float exponents:
33//!
34//! ```
35//! use unsigned_float::{PowUf, Uf16};
36//!
37//! let root = 9.0_f32.powuf(Uf16::from_f32(0.5));
38//! assert_eq!(root, 3.0);
39//! ```
40//!
41//! `PowUf` uses exact kernels for common exponent shapes such as zero, one,
42//! one-half, and small integers, then falls back to `libm` for the general
43//! fractional case. Same-layout and cross-layout UF8 exponentiation uses
44//! generated lookup tables and returns the UF8 layout of the base.
45//!
46//! [`Pow1mUf`] evaluates `(1 - u)^a` directly. This keeps the complement
47//! operation explicit and lets UF8 use generated lookup tables without
48//! materializing `1 - u` as a separately rounded value.
49//!
50#![no_std]
51#![cfg_attr(feature = "f16", feature(f16))]
52#![cfg_attr(feature = "f128", feature(f128))]
53#![cfg_attr(feature = "simd", feature(portable_simd))]
54
55#[cfg(test)]
56extern crate std;
57
58mod convert;
59mod dispatch;
60mod pow;
61#[cfg(feature = "simd")]
62pub mod simd;
63mod uf16;
64mod uf32;
65#[cfg(feature = "f128")]
66mod uf64;
67mod uf8;
68
69pub use convert::ConversionError;
70pub use pow::{Pow1mUf, PowUf};
71pub use uf8::{Uf8, Uf8E4M4, Uf8E5M3};
72pub use uf16::{Uf16, Uf16E5M11, Uf16E6M10};
73pub use uf32::{Uf32, Uf32E8M24};
74#[cfg(feature = "f128")]
75pub use uf64::{Uf64, Uf64E11M52};
76
77#[cfg(test)]
78mod tests {
79    #[cfg(feature = "f128")]
80    use super::Uf64;
81    use super::{ConversionError, Pow1mUf, PowUf, Uf8, Uf8E5M3, Uf16, Uf16E6M10, Uf32};
82
83    #[test]
84    fn canonical_one_bits_match_the_layouts() {
85        assert_eq!(Uf8::ONE.to_bits(), 0x70);
86        assert_eq!(Uf8E5M3::ONE.to_bits(), 0x78);
87        assert_eq!(Uf16::ONE.to_bits(), 0x7800);
88        assert_eq!(Uf16E6M10::ONE.to_bits(), 0x7c00);
89        assert_eq!(Uf32::ONE.to_bits(), 0x7f00_0000);
90        #[cfg(feature = "f128")]
91        assert_eq!(Uf64::ONE.to_bits(), 0x3ff0_0000_0000_0000);
92    }
93
94    #[test]
95    fn uf8_finite_values_round_trip_through_f32() {
96        for bits in u8::MIN..=u8::MAX {
97            let value = Uf8::from_bits(bits);
98
99            if value.is_nan() {
100                continue;
101            }
102
103            assert_eq!(Uf8::from_f32(value.to_f32()).to_bits(), bits);
104        }
105    }
106
107    #[test]
108    fn uf8_e5m3_finite_values_round_trip_through_f32() {
109        for bits in u8::MIN..=u8::MAX {
110            let value = Uf8E5M3::from_bits(bits);
111
112            if value.is_nan() {
113                continue;
114            }
115
116            assert_eq!(Uf8E5M3::from_f32(value.to_f32()).to_bits(), bits);
117        }
118    }
119
120    #[test]
121    fn conversions_handle_special_values() {
122        assert!(Uf8::from_f32(f32::NAN).is_nan());
123        assert!(Uf8E5M3::from_f32(f32::NAN).is_nan());
124        assert!(Uf16::from_f32(f32::NEG_INFINITY).is_nan());
125        assert!(Uf16E6M10::from_f32(f32::NEG_INFINITY).is_nan());
126        assert!(Uf32::from_f64(-1.0).is_nan());
127        #[cfg(feature = "f128")]
128        assert!(Uf64::from_f64(-1.0).is_nan());
129
130        assert!(Uf8::from_f32(f32::INFINITY).is_infinite());
131        assert!(Uf8E5M3::from_f32(f32::INFINITY).is_infinite());
132        assert!(Uf16::from_f32(f32::INFINITY).is_infinite());
133        assert!(Uf16E6M10::from_f32(f32::INFINITY).is_infinite());
134        assert!(Uf32::from_f64(f64::INFINITY).is_infinite());
135        #[cfg(feature = "f128")]
136        assert!(Uf64::from_f64(f64::INFINITY).is_infinite());
137    }
138
139    #[test]
140    fn try_from_f64_rejects_invalid_or_unrepresentable_values() {
141        assert_eq!(Uf8::try_from(-1.0_f64), Err(ConversionError::Negative));
142        assert_eq!(Uf16::try_from(f64::NAN), Err(ConversionError::Nan));
143        assert_eq!(
144            Uf32::try_from(f64::INFINITY),
145            Err(ConversionError::Infinite)
146        );
147
148        assert_eq!(Uf8::try_from(1.0e20_f64), Err(ConversionError::Overflow));
149        assert_eq!(Uf16::try_from(1.0e20_f64), Err(ConversionError::Overflow));
150        assert_eq!(Uf8::try_from(1.0e-20_f64), Err(ConversionError::Underflow));
151
152        assert_eq!(Uf8::try_from(2.0_f64), Ok(Uf8::from_f32(2.0)));
153        assert_eq!(Uf8E5M3::try_from(2.0_f64), Ok(Uf8E5M3::from_f32(2.0)));
154        assert_eq!(Uf16::try_from(2.0_f64), Ok(Uf16::from_f32(2.0)));
155        assert_eq!(Uf16E6M10::try_from(2.0_f64), Ok(Uf16E6M10::from_f32(2.0)));
156        assert_eq!(Uf32::try_from(2.0_f64), Ok(Uf32::from_f64(2.0)));
157        #[cfg(feature = "f128")]
158        assert_eq!(Uf64::try_from_f64(2.0_f64), Ok(Uf64::from_f64(2.0)));
159    }
160
161    #[test]
162    fn try_from_integer_types() {
163        assert_eq!(Uf8::try_from(2_u8), Ok(Uf8::from_f32(2.0)));
164        assert_eq!(Uf8E5M3::try_from(2_u8), Ok(Uf8E5M3::from_f32(2.0)));
165        assert_eq!(Uf16::try_from(1024_u32), Ok(Uf16::from_f32(1024.0)));
166        assert_eq!(
167            Uf16E6M10::try_from(1024_u32),
168            Ok(Uf16E6M10::from_f32(1024.0))
169        );
170        assert_eq!(Uf32::try_from(1024_u64), Ok(Uf32::from_f64(1024.0)));
171        #[cfg(feature = "f128")]
172        assert_eq!(Uf64::try_from(1024_u64), Ok(Uf64::from_f64(1024.0)));
173
174        assert_eq!(Uf8::try_from(-1_i8), Err(ConversionError::Negative));
175        assert_eq!(Uf8::try_from(u128::MAX), Err(ConversionError::Overflow));
176    }
177
178    #[cfg(feature = "f16")]
179    #[test]
180    fn f16_conversions_are_available_when_enabled() {
181        let native = 2.0_f16;
182
183        assert_eq!(Uf8::from_f16(native).to_f16(), native);
184        assert_eq!(Uf8E5M3::from_f16(native).to_f16(), native);
185        assert_eq!(Uf16::from_f16(native).to_f16(), native);
186        assert_eq!(Uf16E6M10::from_f16(native).to_f16(), native);
187        assert_eq!(Uf32::from_f16(native).to_f16(), native);
188        #[cfg(feature = "f128")]
189        assert_eq!(Uf64::from_f16(native).to_f16(), native);
190
191        assert_eq!(Uf8::from(native), Uf8::from_f16(native));
192        assert_eq!(Uf8E5M3::from(native), Uf8E5M3::from_f16(native));
193        assert_eq!(Uf16::from(native), Uf16::from_f16(native));
194        assert_eq!(Uf16E6M10::from(native), Uf16E6M10::from_f16(native));
195        assert_eq!(Uf32::from(native), Uf32::from_f16(native));
196        #[cfg(feature = "f128")]
197        assert_eq!(Uf64::from(native), Uf64::from_f16(native));
198
199        let _: f16 = Uf8::from_f16(native).into();
200        let _: f16 = Uf8E5M3::from_f16(native).into();
201        let _: f16 = Uf16::from_f16(native).into();
202        let _: f16 = Uf16E6M10::from_f16(native).into();
203        let _: f16 = Uf32::from_f16(native).into();
204        #[cfg(feature = "f128")]
205        let _: f16 = Uf64::from_f16(native).into();
206    }
207
208    #[test]
209    fn subnormal_values_decode_correctly() {
210        assert_eq!(Uf8::MIN_POSITIVE.to_f32(), 2.0_f32.powi(-10));
211        assert_eq!(Uf8E5M3::MIN_POSITIVE.to_f32(), 2.0_f32.powi(-17));
212        assert_eq!(Uf16::MIN_POSITIVE.to_f32(), 2.0_f32.powi(-25));
213        assert_eq!(Uf16E6M10::MIN_POSITIVE.to_f32(), 2.0_f32.powi(-40));
214        assert_eq!(Uf32::MIN_POSITIVE.to_f64(), 2.0_f64.powi(-150));
215        #[cfg(feature = "f128")]
216        assert_eq!(
217            Uf64::MIN_POSITIVE.to_f64(),
218            f64::MIN_POSITIVE / 2.0_f64.powi(52)
219        );
220    }
221
222    #[test]
223    fn arithmetic_promotes_computes_and_demotes() {
224        assert_eq!((Uf8::from_f32(1.0) + Uf8::from_f32(1.0)).to_f32(), 2.0);
225        assert_eq!(
226            (Uf8E5M3::from_f32(1.0) + Uf8E5M3::from_f32(1.0)).to_f32(),
227            2.0
228        );
229        assert_eq!((Uf16::from_f32(3.0) * Uf16::from_f32(0.5)).to_f32(), 1.5);
230        assert_eq!(
231            (Uf16E6M10::from_f32(3.0) * Uf16E6M10::from_f32(0.5)).to_f32(),
232            1.5
233        );
234        assert_eq!((Uf32::from_f64(9.0) / Uf32::from_f64(3.0)).to_f64(), 3.0);
235        #[cfg(feature = "f128")]
236        assert_eq!((Uf64::from_f64(9.0) / Uf64::from_f64(3.0)).to_f64(), 3.0);
237    }
238
239    #[test]
240    fn native_float_bases_can_use_unsigned_float_exponents() {
241        assert_eq!(9.0_f32.powuf(Uf8::from_f32(0.5)), 3.0);
242        assert_eq!(9.0_f32.powuf(Uf8E5M3::from_f32(0.5)), 3.0);
243        assert_eq!(9.0_f32.powuf(Uf16::from_f32(0.5)), 3.0);
244        assert_eq!(9.0_f64.powuf(Uf16E6M10::from_f32(0.5)), 3.0);
245        assert_eq!(9.0_f64.powuf(Uf32::from_f64(0.5)), 3.0);
246        assert_eq!(2.0_f32.powuf(Uf16::from_f32(8.0)), 256.0);
247        assert_eq!((-2.0_f32).powuf(Uf8::from_f32(3.0)), -8.0);
248        assert_eq!(f32::NAN.powuf(Uf8::ZERO), 1.0);
249        assert!((16.0_f64.powuf(Uf32::from_f64(1.25)) - 32.0).abs() < 1.0e-12);
250
251        #[cfg(feature = "f128")]
252        {
253            assert_eq!(9.0_f64.powuf(Uf64::from_f64(0.5)), 3.0);
254            assert_eq!(2.0_f64.powuf(Uf64::from_f64(10.0)), 1024.0);
255        }
256    }
257
258    #[test]
259    fn native_float_bases_can_use_complement_exponents() {
260        assert_eq!(0.75_f32.pow1muf(Uf8::from_f32(0.5)), 0.5);
261        assert_eq!(0.75_f32.pow1muf(Uf16::from_f32(0.5)), 0.5);
262        assert_eq!(0.5_f64.pow1muf(Uf32::from_f64(2.0)), 0.25);
263        assert_eq!(f32::NAN.pow1muf(Uf8::ZERO), 1.0);
264        assert!(
265            (0.25_f64.pow1muf(Uf16E6M10::from_f32(1.25)) - 0.697_953_644_326_574_7).abs() < 1.0e-15
266        );
267
268        #[cfg(feature = "f128")]
269        {
270            assert_eq!(0.5_f64.pow1muf(Uf64::from_f64(2.0)), 0.25);
271        }
272    }
273
274    #[test]
275    fn uf8_pow_lut_matches_promoted_arithmetic() {
276        assert_eq!(Uf8::from_f32(9.0).powuf(Uf8::from_f32(0.5)).to_f32(), 3.0);
277
278        for a_bits in u8::MIN..=u8::MAX {
279            for b_bits in u8::MIN..=u8::MAX {
280                let a = Uf8::from_bits(a_bits);
281                let b = Uf8::from_bits(b_bits);
282
283                assert_eq!(
284                    a.powuf(b).to_bits(),
285                    Uf8::from_f32(a.to_f32().powuf(b)).to_bits()
286                );
287            }
288        }
289    }
290
291    #[test]
292    fn uf8_e5m3_pow_lut_matches_promoted_arithmetic() {
293        assert_eq!(
294            Uf8E5M3::from_f32(9.0)
295                .powuf(Uf8E5M3::from_f32(0.5))
296                .to_f32(),
297            3.0
298        );
299
300        for a_bits in u8::MIN..=u8::MAX {
301            for b_bits in u8::MIN..=u8::MAX {
302                let a = Uf8E5M3::from_bits(a_bits);
303                let b = Uf8E5M3::from_bits(b_bits);
304
305                assert_eq!(
306                    a.powuf(b).to_bits(),
307                    Uf8E5M3::from_f32(a.to_f32().powuf(b)).to_bits()
308                );
309            }
310        }
311    }
312
313    #[test]
314    fn uf8_cross_layout_pow_luts_match_promoted_arithmetic() {
315        assert_eq!(
316            Uf8::from_f32(9.0).powuf(Uf8E5M3::from_f32(0.5)).to_f32(),
317            3.0
318        );
319        assert_eq!(
320            Uf8E5M3::from_f32(9.0).powuf(Uf8::from_f32(0.5)).to_f32(),
321            3.0
322        );
323
324        for a_bits in u8::MIN..=u8::MAX {
325            for b_bits in u8::MIN..=u8::MAX {
326                let e4m4 = Uf8::from_bits(a_bits);
327                let e5m3 = Uf8E5M3::from_bits(b_bits);
328
329                assert_eq!(
330                    e4m4.powuf(e5m3).to_bits(),
331                    Uf8::from_f32(e4m4.to_f32().powuf(e5m3)).to_bits()
332                );
333                assert_eq!(
334                    e5m3.powuf(e4m4).to_bits(),
335                    Uf8E5M3::from_f32(e5m3.to_f32().powuf(e4m4)).to_bits()
336                );
337            }
338        }
339    }
340
341    #[test]
342    fn uf8_pow1m_lut_matches_promoted_arithmetic() {
343        assert_eq!(
344            Uf8::from_f32(0.75).pow1muf(Uf8::from_f32(0.5)).to_f32(),
345            0.5
346        );
347
348        for a_bits in u8::MIN..=u8::MAX {
349            for b_bits in u8::MIN..=u8::MAX {
350                let u = Uf8::from_bits(a_bits);
351                let exponent = Uf8::from_bits(b_bits);
352
353                assert_eq!(
354                    u.pow1muf(exponent).to_bits(),
355                    Uf8::from_f32(u.to_f32().pow1muf(exponent)).to_bits()
356                );
357            }
358        }
359    }
360
361    #[test]
362    fn uf8_e5m3_pow1m_lut_matches_promoted_arithmetic() {
363        assert_eq!(
364            Uf8E5M3::from_f32(0.75)
365                .pow1muf(Uf8E5M3::from_f32(0.5))
366                .to_f32(),
367            0.5
368        );
369
370        for a_bits in u8::MIN..=u8::MAX {
371            for b_bits in u8::MIN..=u8::MAX {
372                let u = Uf8E5M3::from_bits(a_bits);
373                let exponent = Uf8E5M3::from_bits(b_bits);
374
375                assert_eq!(
376                    u.pow1muf(exponent).to_bits(),
377                    Uf8E5M3::from_f32(u.to_f32().pow1muf(exponent)).to_bits()
378                );
379            }
380        }
381    }
382
383    #[test]
384    fn uf8_cross_layout_pow1m_luts_match_promoted_arithmetic() {
385        assert_eq!(
386            Uf8::from_f32(0.75).pow1muf(Uf8E5M3::from_f32(0.5)).to_f32(),
387            0.5
388        );
389        assert_eq!(
390            Uf8E5M3::from_f32(0.75).pow1muf(Uf8::from_f32(0.5)).to_f32(),
391            0.5
392        );
393
394        for a_bits in u8::MIN..=u8::MAX {
395            for b_bits in u8::MIN..=u8::MAX {
396                let e4m4 = Uf8::from_bits(a_bits);
397                let e5m3 = Uf8E5M3::from_bits(b_bits);
398
399                assert_eq!(
400                    e4m4.pow1muf(e5m3).to_bits(),
401                    Uf8::from_f32(e4m4.to_f32().pow1muf(e5m3)).to_bits()
402                );
403                assert_eq!(
404                    e5m3.pow1muf(e4m4).to_bits(),
405                    Uf8E5M3::from_f32(e5m3.to_f32().pow1muf(e4m4)).to_bits()
406                );
407            }
408        }
409    }
410
411    #[cfg(any(not(feature = "f16"), feature = "soft-float"))]
412    #[test]
413    fn uf8_lut_matches_promoted_arithmetic() {
414        for a_bits in u8::MIN..=u8::MAX {
415            for b_bits in u8::MIN..=u8::MAX {
416                let a = Uf8::from_bits(a_bits);
417                let b = Uf8::from_bits(b_bits);
418                let a_f32 = a.to_f32();
419                let b_f32 = b.to_f32();
420
421                assert_eq!((a + b).to_bits(), Uf8::from_f32(a_f32 + b_f32).to_bits());
422                assert_eq!((a - b).to_bits(), Uf8::from_f32(a_f32 - b_f32).to_bits());
423                assert_eq!((a * b).to_bits(), Uf8::from_f32(a_f32 * b_f32).to_bits());
424                assert_eq!((a / b).to_bits(), Uf8::from_f32(a_f32 / b_f32).to_bits());
425            }
426        }
427    }
428
429    #[cfg(any(not(feature = "f16"), feature = "soft-float"))]
430    #[test]
431    fn uf8_e5m3_lut_matches_promoted_arithmetic() {
432        for a_bits in u8::MIN..=u8::MAX {
433            for b_bits in u8::MIN..=u8::MAX {
434                let a = Uf8E5M3::from_bits(a_bits);
435                let b = Uf8E5M3::from_bits(b_bits);
436                let a_f32 = a.to_f32();
437                let b_f32 = b.to_f32();
438
439                assert_eq!(
440                    (a + b).to_bits(),
441                    Uf8E5M3::from_f32(a_f32 + b_f32).to_bits()
442                );
443                assert_eq!(
444                    (a - b).to_bits(),
445                    Uf8E5M3::from_f32(a_f32 - b_f32).to_bits()
446                );
447                assert_eq!(
448                    (a * b).to_bits(),
449                    Uf8E5M3::from_f32(a_f32 * b_f32).to_bits()
450                );
451                assert_eq!(
452                    (a / b).to_bits(),
453                    Uf8E5M3::from_f32(a_f32 / b_f32).to_bits()
454                );
455            }
456        }
457    }
458
459    #[test]
460    fn negative_subtraction_result_is_nan() {
461        assert!((Uf8::from_f32(1.0) - Uf8::from_f32(2.0)).is_nan());
462        assert!((Uf8E5M3::from_f32(1.0) - Uf8E5M3::from_f32(2.0)).is_nan());
463        assert!((Uf16::from_f32(1.0) - Uf16::from_f32(2.0)).is_nan());
464        assert!((Uf16E6M10::from_f32(1.0) - Uf16E6M10::from_f32(2.0)).is_nan());
465        assert!((Uf32::from_f64(1.0) - Uf32::from_f64(2.0)).is_nan());
466        #[cfg(feature = "f128")]
467        assert!((Uf64::from_f64(1.0) - Uf64::from_f64(2.0)).is_nan());
468    }
469
470    #[test]
471    fn raw_bits_define_total_ordering() {
472        assert!(Uf8::ZERO < Uf8::MIN_POSITIVE);
473        assert!(Uf8::MAX < Uf8::INFINITY);
474        assert!(Uf8::INFINITY < Uf8::NAN);
475
476        assert!(Uf8E5M3::ZERO < Uf8E5M3::MIN_POSITIVE);
477        assert!(Uf8E5M3::MAX < Uf8E5M3::INFINITY);
478        assert!(Uf8E5M3::INFINITY < Uf8E5M3::NAN);
479
480        assert!(Uf16::ZERO < Uf16::MIN_POSITIVE);
481        assert!(Uf16::MAX < Uf16::INFINITY);
482        assert!(Uf16::INFINITY < Uf16::NAN);
483
484        assert!(Uf16E6M10::ZERO < Uf16E6M10::MIN_POSITIVE);
485        assert!(Uf16E6M10::MAX < Uf16E6M10::INFINITY);
486        assert!(Uf16E6M10::INFINITY < Uf16E6M10::NAN);
487
488        assert!(Uf32::ZERO < Uf32::MIN_POSITIVE);
489        assert!(Uf32::MAX < Uf32::INFINITY);
490        assert!(Uf32::INFINITY < Uf32::NAN);
491
492        #[cfg(feature = "f128")]
493        {
494            assert!(Uf64::ZERO < Uf64::MIN_POSITIVE);
495            assert!(Uf64::MAX < Uf64::INFINITY);
496            assert!(Uf64::INFINITY < Uf64::NAN);
497        }
498    }
499
500    #[test]
501    fn formatting_delegates_to_promoted_float() {
502        macro_rules! assert_f32_formatting {
503            ($value:expr, $native:expr) => {
504                assert_eq!(std::format!("{}", $value), std::format!("{}", $native));
505                assert_eq!(
506                    std::format!("{:.2}", $value),
507                    std::format!("{:.2}", $native)
508                );
509                assert_eq!(
510                    std::format!("{:08.2}", $value),
511                    std::format!("{:08.2}", $native)
512                );
513                assert_eq!(std::format!("{:e}", $value), std::format!("{:e}", $native));
514                assert_eq!(std::format!("{:E}", $value), std::format!("{:E}", $native));
515            };
516        }
517
518        macro_rules! assert_f64_formatting {
519            ($value:expr, $native:expr) => {
520                assert_eq!(std::format!("{}", $value), std::format!("{}", $native));
521                assert_eq!(
522                    std::format!("{:.4}", $value),
523                    std::format!("{:.4}", $native)
524                );
525                assert_eq!(
526                    std::format!("{:010.4}", $value),
527                    std::format!("{:010.4}", $native)
528                );
529                assert_eq!(std::format!("{:e}", $value), std::format!("{:e}", $native));
530                assert_eq!(std::format!("{:E}", $value), std::format!("{:E}", $native));
531            };
532        }
533
534        let uf8 = Uf8::from_f32(1.5);
535        assert_f32_formatting!(uf8, uf8.to_f32());
536
537        let uf8_e5m3 = Uf8E5M3::from_f32(1.5);
538        assert_f32_formatting!(uf8_e5m3, uf8_e5m3.to_f32());
539
540        let uf16 = Uf16::from_f32(1.5);
541        assert_f32_formatting!(uf16, uf16.to_f32());
542
543        let uf16_e6m10 = Uf16E6M10::from_f32(1.5);
544        assert_f32_formatting!(uf16_e6m10, uf16_e6m10.to_f32());
545
546        let uf32 = Uf32::from_f64(1.5);
547        assert_f64_formatting!(uf32, uf32.to_f64());
548
549        #[cfg(feature = "f128")]
550        {
551            let uf64 = Uf64::from_f64(1.5);
552            assert_f64_formatting!(uf64, uf64.to_f64());
553        }
554    }
555
556    #[test]
557    fn round_to_nearest_even_when_encoding() {
558        assert_eq!(Uf8::from_f32(1.0 + 1.0 / 32.0).to_bits(), 0x70);
559        assert_eq!(Uf8::from_f32(1.0 + 3.0 / 32.0).to_bits(), 0x72);
560
561        assert_eq!(
562            Uf32::from_f64(1.0 + 2.0_f64.powi(-25)).to_bits(),
563            Uf32::ONE.to_bits()
564        );
565        assert_eq!(
566            Uf32::from_f64(1.0 + 3.0 * 2.0_f64.powi(-25)).to_bits(),
567            Uf32::ONE.to_bits() + 2
568        );
569    }
570}