@@ -7,126 +7,166 @@ pub trait FloatExt {
77
88impl FloatExt for Float {
99 fn display ( & self ) -> String {
10- match ( ) {
11- _ if self . is_nan ( ) => "nan" . into ( ) ,
12- _ if self . is_inf_pos ( ) => "inf" . into ( ) ,
13- _ if self . is_inf_neg ( ) => "-inf" . into ( ) ,
14- _ if self . is_zero ( ) => "0" . into ( ) ,
15- _ => {
16- let formatted = with_consts ( |consts| {
17- self . format ( Radix :: Dec , astro_float:: RoundingMode :: None , consts)
18- } )
19- . unwrap ( ) ;
20-
21- if !formatted. contains ( 'e' ) {
22- return formatted;
23- }
24-
25- let ( mantissa, exponent) = {
26- let mut parts = formatted. split ( 'e' ) ;
27- ( parts. next ( ) . unwrap ( ) , parts. next ( ) . unwrap ( ) )
28- } ;
29-
30- let exponent = exponent. parse :: < i32 > ( ) . unwrap ( ) ;
31-
32- let ( sign, mantissa) =
33- mantissa. split_at ( if mantissa. starts_with ( '-' ) { 1 } else { 0 } ) ;
34-
35- let digits = mantissa. replace ( '.' , "" ) ;
36-
37- let length =
38- mantissa. find ( '.' ) . unwrap_or ( mantissa. len ( ) ) as i32 + exponent;
39-
40- let result = match length {
41- length if length <= 0 => {
42- format ! ( "{}0.{}{}" , sign, "0" . repeat( ( -length) as usize ) , digits)
43- }
44- length if ( length as usize ) >= digits. len ( ) => {
45- format ! (
46- "{}{}{}" ,
47- sign,
48- digits,
49- "0" . repeat( length as usize - digits. len( ) )
50- )
51- }
52- _ => {
53- let ( left, right) = digits. split_at ( length as usize ) ;
54- format ! ( "{}{}.{}" , sign, left, right)
55- }
56- } ;
57-
58- if result. find ( '.' ) . is_some ( ) {
59- return result
60- . trim_end_matches ( '0' )
61- . trim_end_matches ( '.' )
62- . to_string ( ) ;
63- }
64-
65- result
10+ if self . is_nan ( ) {
11+ return "nan" . into ( ) ;
12+ }
13+
14+ if self . is_inf_pos ( ) {
15+ return "inf" . into ( ) ;
16+ }
17+
18+ if self . is_inf_neg ( ) {
19+ return "-inf" . into ( ) ;
20+ }
21+
22+ if self . is_zero ( ) {
23+ return "0" . into ( ) ;
24+ }
25+
26+ let formatted = with_consts ( |consts| {
27+ self . format ( Radix :: Dec , astro_float:: RoundingMode :: None , consts)
28+ } )
29+ . expect ( "failed to format Float as decimal" ) ;
30+
31+ let Some ( ( mantissa_with_sign, exponent_str) ) = formatted. split_once ( 'e' )
32+ else {
33+ return formatted;
34+ } ;
35+
36+ let Ok ( exponent) = exponent_str. parse :: < i32 > ( ) else {
37+ return formatted;
38+ } ;
39+
40+ let ( sign, mantissa) =
41+ if let Some ( rest) = mantissa_with_sign. strip_prefix ( '-' ) {
42+ ( "-" , rest)
43+ } else if let Some ( rest) = mantissa_with_sign. strip_prefix ( '+' ) {
44+ ( "" , rest)
45+ } else {
46+ ( "" , mantissa_with_sign)
47+ } ;
48+
49+ let mut parts = mantissa. split ( '.' ) ;
50+ let int_part = parts. next ( ) . unwrap_or ( "" ) ;
51+ let frac_part = parts. next ( ) . unwrap_or ( "" ) ;
52+
53+ let mut digits = String :: with_capacity ( int_part. len ( ) + frac_part. len ( ) ) ;
54+ digits. push_str ( int_part) ;
55+ digits. push_str ( frac_part) ;
56+
57+ let length = int_part. len ( ) as i32 + exponent;
58+ let digits_len = digits. len ( ) as i32 ;
59+
60+ let mut result = if length <= 0 {
61+ let zeros = ( -length) as usize ;
62+ let mut out =
63+ String :: with_capacity ( sign. len ( ) + 2 + zeros + digits. len ( ) ) ;
64+ out. push_str ( sign) ;
65+ out. push ( '0' ) ;
66+ out. push ( '.' ) ;
67+ out. extend ( std:: iter:: repeat_n ( '0' , zeros) ) ;
68+ out. push_str ( & digits) ;
69+ out
70+ } else if length >= digits_len {
71+ let zeros = ( length - digits_len) as usize ;
72+ let mut out = String :: with_capacity ( sign. len ( ) + digits. len ( ) + zeros) ;
73+ out. push_str ( sign) ;
74+ out. push_str ( & digits) ;
75+ out. extend ( std:: iter:: repeat_n ( '0' , zeros) ) ;
76+ out
77+ } else {
78+ let split_at = length as usize ;
79+ let ( left, right) = digits. split_at ( split_at) ;
80+ let mut out =
81+ String :: with_capacity ( sign. len ( ) + left. len ( ) + 1 + right. len ( ) ) ;
82+ out. push_str ( sign) ;
83+ out. push_str ( left) ;
84+ out. push ( '.' ) ;
85+ out. push_str ( right) ;
86+ out
87+ } ;
88+
89+ if result. contains ( '.' ) {
90+ while result. ends_with ( '0' ) {
91+ result. pop ( ) ;
92+ }
93+
94+ if result. ends_with ( '.' ) {
95+ result. pop ( ) ;
6696 }
6797 }
98+
99+ result
68100 }
69101
70102 fn to_f64 ( & self , rounding_mode : astro_float:: RoundingMode ) -> Option < f64 > {
71- let mut big_float = self . clone ( ) ;
72-
73- big_float. set_precision ( 64 , rounding_mode) . ok ( ) ?;
74-
75- let sign = big_float. sign ( ) ?;
103+ if self . is_nan ( ) {
104+ return None ;
105+ }
76106
77- let exponent = big_float. exponent ( ) ? as isize ;
107+ if self . is_inf_pos ( ) {
108+ return Some ( f64:: INFINITY ) ;
109+ }
78110
79- let mantissa_digits = big_float. mantissa_digits ( ) ?;
111+ if self . is_inf_neg ( ) {
112+ return Some ( f64:: NEG_INFINITY ) ;
113+ }
80114
81- if mantissa_digits . is_empty ( ) {
115+ if self . is_zero ( ) {
82116 return Some ( 0.0 ) ;
83117 }
84118
85- let mantissa = mantissa_digits[ 0 ] ;
119+ let mut big_float = self . clone ( ) ;
120+ big_float. set_precision ( 64 , rounding_mode) . ok ( ) ?;
121+
122+ let sign = big_float. sign ( ) ?;
123+ let mut exponent = big_float. exponent ( ) ? as isize ;
124+ let mantissa_digits = big_float. mantissa_digits ( ) ?;
125+ let mantissa = * mantissa_digits. first ( ) . unwrap_or ( & 0 ) ;
126+
127+ const F64_EXPONENT_BIAS : isize = 0x3ff ;
128+ const F64_EXPONENT_MAX : isize = 0x7ff ;
129+ const F64_SIGNIFICAND_BITS : usize = 52 ;
130+ const INTERNAL_SHIFT : usize = 12 ;
131+ const SIGN_MASK : u64 = 1u64 << 63 ;
86132
87133 if mantissa == 0 {
88- return Some ( 0.0 ) ;
134+ return Some ( if sign == Sign :: Neg {
135+ f64:: from_bits ( SIGN_MASK )
136+ } else {
137+ 0.0
138+ } ) ;
89139 }
90140
91- let mut exponent = exponent + 0b1111111111 ;
141+ exponent += F64_EXPONENT_BIAS ;
92142
93- let mut ret = 0u64 ;
94-
95- if exponent >= 0b11111111111 {
96- Some ( match sign {
143+ if exponent >= F64_EXPONENT_MAX {
144+ return Some ( match sign {
97145 Sign :: Pos => f64:: INFINITY ,
98146 Sign :: Neg => f64:: NEG_INFINITY ,
99- } )
100- } else if exponent <= 0 {
101- let shift = -exponent;
147+ } ) ;
148+ }
102149
103- if shift < 52 {
104- ret |= mantissa >> ( shift + 12 ) ;
150+ let sign_bit = if sign == Sign :: Neg { SIGN_MASK } else { 0 } ;
105151
106- if sign == Sign :: Neg {
107- ret |= 0x8000000000000000u64 ;
108- }
152+ if exponent <= 0 {
153+ let shift = ( -exponent) as usize ;
109154
110- Some ( f64:: from_bits ( ret) )
111- } else {
112- Some ( 0.0 )
155+ if shift >= F64_SIGNIFICAND_BITS {
156+ return Some ( f64:: from_bits ( sign_bit) ) ;
113157 }
114- } else {
115- let mantissa = mantissa << 1 ;
116158
117- exponent -= 1 ;
159+ let fraction = mantissa >> ( shift + INTERNAL_SHIFT ) ;
118160
119- if sign == Sign :: Neg {
120- ret |= 1 ;
121- }
161+ return Some ( f64:: from_bits ( sign_bit | fraction) ) ;
162+ }
122163
123- ret <<= 11 ;
124- ret |= exponent as u64 ;
125- ret <<= 52 ;
126- ret |= mantissa >> 12 ;
164+ let adjusted_mantissa = mantissa << 1 ;
165+ let adjusted_exponent = ( exponent - 1 ) as u64 ;
166+ let exponent_bits = adjusted_exponent << F64_SIGNIFICAND_BITS ;
167+ let fraction_bits = adjusted_mantissa >> INTERNAL_SHIFT ;
127168
128- Some ( f64:: from_bits ( ret) )
129- }
169+ Some ( f64:: from_bits ( sign_bit | exponent_bits | fraction_bits) )
130170 }
131171}
132172
@@ -210,4 +250,37 @@ mod tests {
210250 Some ( -1.0 )
211251 ) ;
212252 }
253+
254+ #[ test]
255+ fn convert_special_values_to_double_precision ( ) {
256+ assert_eq ! (
257+ Float :: from( f64 :: INFINITY ) . to_f64( astro_float:: RoundingMode :: ToEven ) ,
258+ Some ( f64 :: INFINITY )
259+ ) ;
260+
261+ assert_eq ! (
262+ Float :: from( f64 :: NEG_INFINITY ) . to_f64( astro_float:: RoundingMode :: ToEven ) ,
263+ Some ( f64 :: NEG_INFINITY )
264+ ) ;
265+
266+ assert_eq ! (
267+ Float :: nan( None ) . to_f64( astro_float:: RoundingMode :: ToEven ) ,
268+ None
269+ ) ;
270+ }
271+
272+ #[ test]
273+ fn convert_underflow_preserves_sign ( ) {
274+ let tiny_negative = float_from_str ( "-1e-4000" ) ;
275+
276+ let result = tiny_negative. to_f64 ( astro_float:: RoundingMode :: ToEven ) ;
277+
278+ assert ! ( result. is_some( ) ) ;
279+
280+ let value = result. unwrap ( ) ;
281+
282+ assert ! ( value. is_sign_negative( ) ) ;
283+
284+ assert_eq ! ( value, -0.0 ) ;
285+ }
213286}
0 commit comments