@@ -524,23 +524,28 @@ fn format_float_hexadecimal(
524524 // We have arbitrary precision in base 10, so we can't always represent
525525 // the value exactly (e.g. 0.1 is c.ccccc...).
526526 //
527- // Emulate x86(-64) behavior, where 64 bits are printed in total, that's
528- // 16 hex digits, including 1 before the decimal point (so 15 after).
527+ // Note that this is the maximum precision, trailing 0's are trimmed when
528+ // printing.
529+ //
530+ // Emulate x86(-64) behavior, where 64 bits at _most_ are printed in total,
531+ // that's 16 hex digits, including 1 before the decimal point (so 15 after).
529532 //
530533 // TODO: Make this configurable? e.g. arm64 value would be 28 (f128),
531534 // arm value 13 (f64).
532- let precision = precision. unwrap_or ( 15 ) ;
535+ let max_precision = precision. unwrap_or ( 15 ) ;
533536
534537 let ( prefix, exp_char) = match case {
535538 Case :: Lowercase => ( "0x" , 'p' ) ,
536539 Case :: Uppercase => ( "0X" , 'P' ) ,
537540 } ;
538541
539542 if BigDecimal :: zero ( ) . eq ( bd) {
540- return if force_decimal == ForceDecimal :: Yes && precision == 0 {
543+ // To print 0, we don't ever need any digits after the decimal point, so default to
544+ // that if precision is not specified.
545+ return if force_decimal == ForceDecimal :: Yes && precision. unwrap_or ( 0 ) == 0 {
541546 format ! ( "0x0.{exp_char}+0" )
542547 } else {
543- format ! ( "0x{:.*}{exp_char}+0" , precision, 0.0 )
548+ format ! ( "0x{:.*}{exp_char}+0" , precision. unwrap_or ( 0 ) , 0.0 )
544549 } ;
545550 }
546551
@@ -575,7 +580,8 @@ fn format_float_hexadecimal(
575580 // Then, dividing by 5^-exp10 loses at most -exp10*3 binary digits
576581 // (since 5^-exp10 < 8^-exp10), so we add that, and another bit for
577582 // rounding.
578- let margin = ( ( precision + 1 ) as i64 * 4 - frac10. bits ( ) as i64 ) . max ( 0 ) + -exp10 * 3 + 1 ;
583+ let margin =
584+ ( ( max_precision + 1 ) as i64 * 4 - frac10. bits ( ) as i64 ) . max ( 0 ) + -exp10 * 3 + 1 ;
579585
580586 // frac10 * 10^exp10 = frac10 * 2^margin * 10^exp10 * 2^-margin =
581587 // (frac10 * 2^margin * 5^exp10) * 2^exp10 * 2^-margin =
@@ -590,7 +596,7 @@ fn format_float_hexadecimal(
590596 // so the value will always be between 0x8 and 0xf.
591597 // TODO: Make this configurable? e.g. arm64 only displays 1 digit.
592598 const BEFORE_BITS : usize = 4 ;
593- let wanted_bits = ( BEFORE_BITS + precision * 4 ) as u64 ;
599+ let wanted_bits = ( BEFORE_BITS + max_precision * 4 ) as u64 ;
594600 let bits = frac2. bits ( ) ;
595601
596602 exp2 += bits as i64 - wanted_bits as i64 ;
@@ -620,18 +626,39 @@ fn format_float_hexadecimal(
620626 digits. make_ascii_uppercase ( ) ;
621627 }
622628 let ( first_digit, remaining_digits) = digits. split_at ( 1 ) ;
623- let exponent = exp2 + ( 4 * precision ) as i64 ;
629+ let exponent = exp2 + ( 4 * max_precision ) as i64 ;
624630
625- let dot =
626- if !remaining_digits. is_empty ( ) || ( precision == 0 && ForceDecimal :: Yes == force_decimal) {
627- "."
628- } else {
629- ""
630- } ;
631+ let mut remaining_digits = remaining_digits. to_string ( ) ;
632+ if precision. is_none ( ) {
633+ // Trim trailing zeros
634+ strip_fractional_zeroes ( & mut remaining_digits) ;
635+ }
636+
637+ let dot = if !remaining_digits. is_empty ( )
638+ || ( precision. unwrap_or ( 0 ) == 0 && ForceDecimal :: Yes == force_decimal)
639+ {
640+ "."
641+ } else {
642+ ""
643+ } ;
631644
632645 format ! ( "{prefix}{first_digit}{dot}{remaining_digits}{exp_char}{exponent:+}" )
633646}
634647
648+ fn strip_fractional_zeroes ( s : & mut String ) {
649+ let mut trim_to = s. len ( ) ;
650+ for ( pos, c) in s. char_indices ( ) . rev ( ) {
651+ if pos + c. len_utf8 ( ) == trim_to {
652+ if c == '0' {
653+ trim_to = pos;
654+ } else {
655+ break ;
656+ }
657+ }
658+ }
659+ s. truncate ( trim_to) ;
660+ }
661+
635662fn strip_fractional_zeroes_and_dot ( s : & mut String ) {
636663 let mut trim_to = s. len ( ) ;
637664 for ( pos, c) in s. char_indices ( ) . rev ( ) {
@@ -995,6 +1022,37 @@ mod test {
9951022 assert_eq ! ( f( "0.125" ) , "0x8.p-6" ) ;
9961023 assert_eq ! ( f( "256.0" ) , "0x8.p+5" ) ;
9971024
1025+ // Default precision, maximum 13 digits (x86-64 behavior)
1026+ let f = |x| {
1027+ format_float_hexadecimal (
1028+ & BigDecimal :: from_str ( x) . unwrap ( ) ,
1029+ None ,
1030+ Case :: Lowercase ,
1031+ ForceDecimal :: No ,
1032+ )
1033+ } ;
1034+ assert_eq ! ( f( "0" ) , "0x0p+0" ) ;
1035+ assert_eq ! ( f( "0.00001" ) , "0xa.7c5ac471b478423p-20" ) ;
1036+ assert_eq ! ( f( "0.125" ) , "0x8p-6" ) ;
1037+ assert_eq ! ( f( "4.25" ) , "0x8.8p-1" ) ;
1038+ assert_eq ! ( f( "17.203125" ) , "0x8.9ap+1" ) ;
1039+ assert_eq ! ( f( "256.0" ) , "0x8p+5" ) ;
1040+ assert_eq ! ( f( "1000.01" ) , "0xf.a00a3d70a3d70a4p+6" ) ;
1041+ assert_eq ! ( f( "65536.0" ) , "0x8p+13" ) ;
1042+
1043+ let f = |x| {
1044+ format_float_hexadecimal (
1045+ & BigDecimal :: from_str ( x) . unwrap ( ) ,
1046+ None ,
1047+ Case :: Lowercase ,
1048+ ForceDecimal :: Yes ,
1049+ )
1050+ } ;
1051+ assert_eq ! ( f( "0" ) , "0x0.p+0" ) ;
1052+ assert_eq ! ( f( "0.125" ) , "0x8.p-6" ) ;
1053+ assert_eq ! ( f( "4.25" ) , "0x8.8p-1" ) ;
1054+ assert_eq ! ( f( "256.0" ) , "0x8.p+5" ) ;
1055+
9981056 let f = |x| {
9991057 format_float_hexadecimal (
10001058 & BigDecimal :: from_str ( x) . unwrap ( ) ,
0 commit comments