From f4b248e4d8f3684ae6b7604951155301dd43206f Mon Sep 17 00:00:00 2001 From: Alexander Shirokov Date: Mon, 30 Dec 2024 22:33:40 +0100 Subject: [PATCH] uucore:format:fix floating-point rounding Handle a special case for floating-point numbers when |f| < 1.0. In this case, the total number of decimal places should include both the padding zeros which are not subject to precision rounding and the significant digits, rounded according to the precision parameter. --- .../src/lib/features/format/num_format.rs | 30 ++++++++++++++++++- tests/by-util/test_printf.rs | 24 +++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 546171b96fe..41465c80aa8 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -442,7 +442,23 @@ fn format_float_shortest( // of digits instead of the digits in the fractional part. // - If we don't force the decimal, `.` and trailing `0` in the fractional part // are trimmed. - let decimal_places = (precision as i32 - exponent) as usize; + let decimal_places = if f.abs() >= 1.0 { + (precision as i32 - exponent) as usize + } else { + // This is a special case for |f| < 1.0. + // + // `decimal_places` is calculated as the sum of the padding zeros on the left + // and the number of significant digits. The precision parameter applies only + // to the significant digits. + // + // For example, the number 0.01171875 with a precision of 6 should be interpreted as + // 2 padding zeros and 7 significant digits (1171875). Applying the precision parameter + // to 1171875 results in the number 117188. + // + // So, the final result is 0.0117188. + let padding_zeros = -f.abs().log10().floor() as usize; + precision + padding_zeros + }; let mut formatted = if decimal_places == 0 && force_decimal == ForceDecimal::Yes { format!("{f:.0}.") } else { @@ -665,4 +681,16 @@ mod test { assert_eq!(&f("1000.02030"), "1000.0203"); assert_eq!(&f("1000.00000"), "1000"); } + + #[test] + fn shortest_float_less_than_one() { + use super::format_float_shortest; + let f = |x| format_float_shortest(x, 6, Case::Lowercase, ForceDecimal::No); + assert_eq!(f(0.1171875), "0.117188"); + assert_eq!(f(-0.1171875), "-0.117188"); + assert_eq!(f(0.01171875), "0.0117188"); + assert_eq!(f(-0.01171875), "-0.0117188"); + assert_eq!(f(0.001171875), "0.00117187"); + assert_eq!(f(-0.001171875), "-0.00117187"); + } } diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 29a8cc9140f..5219b91d584 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -916,3 +916,27 @@ fn float_flag_position_space_padding() { .succeeds() .stdout_only(" +1.0"); } + +#[test] +fn float_less_than_one() { + new_ucmd!() + .args(&["%g", "0.1171875"]) + .succeeds() + .stdout_only("0.117188"); + + // The original value from #7031 issue + new_ucmd!() + .args(&["%g", "-0.1171875"]) + .succeeds() + .stdout_only("-0.117188"); + + new_ucmd!() + .args(&["%g", "0.01171875"]) + .succeeds() + .stdout_only("0.0117188"); + + new_ucmd!() + .args(&["%g", "-0.01171875"]) + .succeeds() + .stdout_only("-0.0117188"); +}