@@ -265,7 +265,7 @@ impl Formatter<&ExtendedBigDecimal> for Float {
265265 format_float_scientific ( & bd, self . precision , self . case , self . force_decimal )
266266 }
267267 FloatVariant :: Shortest => {
268- format_float_shortest ( x , self . precision , self . case , self . force_decimal )
268+ format_float_shortest ( & bd , self . precision , self . case , self . force_decimal )
269269 }
270270 FloatVariant :: Hexadecimal => {
271271 format_float_hexadecimal ( x, self . precision , self . case , self . force_decimal )
@@ -403,50 +403,50 @@ fn format_float_scientific(
403403}
404404
405405fn format_float_shortest (
406- f : f64 ,
406+ bd : & BigDecimal ,
407407 precision : usize ,
408408 case : Case ,
409409 force_decimal : ForceDecimal ,
410410) -> String {
411- debug_assert ! ( !f. is_sign_negative( ) ) ;
412- // Precision here is about how many digits should be displayed
413- // instead of how many digits for the fractional part, this means that if
414- // we pass this to rust's format string, it's always gonna be one less.
415- let precision = precision. saturating_sub ( 1 ) ;
411+ debug_assert ! ( !bd. is_negative( ) ) ;
412+
413+ // Note: Precision here is how many digits should be displayed in total,
414+ // instead of how many digits in the fractional part.
416415
417- if f == 0.0 {
416+ // Precision 0 is equivalent to precision 1.
417+ let precision = precision. max ( 1 ) ;
418+
419+ if BigDecimal :: zero ( ) . eq ( bd) {
418420 return match ( force_decimal, precision) {
419- ( ForceDecimal :: Yes , 0 ) => "0." . into ( ) ,
421+ ( ForceDecimal :: Yes , 1 ) => "0." . into ( ) ,
420422 ( ForceDecimal :: Yes , _) => {
421- format ! ( "{:.*}" , precision, 0.0 )
423+ format ! ( "{:.*}" , precision - 1 , 0.0 )
422424 }
423425 ( ForceDecimal :: No , _) => "0" . into ( ) ,
424426 } ;
425427 }
426428
427- // Retrieve the exponent. Note that log10 is undefined for negative numbers.
428- // To avoid NaN or zero (due to i32 conversion), use the absolute value of f.
429- let mut exponent = f. abs ( ) . log10 ( ) . floor ( ) as i32 ;
430- if f != 0.0 && exponent < -4 || exponent > precision as i32 {
431- // Scientific-ish notation (with a few differences)
432- let mut normalized = f / 10.0_f64 . powi ( exponent) ;
429+ // Round bd to precision digits (including the leading digit)
430+ // We call `with_prec` twice as it will produce an extra digit if rounding overflows
431+ // (e.g. 9995.with_prec(3) => 1000 * 10^1, but we want 100 * 10^2).
432+ let bd_round = bd. with_prec ( precision as u64 ) . with_prec ( precision as u64 ) ;
433433
434- // If the normalized value will be rounded to a value greater than 10
435- // we need to correct.
436- if ( normalized * 10_f64 . powi ( precision as i32 ) ) . round ( ) / 10_f64 . powi ( precision as i32 )
437- >= 10.0
438- {
439- normalized /= 10.0 ;
440- exponent += 1 ;
441- }
434+ // Convert to the form XXX * 10^-p (XXX is precision digit long)
435+ let ( frac, e) = bd_round. as_bigint_and_exponent ( ) ;
442436
443- let additional_dot = if precision == 0 && ForceDecimal :: Yes == force_decimal {
444- "."
445- } else {
446- ""
447- } ;
437+ let digits = frac. to_str_radix ( 10 ) ;
438+ // If we end up with scientific formatting, we would convert XXX to X.XX:
439+ // that divides by 10^(precision-1), so add that to the exponent.
440+ let exponent = -e + precision as i64 - 1 ;
448441
449- let mut normalized = format ! ( "{normalized:.precision$}" ) ;
442+ if exponent < -4 || exponent >= precision as i64 {
443+ // Scientific-ish notation (with a few differences)
444+
445+ // Scale down "XXX" to "X.XX"
446+ let ( first_digit, remaining_digits) = digits. split_at ( 1 ) ;
447+
448+ // Always add the dot, we might trim it later.
449+ let mut normalized = format ! ( "{first_digit}.{remaining_digits}" ) ;
450450
451451 if force_decimal == ForceDecimal :: No {
452452 strip_fractional_zeroes_and_dot ( & mut normalized) ;
@@ -457,18 +457,23 @@ fn format_float_shortest(
457457 Case :: Uppercase => 'E' ,
458458 } ;
459459
460- format ! ( "{normalized}{additional_dot}{ exp_char}{exponent:+03}" )
460+ format ! ( "{normalized}{exp_char}{exponent:+03}" )
461461 } else {
462462 // Decimal-ish notation with a few differences:
463463 // - The precision works differently and specifies the total number
464464 // of digits instead of the digits in the fractional part.
465465 // - If we don't force the decimal, `.` and trailing `0` in the fractional part
466466 // are trimmed.
467- let decimal_places = ( precision as i32 - exponent) as usize ;
468- let mut formatted = if decimal_places == 0 && force_decimal == ForceDecimal :: Yes {
469- format ! ( "{f:.0}." )
467+ let mut formatted = if exponent < 0 {
468+ // Small number, prepend some "0.00" string
469+ let zeros = "0" . repeat ( -exponent as usize - 1 ) ;
470+ format ! ( "0.{zeros}{digits}" )
470471 } else {
471- format ! ( "{f:.decimal_places$}" )
472+ // exponent >= 0, slot in a dot at the right spot
473+ let ( first_digits, remaining_digits) = digits. split_at ( exponent as usize + 1 ) ;
474+
475+ // Always add `.` even if it's trailing, we might trim it later
476+ format ! ( "{first_digits}.{remaining_digits}" )
472477 } ;
473478
474479 if force_decimal == ForceDecimal :: No {
@@ -692,8 +697,17 @@ mod test {
692697 #[ test]
693698 fn shortest_float ( ) {
694699 use super :: format_float_shortest;
695- let f = |x| format_float_shortest ( x, 6 , Case :: Lowercase , ForceDecimal :: No ) ;
700+ let f = |x| {
701+ format_float_shortest (
702+ & BigDecimal :: from_f64 ( x) . unwrap ( ) ,
703+ 6 ,
704+ Case :: Lowercase ,
705+ ForceDecimal :: No ,
706+ )
707+ } ;
696708 assert_eq ! ( f( 0.0 ) , "0" ) ;
709+ assert_eq ! ( f( 0.00001 ) , "1e-05" ) ;
710+ assert_eq ! ( f( 0.0001 ) , "0.0001" ) ;
697711 assert_eq ! ( f( 1.0 ) , "1" ) ;
698712 assert_eq ! ( f( 100.0 ) , "100" ) ;
699713 assert_eq ! ( f( 123_456.789 ) , "123457" ) ;
@@ -705,8 +719,17 @@ mod test {
705719 #[ test]
706720 fn shortest_float_force_decimal ( ) {
707721 use super :: format_float_shortest;
708- let f = |x| format_float_shortest ( x, 6 , Case :: Lowercase , ForceDecimal :: Yes ) ;
722+ let f = |x| {
723+ format_float_shortest (
724+ & BigDecimal :: from_f64 ( x) . unwrap ( ) ,
725+ 6 ,
726+ Case :: Lowercase ,
727+ ForceDecimal :: Yes ,
728+ )
729+ } ;
709730 assert_eq ! ( f( 0.0 ) , "0.00000" ) ;
731+ assert_eq ! ( f( 0.00001 ) , "1.00000e-05" ) ;
732+ assert_eq ! ( f( 0.0001 ) , "0.000100000" ) ;
710733 assert_eq ! ( f( 1.0 ) , "1.00000" ) ;
711734 assert_eq ! ( f( 100.0 ) , "100.000" ) ;
712735 assert_eq ! ( f( 123_456.789 ) , "123457." ) ;
@@ -718,18 +741,38 @@ mod test {
718741 #[ test]
719742 fn shortest_float_force_decimal_zero_precision ( ) {
720743 use super :: format_float_shortest;
721- let f = |x| format_float_shortest ( x, 0 , Case :: Lowercase , ForceDecimal :: No ) ;
744+ let f = |x| {
745+ format_float_shortest (
746+ & BigDecimal :: from_f64 ( x) . unwrap ( ) ,
747+ 0 ,
748+ Case :: Lowercase ,
749+ ForceDecimal :: No ,
750+ )
751+ } ;
722752 assert_eq ! ( f( 0.0 ) , "0" ) ;
753+ assert_eq ! ( f( 0.00001 ) , "1e-05" ) ;
754+ assert_eq ! ( f( 0.0001 ) , "0.0001" ) ;
723755 assert_eq ! ( f( 1.0 ) , "1" ) ;
756+ assert_eq ! ( f( 10.0 ) , "1e+01" ) ;
724757 assert_eq ! ( f( 100.0 ) , "1e+02" ) ;
725758 assert_eq ! ( f( 123_456.789 ) , "1e+05" ) ;
726759 assert_eq ! ( f( 12.345_678_9 ) , "1e+01" ) ;
727760 assert_eq ! ( f( 1_000_000.0 ) , "1e+06" ) ;
728761 assert_eq ! ( f( 99_999_999.0 ) , "1e+08" ) ;
729762
730- let f = |x| format_float_shortest ( x, 0 , Case :: Lowercase , ForceDecimal :: Yes ) ;
763+ let f = |x| {
764+ format_float_shortest (
765+ & BigDecimal :: from_f64 ( x) . unwrap ( ) ,
766+ 0 ,
767+ Case :: Lowercase ,
768+ ForceDecimal :: Yes ,
769+ )
770+ } ;
731771 assert_eq ! ( f( 0.0 ) , "0." ) ;
772+ assert_eq ! ( f( 0.00001 ) , "1.e-05" ) ;
773+ assert_eq ! ( f( 0.0001 ) , "0.0001" ) ;
732774 assert_eq ! ( f( 1.0 ) , "1." ) ;
775+ assert_eq ! ( f( 10.0 ) , "1.e+01" ) ;
733776 assert_eq ! ( f( 100.0 ) , "1.e+02" ) ;
734777 assert_eq ! ( f( 123_456.789 ) , "1.e+05" ) ;
735778 assert_eq ! ( f( 12.345_678_9 ) , "1.e+01" ) ;
@@ -773,7 +816,14 @@ mod test {
773816 #[ test]
774817 fn shortest_float_abs_value_less_than_one ( ) {
775818 use super :: format_float_shortest;
776- let f = |x| format_float_shortest ( x, 6 , Case :: Lowercase , ForceDecimal :: No ) ;
819+ let f = |x| {
820+ format_float_shortest (
821+ & BigDecimal :: from_f64 ( x) . unwrap ( ) ,
822+ 6 ,
823+ Case :: Lowercase ,
824+ ForceDecimal :: No ,
825+ )
826+ } ;
777827 assert_eq ! ( f( 0.1171875 ) , "0.117188" ) ;
778828 assert_eq ! ( f( 0.01171875 ) , "0.0117188" ) ;
779829 assert_eq ! ( f( 0.001171875 ) , "0.00117187" ) ;
@@ -784,7 +834,14 @@ mod test {
784834 #[ test]
785835 fn shortest_float_switch_decimal_scientific ( ) {
786836 use super :: format_float_shortest;
787- let f = |x| format_float_shortest ( x, 6 , Case :: Lowercase , ForceDecimal :: No ) ;
837+ let f = |x| {
838+ format_float_shortest (
839+ & BigDecimal :: from_f64 ( x) . unwrap ( ) ,
840+ 6 ,
841+ Case :: Lowercase ,
842+ ForceDecimal :: No ,
843+ )
844+ } ;
788845 assert_eq ! ( f( 0.001 ) , "0.001" ) ;
789846 assert_eq ! ( f( 0.0001 ) , "0.0001" ) ;
790847 assert_eq ! ( f( 0.00001 ) , "1e-05" ) ;
0 commit comments