22
33#![ allow( dead_code) ] // FIXME: remove once this gets used
44
5- use super :: { f32_from_bits, f64_from_bits} ;
5+ use core:: fmt;
6+
7+ use super :: { Float , f32_from_bits, f64_from_bits} ;
68
79/// Construct a 16-bit float from hex float representation (C-style)
810#[ cfg( f16_enabled) ]
@@ -26,17 +28,25 @@ pub const fn hf128(s: &str) -> f128 {
2628 f128:: from_bits ( parse_any ( s, 128 , 112 ) )
2729}
2830
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 {
3035 let exp_bits: u32 = bits - sig_bits - 1 ;
3136 let max_msb: i32 = ( 1 << ( exp_bits - 1 ) ) - 1 ;
3237 // The exponent of one ULP in the subnormals
3338 let min_lsb: i32 = 1 - max_msb - sig_bits as i32 ;
3439
35- let ( neg , mut sig , exp ) = parse_hex ( s . as_bytes ( ) ) ;
40+ let exp_mask = ( ( 1 << exp_bits ) - 1 ) << sig_bits ;
3641
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+ } ;
4050
4151 // exponents of the least and most significant bits in the value
4252 let lsb = sig. trailing_zeros ( ) as i32 ;
@@ -76,11 +86,24 @@ const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
7686 sig | ( ( neg as u128 ) << ( bits - 1 ) )
7787}
7888
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+
79105/// 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 {
84107 let mut neg = false ;
85108 let mut sig: u128 = 0 ;
86109 let mut exp: i32 = 0 ;
@@ -90,6 +113,12 @@ const fn parse_hex(mut b: &[u8]) -> (bool, u128, i32) {
90113 neg = c == b'-' ;
91114 }
92115
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+
93122 if let & [ b'0' , b'x' | b'X' , ref rest @ ..] = b {
94123 b = rest;
95124 } else {
@@ -152,7 +181,7 @@ const fn parse_hex(mut b: &[u8]) -> (bool, u128, i32) {
152181 exp += pexp;
153182 }
154183
155- ( neg, sig, exp)
184+ Parsed :: Finite { neg, sig, exp }
156185}
157186
158187const fn dec_digit ( c : u8 ) -> u8 {
@@ -179,8 +208,107 @@ const fn u128_ilog2(v: u128) -> u32 {
179208 u128:: BITS - 1 - v. leading_zeros ( )
180209}
181210
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+
182310#[ cfg( test) ]
183- mod tests {
311+ mod parse_tests {
184312 extern crate std;
185313 use std:: { format, println} ;
186314
@@ -272,6 +400,10 @@ mod tests {
272400 ( "-0x1.998p-4" , ( -0.1f16 ) . to_bits( ) ) ,
273401 ( "0x0.123p-12" , 0x0123 ) ,
274402 ( "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( ) ) ,
275407 ] ;
276408 for ( s, exp) in checks {
277409 println!( "parsing {s}" ) ;
@@ -322,6 +454,10 @@ mod tests {
322454 ( "0x1.111114p-127" , 0x00444445 ) ,
323455 ( "0x1.23456p-130" , 0x00091a2b ) ,
324456 ( "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 ( ) ) ,
325461 ] ;
326462 for ( s, exp) in checks {
327463 println ! ( "parsing {s}" ) ;
@@ -360,6 +496,10 @@ mod tests {
360496 ( "0x0.8000000000001p-1022" , 0x0008000000000001 ) ,
361497 ( "0x0.123456789abcdp-1022" , 0x000123456789abcd ) ,
362498 ( "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 ( ) ) ,
363503 ] ;
364504 for ( s, exp) in checks {
365505 println ! ( "parsing {s}" ) ;
@@ -401,6 +541,10 @@ mod tests {
401541 ( "-0x1.999999999999999999999999999ap-4" , ( -0.1f128 ) . to_bits( ) ) ,
402542 ( "0x0.abcdef0123456789abcdef012345p-16382" , 0x0000abcdef0123456789abcdef012345 ) ,
403543 ( "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( ) ) ,
404548 ] ;
405549 for ( s, exp) in checks {
406550 println!( "parsing {s}" ) ;
@@ -623,3 +767,79 @@ mod tests_panicking {
623767 #[ cfg( f128_enabled) ]
624768 f128_tests ! ( ) ;
625769}
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