1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 20:17:45 +00:00

uucode: format: format_float_scientific: Take in &BigDecimal

No more f64 operations needed, we just trim (or extend) BigDecimal to
appropriate precision, get the digits as a string, then add the
decimal point.

Similar to what BigDecimal::write_scientific_notation does, but
we need a little bit more control.
This commit is contained in:
Nicolas Boichat 2025-03-11 11:22:23 +01:00 committed by Sylvestre Ledru
parent edaccc88b9
commit 7f0e5eb473

View file

@ -2,12 +2,13 @@
// //
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore bigdecimal // spell-checker:ignore bigdecimal prec
//! Utilities for formatting numbers in various formats //! Utilities for formatting numbers in various formats
use bigdecimal::BigDecimal; use bigdecimal::BigDecimal;
use num_traits::Signed; use num_traits::Signed;
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use num_traits::Zero;
use std::cmp::min; use std::cmp::min;
use std::io::Write; use std::io::Write;
@ -261,7 +262,7 @@ impl Formatter<&ExtendedBigDecimal> for Float {
format_float_decimal(&bd, self.precision, self.force_decimal) format_float_decimal(&bd, self.precision, self.force_decimal)
} }
FloatVariant::Scientific => { FloatVariant::Scientific => {
format_float_scientific(x, self.precision, self.case, self.force_decimal) format_float_scientific(&bd, self.precision, self.case, self.force_decimal)
} }
FloatVariant::Shortest => { FloatVariant::Shortest => {
format_float_shortest(x, self.precision, self.case, self.force_decimal) format_float_shortest(x, self.precision, self.case, self.force_decimal)
@ -357,18 +358,18 @@ fn format_float_decimal(bd: &BigDecimal, precision: usize, force_decimal: ForceD
} }
fn format_float_scientific( fn format_float_scientific(
f: f64, bd: &BigDecimal,
precision: usize, precision: usize,
case: Case, case: Case,
force_decimal: ForceDecimal, force_decimal: ForceDecimal,
) -> String { ) -> String {
debug_assert!(!f.is_sign_negative()); debug_assert!(!bd.is_negative());
let exp_char = match case { let exp_char = match case {
Case::Lowercase => 'e', Case::Lowercase => 'e',
Case::Uppercase => 'E', Case::Uppercase => 'E',
}; };
if f == 0.0 { if BigDecimal::zero().eq(bd) {
return if force_decimal == ForceDecimal::Yes && precision == 0 { return if force_decimal == ForceDecimal::Yes && precision == 0 {
format!("0.{exp_char}+00") format!("0.{exp_char}+00")
} else { } else {
@ -376,24 +377,29 @@ fn format_float_scientific(
}; };
} }
let mut exponent: i32 = f.log10().floor() as i32; // Round bd to (1 + precision) digits (including the leading digit)
let mut normalized = f / 10.0_f64.powi(exponent); // We call `with_prec` twice as it will produce an extra digit if rounding overflows
// (e.g. 9995.with_prec(3) => 1000 * 10^1, but we want 100 * 10^2).
let bd_round = bd
.with_prec(precision as u64 + 1)
.with_prec(precision as u64 + 1);
// If the normalized value will be rounded to a value greater than 10 // Convert to the form XXX * 10^-e (XXX is 1+precision digit long)
// we need to correct. let (frac, e) = bd_round.as_bigint_and_exponent();
if (normalized * 10_f64.powi(precision as i32)).round() / 10_f64.powi(precision as i32) >= 10.0
{
normalized /= 10.0;
exponent += 1;
}
let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal { // Scale down "XXX" to "X.XX": that divides by 10^precision, so add that to the exponent.
"." let digits = frac.to_str_radix(10);
} else { let (first_digit, remaining_digits) = digits.split_at(1);
"" let exponent = -e + precision as i64;
};
format!("{normalized:.precision$}{additional_dot}{exp_char}{exponent:+03}") let dot =
if !remaining_digits.is_empty() || (precision == 0 && ForceDecimal::Yes == force_decimal) {
"."
} else {
""
};
format!("{first_digit}{dot}{remaining_digits}{exp_char}{exponent:+03}")
} }
fn format_float_shortest( fn format_float_shortest(
@ -618,7 +624,14 @@ mod test {
#[test] #[test]
fn scientific_float() { fn scientific_float() {
use super::format_float_scientific; use super::format_float_scientific;
let f = |x| format_float_scientific(x, 6, Case::Lowercase, ForceDecimal::No); let f = |x| {
format_float_scientific(
&BigDecimal::from_f64(x).unwrap(),
6,
Case::Lowercase,
ForceDecimal::No,
)
};
assert_eq!(f(0.0), "0.000000e+00"); assert_eq!(f(0.0), "0.000000e+00");
assert_eq!(f(1.0), "1.000000e+00"); assert_eq!(f(1.0), "1.000000e+00");
assert_eq!(f(100.0), "1.000000e+02"); assert_eq!(f(100.0), "1.000000e+02");
@ -627,7 +640,14 @@ mod test {
assert_eq!(f(1_000_000.0), "1.000000e+06"); assert_eq!(f(1_000_000.0), "1.000000e+06");
assert_eq!(f(99_999_999.0), "1.000000e+08"); assert_eq!(f(99_999_999.0), "1.000000e+08");
let f = |x| format_float_scientific(x, 6, Case::Uppercase, ForceDecimal::No); let f = |x| {
format_float_scientific(
&BigDecimal::from_f64(x).unwrap(),
6,
Case::Uppercase,
ForceDecimal::No,
)
};
assert_eq!(f(0.0), "0.000000E+00"); assert_eq!(f(0.0), "0.000000E+00");
assert_eq!(f(123_456.789), "1.234568E+05"); assert_eq!(f(123_456.789), "1.234568E+05");
} }
@ -636,7 +656,14 @@ mod test {
fn scientific_float_zero_precision() { fn scientific_float_zero_precision() {
use super::format_float_scientific; use super::format_float_scientific;
let f = |x| format_float_scientific(x, 0, Case::Lowercase, ForceDecimal::No); let f = |x| {
format_float_scientific(
&BigDecimal::from_f64(x).unwrap(),
0,
Case::Lowercase,
ForceDecimal::No,
)
};
assert_eq!(f(0.0), "0e+00"); assert_eq!(f(0.0), "0e+00");
assert_eq!(f(1.0), "1e+00"); assert_eq!(f(1.0), "1e+00");
assert_eq!(f(100.0), "1e+02"); assert_eq!(f(100.0), "1e+02");
@ -645,7 +672,14 @@ mod test {
assert_eq!(f(1_000_000.0), "1e+06"); assert_eq!(f(1_000_000.0), "1e+06");
assert_eq!(f(99_999_999.0), "1e+08"); assert_eq!(f(99_999_999.0), "1e+08");
let f = |x| format_float_scientific(x, 0, Case::Lowercase, ForceDecimal::Yes); let f = |x| {
format_float_scientific(
&BigDecimal::from_f64(x).unwrap(),
0,
Case::Lowercase,
ForceDecimal::Yes,
)
};
assert_eq!(f(0.0), "0.e+00"); assert_eq!(f(0.0), "0.e+00");
assert_eq!(f(1.0), "1.e+00"); assert_eq!(f(1.0), "1.e+00");
assert_eq!(f(100.0), "1.e+02"); assert_eq!(f(100.0), "1.e+02");