@@ -374,6 +374,41 @@ fn format_float_decimal(
374374 format ! ( "{bd:.precision$}" )
375375}
376376
377+ /// Converts a `&BigDecimal` to a scientific-like `X.XX * 10^e`.
378+ /// - The returned `String` contains the digits `XXX`, _without_ the separating
379+ /// `.` (the caller must add that to get a valid scientific format number).
380+ /// - `e` is an integer exponent.
381+ fn bd_to_string_exp_with_prec ( bd : & BigDecimal , precision : usize ) -> ( String , i64 ) {
382+ // TODO: A lot of time is spent in `with_prec` computing the exact number
383+ // of digits, it might be possible to save computation time by doing a rough
384+ // division followed by arithmetics on `digits` to round if necessary (using
385+ // `fast_inc`).
386+
387+ // Round bd to precision digits (including the leading digit)
388+ // Note that `with_prec` will produce an extra digit if rounding overflows
389+ // (e.g. 9995.with_prec(3) => 1000 * 10^1, but we want 100 * 10^2), we compensate
390+ // for that later.
391+ let bd_round = bd. with_prec ( precision as u64 ) ;
392+
393+ // Convert to the form XXX * 10^-p (XXX is precision digit long)
394+ let ( frac, mut p) = bd_round. as_bigint_and_exponent ( ) ;
395+
396+ let mut digits = frac. to_str_radix ( 10 ) ;
397+
398+ // In the unlikely case we had an overflow, correct for that.
399+ if digits. len ( ) == precision + 1 {
400+ debug_assert ! ( & digits[ precision..] == "0" ) ;
401+ digits. truncate ( precision) ;
402+ p -= 1 ;
403+ }
404+
405+ // If we end up with scientific formatting, we would convert XXX to X.XX:
406+ // that divides by 10^(precision-1), so add that to the exponent.
407+ let exponent = -p + precision as i64 - 1 ;
408+
409+ ( digits, exponent)
410+ }
411+
377412fn format_float_scientific (
378413 bd : & BigDecimal ,
379414 precision : Option < usize > ,
@@ -395,20 +430,10 @@ fn format_float_scientific(
395430 } ;
396431 }
397432
398- // Round bd to (1 + precision) digits (including the leading digit)
399- // We call `with_prec` twice as it will produce an extra digit if rounding overflows
400- // (e.g. 9995.with_prec(3) => 1000 * 10^1, but we want 100 * 10^2).
401- let bd_round = bd
402- . with_prec ( precision as u64 + 1 )
403- . with_prec ( precision as u64 + 1 ) ;
404-
405- // Convert to the form XXX * 10^-e (XXX is 1+precision digit long)
406- let ( frac, e) = bd_round. as_bigint_and_exponent ( ) ;
433+ let ( digits, exponent) = bd_to_string_exp_with_prec ( bd, precision + 1 ) ;
407434
408- // Scale down "XXX" to "X.XX": that divides by 10^precision, so add that to the exponent.
409- let digits = frac. to_str_radix ( 10 ) ;
435+ // TODO: Optimizations in format_float_shortest can be made here as well
410436 let ( first_digit, remaining_digits) = digits. split_at ( 1 ) ;
411- let exponent = -e + precision as i64 ;
412437
413438 let dot =
414439 if !remaining_digits. is_empty ( ) || ( precision == 0 && ForceDecimal :: Yes == force_decimal) {
@@ -445,18 +470,8 @@ fn format_float_shortest(
445470 } ;
446471 }
447472
448- // Round bd to precision digits (including the leading digit)
449- // We call `with_prec` twice as it will produce an extra digit if rounding overflows
450- // (e.g. 9995.with_prec(3) => 1000 * 10^1, but we want 100 * 10^2).
451- let bd_round = bd. with_prec ( precision as u64 ) . with_prec ( precision as u64 ) ;
452-
453- // Convert to the form XXX * 10^-p (XXX is precision digit long)
454- let ( frac, e) = bd_round. as_bigint_and_exponent ( ) ;
455-
456- let digits = frac. to_str_radix ( 10 ) ;
457- // If we end up with scientific formatting, we would convert XXX to X.XX:
458- // that divides by 10^(precision-1), so add that to the exponent.
459- let exponent = -e + precision as i64 - 1 ;
473+ let mut output = String :: with_capacity ( precision) ;
474+ let ( digits, exponent) = bd_to_string_exp_with_prec ( bd, precision) ;
460475
461476 if exponent < -4 || exponent >= precision as i64 {
462477 // Scientific-ish notation (with a few differences)
@@ -465,42 +480,53 @@ fn format_float_shortest(
465480 let ( first_digit, remaining_digits) = digits. split_at ( 1 ) ;
466481
467482 // Always add the dot, we might trim it later.
468- let mut normalized = format ! ( "{first_digit}.{remaining_digits}" ) ;
483+ output. push_str ( first_digit) ;
484+ output. push ( '.' ) ;
485+ output. push_str ( remaining_digits) ;
469486
470487 if force_decimal == ForceDecimal :: No {
471- strip_fractional_zeroes_and_dot ( & mut normalized ) ;
488+ strip_fractional_zeroes_and_dot ( & mut output ) ;
472489 }
473490
474- let exp_char = match case {
491+ output . push ( match case {
475492 Case :: Lowercase => 'e' ,
476493 Case :: Uppercase => 'E' ,
477- } ;
494+ } ) ;
478495
479- format ! ( "{normalized}{exp_char}{exponent:+03}" )
496+ // Format the exponent
497+ let exponent_abs = exponent. abs ( ) ;
498+ output. push ( if exponent < 0 { '-' } else { '+' } ) ;
499+ if exponent_abs < 10 {
500+ output. push ( '0' ) ;
501+ }
502+ output. push_str ( & exponent_abs. to_string ( ) ) ;
480503 } else {
481504 // Decimal-ish notation with a few differences:
482505 // - The precision works differently and specifies the total number
483506 // of digits instead of the digits in the fractional part.
484507 // - If we don't force the decimal, `.` and trailing `0` in the fractional part
485508 // are trimmed.
486- let mut formatted = if exponent < 0 {
509+ if exponent < 0 {
487510 // Small number, prepend some "0.00" string
488- let zeros = "0" . repeat ( -exponent as usize - 1 ) ;
489- format ! ( "0.{zeros}{digits}" )
511+ output. push_str ( "0." ) ;
512+ output. extend ( std:: iter:: repeat_n ( '0' , -exponent as usize - 1 ) ) ;
513+ output. push_str ( & digits) ;
490514 } else {
491515 // exponent >= 0, slot in a dot at the right spot
492516 let ( first_digits, remaining_digits) = digits. split_at ( exponent as usize + 1 ) ;
493517
494518 // Always add `.` even if it's trailing, we might trim it later
495- format ! ( "{first_digits}.{remaining_digits}" )
519+ output. push_str ( first_digits) ;
520+ output. push ( '.' ) ;
521+ output. push_str ( remaining_digits) ;
496522 } ;
497523
498524 if force_decimal == ForceDecimal :: No {
499- strip_fractional_zeroes_and_dot ( & mut formatted ) ;
525+ strip_fractional_zeroes_and_dot ( & mut output ) ;
500526 }
501-
502- formatted
503527 }
528+
529+ output
504530}
505531
506532fn format_float_hexadecimal (
0 commit comments