From 4c330d43ba9a24ee31d0867b2dacaa0cb4c76a8b 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 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change resolves issues with exponent calculation and usage, ensuring more accurate formatting: - Exponent for negative values can differ from 0 - Switching to decimal mode now follows the P > X ≥ −4 rule --- .../src/lib/features/format/num_format.rs | 36 +++++++++++++- tests/by-util/test_printf.rs | 47 +++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 546171b96fe..caee8e30374 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -404,8 +404,10 @@ fn format_float_shortest( }; } - let mut exponent = f.log10().floor() as i32; - if f != 0.0 && exponent <= -4 || exponent > precision as i32 { + // Retrieve the exponent. Note that log10 is undefined for negative numbers. + // To avoid NaN or zero (due to i32 conversion), use the absolute value of f. + let mut exponent = f.abs().log10().floor() as i32; + if f != 0.0 && exponent < -4 || exponent > precision as i32 { // Scientific-ish notation (with a few differences) let mut normalized = f / 10.0_f64.powi(exponent); @@ -665,4 +667,34 @@ mod test { assert_eq!(&f("1000.02030"), "1000.0203"); assert_eq!(&f("1000.00000"), "1000"); } + + #[test] + fn shortest_float_abs_value_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.01171875), "0.0117188"); + assert_eq!(f(0.001171875), "0.00117187"); + assert_eq!(f(0.0001171875), "0.000117187"); + assert_eq!(f(0.001171875001), "0.00117188"); + assert_eq!(f(-0.1171875), "-0.117188"); + assert_eq!(f(-0.01171875), "-0.0117188"); + assert_eq!(f(-0.001171875), "-0.00117187"); + assert_eq!(f(-0.0001171875), "-0.000117187"); + assert_eq!(f(-0.001171875001), "-0.00117188"); + } + + #[test] + fn shortest_float_switch_decimal_scientific() { + use super::format_float_shortest; + let f = |x| format_float_shortest(x, 6, Case::Lowercase, ForceDecimal::No); + assert_eq!(f(0.001), "0.001"); + assert_eq!(f(0.0001), "0.0001"); + assert_eq!(f(0.00001), "1e-05"); + assert_eq!(f(0.000001), "1e-06"); + assert_eq!(f(-0.001), "-0.001"); + assert_eq!(f(-0.0001), "-0.0001"); + assert_eq!(f(-0.00001), "-1e-05"); + assert_eq!(f(-0.000001), "-1e-06"); + } } diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 29a8cc9140f..9b29c404ca8 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -916,3 +916,50 @@ fn float_flag_position_space_padding() { .succeeds() .stdout_only(" +1.0"); } + +#[test] +fn float_abs_value_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"); + + new_ucmd!() + .args(&["%g", "0.001171875001"]) + .succeeds() + .stdout_only("0.00117188"); + + new_ucmd!() + .args(&["%g", "-0.001171875001"]) + .succeeds() + .stdout_only("-0.00117188"); +} + +#[test] +fn float_switch_switch_decimal_scientific() { + new_ucmd!() + .args(&["%g", "0.0001"]) + .succeeds() + .stdout_only("0.0001"); + + new_ucmd!() + .args(&["%g", "0.00001"]) + .succeeds() + .stdout_only("1e-05"); +}