2
2
3
3
#![ allow( dead_code) ] // FIXME: remove once this gets used
4
4
5
- use super :: { f32_from_bits, f64_from_bits} ;
5
+ use core:: fmt;
6
+
7
+ use super :: { Float , f32_from_bits, f64_from_bits} ;
6
8
7
9
/// Construct a 16-bit float from hex float representation (C-style)
8
10
#[ cfg( f16_enabled) ]
@@ -26,17 +28,25 @@ pub const fn hf128(s: &str) -> f128 {
26
28
f128:: from_bits ( parse_any ( s, 128 , 112 ) )
27
29
}
28
30
29
- const fn parse_any ( s : & str , bits : u32 , sig_bits : u32 ) -> u128 {
31
+ /// Parse any float from hex to its bitwise representation.
32
+ ///
33
+ /// `nan_repr` is passed rather than constructed so the platform-specific NaN is returned.
34
+ pub const fn parse_any ( s : & str , bits : u32 , sig_bits : u32 ) -> u128 {
30
35
let exp_bits: u32 = bits - sig_bits - 1 ;
31
36
let max_msb: i32 = ( 1 << ( exp_bits - 1 ) ) - 1 ;
32
37
// The exponent of one ULP in the subnormals
33
38
let min_lsb: i32 = 1 - max_msb - sig_bits as i32 ;
34
39
35
- let ( neg , mut sig , exp ) = parse_hex ( s . as_bytes ( ) ) ;
40
+ let exp_mask = ( ( 1 << exp_bits ) - 1 ) << sig_bits ;
36
41
37
- if sig == 0 {
38
- return ( neg as u128 ) << ( bits - 1 ) ;
39
- }
42
+ let ( neg, mut sig, exp) = match parse_hex ( s. as_bytes ( ) ) {
43
+ Parsed :: Finite { neg, sig : 0 , .. } => return ( neg as u128 ) << ( bits - 1 ) ,
44
+ Parsed :: Finite { neg, sig, exp } => ( neg, sig, exp) ,
45
+ Parsed :: Infinite { neg } => return ( ( neg as u128 ) << ( bits - 1 ) ) | exp_mask,
46
+ Parsed :: Nan { neg } => {
47
+ return ( ( neg as u128 ) << ( bits - 1 ) ) | exp_mask | ( 1 << ( sig_bits - 1 ) ) ;
48
+ }
49
+ } ;
40
50
41
51
// exponents of the least and most significant bits in the value
42
52
let lsb = sig. trailing_zeros ( ) as i32 ;
@@ -76,11 +86,24 @@ const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
76
86
sig | ( ( neg as u128 ) << ( bits - 1 ) )
77
87
}
78
88
89
+ /// A parsed floating point number.
90
+ enum Parsed {
91
+ /// Absolute value sig * 2^e
92
+ Finite {
93
+ neg : bool ,
94
+ sig : u128 ,
95
+ exp : i32 ,
96
+ } ,
97
+ Infinite {
98
+ neg : bool ,
99
+ } ,
100
+ Nan {
101
+ neg : bool ,
102
+ } ,
103
+ }
104
+
79
105
/// Parse a hexadecimal float x
80
- /// returns (s,n,e):
81
- /// s == x.is_sign_negative()
82
- /// n * 2^e == x.abs()
83
- const fn parse_hex ( mut b : & [ u8 ] ) -> ( bool , u128 , i32 ) {
106
+ const fn parse_hex ( mut b : & [ u8 ] ) -> Parsed {
84
107
let mut neg = false ;
85
108
let mut sig: u128 = 0 ;
86
109
let mut exp: i32 = 0 ;
@@ -90,6 +113,12 @@ const fn parse_hex(mut b: &[u8]) -> (bool, u128, i32) {
90
113
neg = c == b'-' ;
91
114
}
92
115
116
+ match * b {
117
+ [ b'i' | b'I' , b'n' | b'N' , b'f' | b'F' ] => return Parsed :: Infinite { neg } ,
118
+ [ b'n' | b'N' , b'a' | b'A' , b'n' | b'N' ] => return Parsed :: Nan { neg } ,
119
+ _ => ( ) ,
120
+ }
121
+
93
122
if let & [ b'0' , b'x' | b'X' , ref rest @ ..] = b {
94
123
b = rest;
95
124
} else {
@@ -152,7 +181,7 @@ const fn parse_hex(mut b: &[u8]) -> (bool, u128, i32) {
152
181
exp += pexp;
153
182
}
154
183
155
- ( neg, sig, exp)
184
+ Parsed :: Finite { neg, sig, exp }
156
185
}
157
186
158
187
const fn dec_digit ( c : u8 ) -> u8 {
@@ -179,8 +208,107 @@ const fn u128_ilog2(v: u128) -> u32 {
179
208
u128:: BITS - 1 - v. leading_zeros ( )
180
209
}
181
210
211
+ /// Format a floating point number as its IEEE hex (`%a`) representation.
212
+ pub struct Hexf < F > ( pub F ) ;
213
+
214
+ // Adapted from https://github.com/ericseppanen/hexfloat2/blob/a5c27932f0ff/src/format.rs
215
+ fn fmt_any_hex < F : Float > ( x : & F , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
216
+ if x. is_sign_negative ( ) {
217
+ write ! ( f, "-" ) ?;
218
+ }
219
+
220
+ if x. is_nan ( ) {
221
+ return write ! ( f, "NaN" ) ;
222
+ } else if x. is_infinite ( ) {
223
+ return write ! ( f, "inf" ) ;
224
+ } else if * x == F :: ZERO {
225
+ return write ! ( f, "0x0p+0" ) ;
226
+ }
227
+
228
+ let mut exponent = x. exp_unbiased ( ) ;
229
+ let sig = x. to_bits ( ) & F :: SIG_MASK ;
230
+
231
+ let bias = F :: EXP_BIAS as i32 ;
232
+ // The mantissa MSB needs to be shifted up to the nearest nibble.
233
+ let mshift = ( 4 - ( F :: SIG_BITS % 4 ) ) % 4 ;
234
+ let sig = sig << mshift;
235
+ // The width is rounded up to the nearest char (4 bits)
236
+ let mwidth = ( F :: SIG_BITS as usize + 3 ) / 4 ;
237
+ let leading = if exponent == -bias {
238
+ // subnormal number means we shift our output by 1 bit.
239
+ exponent += 1 ;
240
+ "0."
241
+ } else {
242
+ "1."
243
+ } ;
244
+
245
+ write ! ( f, "0x{leading}{sig:0mwidth$x}p{exponent:+}" )
246
+ }
247
+
248
+ #[ cfg( f16_enabled) ]
249
+ impl fmt:: LowerHex for Hexf < f16 > {
250
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
251
+ fmt_any_hex ( & self . 0 , f)
252
+ }
253
+ }
254
+
255
+ impl fmt:: LowerHex for Hexf < f32 > {
256
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
257
+ fmt_any_hex ( & self . 0 , f)
258
+ }
259
+ }
260
+
261
+ impl fmt:: LowerHex for Hexf < f64 > {
262
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
263
+ fmt_any_hex ( & self . 0 , f)
264
+ }
265
+ }
266
+
267
+ #[ cfg( f128_enabled) ]
268
+ impl fmt:: LowerHex for Hexf < f128 > {
269
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
270
+ fmt_any_hex ( & self . 0 , f)
271
+ }
272
+ }
273
+
274
+ impl fmt:: LowerHex for Hexf < i32 > {
275
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
276
+ fmt:: LowerHex :: fmt ( & self . 0 , f)
277
+ }
278
+ }
279
+
280
+ impl < T1 , T2 > fmt:: LowerHex for Hexf < ( T1 , T2 ) >
281
+ where
282
+ T1 : Copy ,
283
+ T2 : Copy ,
284
+ Hexf < T1 > : fmt:: LowerHex ,
285
+ Hexf < T2 > : fmt:: LowerHex ,
286
+ {
287
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
288
+ write ! ( f, "({:x}, {:x})" , Hexf ( self . 0.0 ) , Hexf ( self . 0.1 ) )
289
+ }
290
+ }
291
+
292
+ impl < T > fmt:: Debug for Hexf < T >
293
+ where
294
+ Hexf < T > : fmt:: LowerHex ,
295
+ {
296
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
297
+ fmt:: LowerHex :: fmt ( self , f)
298
+ }
299
+ }
300
+
301
+ impl < T > fmt:: Display for Hexf < T >
302
+ where
303
+ Hexf < T > : fmt:: LowerHex ,
304
+ {
305
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
306
+ fmt:: LowerHex :: fmt ( self , f)
307
+ }
308
+ }
309
+
182
310
#[ cfg( test) ]
183
- mod tests {
311
+ mod parse_tests {
184
312
extern crate std;
185
313
use std:: { format, println} ;
186
314
@@ -272,6 +400,10 @@ mod tests {
272
400
( "-0x1.998p-4" , ( -0.1f16 ) . to_bits( ) ) ,
273
401
( "0x0.123p-12" , 0x0123 ) ,
274
402
( "0x1p-24" , 0x0001 ) ,
403
+ ( "nan" , f16:: NAN . to_bits( ) ) ,
404
+ ( "-nan" , ( -f16:: NAN ) . to_bits( ) ) ,
405
+ ( "inf" , f16:: INFINITY . to_bits( ) ) ,
406
+ ( "-inf" , f16:: NEG_INFINITY . to_bits( ) ) ,
275
407
] ;
276
408
for ( s, exp) in checks {
277
409
println!( "parsing {s}" ) ;
@@ -322,6 +454,10 @@ mod tests {
322
454
( "0x1.111114p-127" , 0x00444445 ) ,
323
455
( "0x1.23456p-130" , 0x00091a2b ) ,
324
456
( "0x1p-149" , 0x00000001 ) ,
457
+ ( "nan" , f32:: NAN . to_bits ( ) ) ,
458
+ ( "-nan" , ( -f32:: NAN ) . to_bits ( ) ) ,
459
+ ( "inf" , f32:: INFINITY . to_bits ( ) ) ,
460
+ ( "-inf" , f32:: NEG_INFINITY . to_bits ( ) ) ,
325
461
] ;
326
462
for ( s, exp) in checks {
327
463
println ! ( "parsing {s}" ) ;
@@ -360,6 +496,10 @@ mod tests {
360
496
( "0x0.8000000000001p-1022" , 0x0008000000000001 ) ,
361
497
( "0x0.123456789abcdp-1022" , 0x000123456789abcd ) ,
362
498
( "0x0.0000000000002p-1022" , 0x0000000000000002 ) ,
499
+ ( "nan" , f64:: NAN . to_bits ( ) ) ,
500
+ ( "-nan" , ( -f64:: NAN ) . to_bits ( ) ) ,
501
+ ( "inf" , f64:: INFINITY . to_bits ( ) ) ,
502
+ ( "-inf" , f64:: NEG_INFINITY . to_bits ( ) ) ,
363
503
] ;
364
504
for ( s, exp) in checks {
365
505
println ! ( "parsing {s}" ) ;
@@ -401,6 +541,10 @@ mod tests {
401
541
( "-0x1.999999999999999999999999999ap-4" , ( -0.1f128 ) . to_bits( ) ) ,
402
542
( "0x0.abcdef0123456789abcdef012345p-16382" , 0x0000abcdef0123456789abcdef012345 ) ,
403
543
( "0x1p-16494" , 0x00000000000000000000000000000001 ) ,
544
+ ( "nan" , f128:: NAN . to_bits( ) ) ,
545
+ ( "-nan" , ( -f128:: NAN ) . to_bits( ) ) ,
546
+ ( "inf" , f128:: INFINITY . to_bits( ) ) ,
547
+ ( "-inf" , f128:: NEG_INFINITY . to_bits( ) ) ,
404
548
] ;
405
549
for ( s, exp) in checks {
406
550
println!( "parsing {s}" ) ;
@@ -623,3 +767,79 @@ mod tests_panicking {
623
767
#[ cfg( f128_enabled) ]
624
768
f128_tests ! ( ) ;
625
769
}
770
+
771
+ #[ cfg( test) ]
772
+ mod print_tests {
773
+ extern crate std;
774
+ use std:: string:: ToString ;
775
+
776
+ use super :: * ;
777
+
778
+ #[ test]
779
+ #[ cfg( f16_enabled) ]
780
+ fn test_f16 ( ) {
781
+ use std:: format;
782
+ // Exhaustively check that `f16` roundtrips.
783
+ for x in 0 ..=u16:: MAX {
784
+ let f = f16:: from_bits ( x) ;
785
+ let s = format ! ( "{}" , Hexf ( f) ) ;
786
+ let from_s = hf16 ( & s) ;
787
+
788
+ if f. is_nan ( ) && from_s. is_nan ( ) {
789
+ continue ;
790
+ }
791
+
792
+ assert_eq ! (
793
+ f. to_bits( ) ,
794
+ from_s. to_bits( ) ,
795
+ "{f:?} formatted as {s} but parsed as {from_s:?}"
796
+ ) ;
797
+ }
798
+ }
799
+
800
+ #[ test]
801
+ fn spot_checks ( ) {
802
+ assert_eq ! ( Hexf ( f32 :: MAX ) . to_string( ) , "0x1.fffffep+127" ) ;
803
+ assert_eq ! ( Hexf ( f64 :: MAX ) . to_string( ) , "0x1.fffffffffffffp+1023" ) ;
804
+
805
+ assert_eq ! ( Hexf ( f32 :: MIN ) . to_string( ) , "-0x1.fffffep+127" ) ;
806
+ assert_eq ! ( Hexf ( f64 :: MIN ) . to_string( ) , "-0x1.fffffffffffffp+1023" ) ;
807
+
808
+ assert_eq ! ( Hexf ( f32 :: ZERO ) . to_string( ) , "0x0p+0" ) ;
809
+ assert_eq ! ( Hexf ( f64 :: ZERO ) . to_string( ) , "0x0p+0" ) ;
810
+
811
+ assert_eq ! ( Hexf ( f32 :: NEG_ZERO ) . to_string( ) , "-0x0p+0" ) ;
812
+ assert_eq ! ( Hexf ( f64 :: NEG_ZERO ) . to_string( ) , "-0x0p+0" ) ;
813
+
814
+ assert_eq ! ( Hexf ( f32 :: NAN ) . to_string( ) , "NaN" ) ;
815
+ assert_eq ! ( Hexf ( f64 :: NAN ) . to_string( ) , "NaN" ) ;
816
+
817
+ assert_eq ! ( Hexf ( f32 :: INFINITY ) . to_string( ) , "inf" ) ;
818
+ assert_eq ! ( Hexf ( f64 :: INFINITY ) . to_string( ) , "inf" ) ;
819
+
820
+ assert_eq ! ( Hexf ( f32 :: NEG_INFINITY ) . to_string( ) , "-inf" ) ;
821
+ assert_eq ! ( Hexf ( f64 :: NEG_INFINITY ) . to_string( ) , "-inf" ) ;
822
+
823
+ #[ cfg( f16_enabled) ]
824
+ {
825
+ assert_eq ! ( Hexf ( f16:: MAX ) . to_string( ) , "0x1.ffcp+15" ) ;
826
+ assert_eq ! ( Hexf ( f16:: MIN ) . to_string( ) , "-0x1.ffcp+15" ) ;
827
+ assert_eq ! ( Hexf ( f16:: ZERO ) . to_string( ) , "0x0p+0" ) ;
828
+ assert_eq ! ( Hexf ( f16:: NEG_ZERO ) . to_string( ) , "-0x0p+0" ) ;
829
+ assert_eq ! ( Hexf ( f16:: NAN ) . to_string( ) , "NaN" ) ;
830
+ assert_eq ! ( Hexf ( f16:: INFINITY ) . to_string( ) , "inf" ) ;
831
+ assert_eq ! ( Hexf ( f16:: NEG_INFINITY ) . to_string( ) , "-inf" ) ;
832
+ }
833
+
834
+ #[ cfg( f128_enabled) ]
835
+ {
836
+ assert_eq ! ( Hexf ( f128:: MAX ) . to_string( ) , "0x1.ffffffffffffffffffffffffffffp+16383" ) ;
837
+ assert_eq ! ( Hexf ( f128:: MIN ) . to_string( ) , "-0x1.ffffffffffffffffffffffffffffp+16383" ) ;
838
+ assert_eq ! ( Hexf ( f128:: ZERO ) . to_string( ) , "0x0p+0" ) ;
839
+ assert_eq ! ( Hexf ( f128:: NEG_ZERO ) . to_string( ) , "-0x0p+0" ) ;
840
+ assert_eq ! ( Hexf ( f128:: NAN ) . to_string( ) , "NaN" ) ;
841
+ assert_eq ! ( Hexf ( f128:: INFINITY ) . to_string( ) , "inf" ) ;
842
+ assert_eq ! ( Hexf ( f128:: NEG_INFINITY ) . to_string( ) , "-inf" ) ;
843
+ }
844
+ }
845
+ }
0 commit comments