From e3872e8e8fb145f232dd6c97a4b7cd3e9e9aa95b Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Fri, 7 Mar 2025 12:19:36 +0100 Subject: [PATCH 1/4] uucore: format: force NaN back to lowercase Fixes formatting of `NaN` to `nan`. Fixes part 1 of #7412. --- .../src/lib/features/format/num_format.rs | 17 +++++++++++++++-- 1 file changed, 15 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 fac733a22..b0dd1b945 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -324,8 +324,9 @@ fn get_sign_indicator(sign: PositiveSign, x: &T) -> Str fn format_float_non_finite(f: f64, case: Case) -> String { debug_assert!(!f.is_finite()); let mut s = format!("{f}"); - if case == Case::Uppercase { - s.make_ascii_uppercase(); + match case { + Case::Lowercase => s.make_ascii_lowercase(), // Forces NaN back to nan. + Case::Uppercase => s.make_ascii_uppercase(), } s } @@ -550,6 +551,18 @@ mod test { assert_eq!(f(8), "010"); } + #[test] + fn non_finite_float() { + use super::format_float_non_finite; + let f = |x| format_float_non_finite(x, Case::Lowercase); + assert_eq!(f(f64::NAN), "nan"); + assert_eq!(f(f64::INFINITY), "inf"); + + let f = |x| format_float_non_finite(x, Case::Uppercase); + assert_eq!(f(f64::NAN), "NAN"); + assert_eq!(f(f64::INFINITY), "INF"); + } + #[test] fn decimal_float() { use super::format_float_decimal; From b7dcaa34dae729bda3055910e2d644da0104c949 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Fri, 7 Mar 2025 12:18:03 +0100 Subject: [PATCH 2/4] uucore: format: print absolute value of float, then add sign Simplifies the code, but also fixes printing of negative and positive `NaN`: `cargo run printf "%f %f\n" nan -nan` Fixes part 2 of #7412. --- .../src/lib/features/format/num_format.rs | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index b0dd1b945..0a4e47528 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -85,7 +85,7 @@ impl Formatter for SignedInt { x.abs().to_string() }; - let sign_indicator = get_sign_indicator(self.positive_sign, &x); + let sign_indicator = get_sign_indicator(self.positive_sign, x.is_negative()); write_output(writer, sign_indicator, s, self.width, self.alignment) } @@ -239,8 +239,9 @@ impl Default for Float { impl Formatter for Float { type Input = f64; - fn fmt(&self, writer: impl Write, x: Self::Input) -> std::io::Result<()> { - let mut s = if x.is_finite() { + fn fmt(&self, writer: impl Write, f: Self::Input) -> std::io::Result<()> { + let x = f.abs(); + let s = if x.is_finite() { match self.variant { FloatVariant::Decimal => { format_float_decimal(x, self.precision, self.force_decimal) @@ -259,11 +260,7 @@ impl Formatter for Float { format_float_non_finite(x, self.case) }; - // The format function will parse `x` together with its sign char, - // which should be placed in `sign_indicator`. So drop it here - s = if x < 0. { s[1..].to_string() } else { s }; - - let sign_indicator = get_sign_indicator(self.positive_sign, &x); + let sign_indicator = get_sign_indicator(self.positive_sign, f.is_sign_negative()); write_output(writer, sign_indicator, s, self.width, self.alignment) } @@ -309,8 +306,8 @@ impl Formatter for Float { } } -fn get_sign_indicator(sign: PositiveSign, x: &T) -> String { - if *x >= T::default() { +fn get_sign_indicator(sign: PositiveSign, negative: bool) -> String { + if !negative { match sign { PositiveSign::None => String::new(), PositiveSign::Plus => String::from("+"), @@ -332,6 +329,7 @@ fn format_float_non_finite(f: f64, case: Case) -> String { } fn format_float_decimal(f: f64, precision: usize, force_decimal: ForceDecimal) -> String { + debug_assert!(!f.is_sign_negative()); if precision == 0 && force_decimal == ForceDecimal::Yes { format!("{f:.0}.") } else { @@ -345,6 +343,7 @@ fn format_float_scientific( case: Case, force_decimal: ForceDecimal, ) -> String { + debug_assert!(!f.is_sign_negative()); let exp_char = match case { Case::Lowercase => 'e', Case::Uppercase => 'E', @@ -384,6 +383,7 @@ fn format_float_shortest( case: Case, force_decimal: ForceDecimal, ) -> String { + debug_assert!(!f.is_sign_negative()); // Precision here is about how many digits should be displayed // instead of how many digits for the fractional part, this means that if // we pass this to rust's format string, it's always gonna be one less. @@ -460,21 +460,21 @@ fn format_float_hexadecimal( case: Case, force_decimal: ForceDecimal, ) -> String { - let (sign, first_digit, mantissa, exponent) = if f == 0.0 { - ("", 0, 0, 0) + debug_assert!(!f.is_sign_negative()); + let (first_digit, mantissa, exponent) = if f == 0.0 { + (0, 0, 0) } else { let bits = f.to_bits(); - let sign = if (bits >> 63) == 1 { "-" } else { "" }; let exponent_bits = ((bits >> 52) & 0x7ff) as i64; let exponent = exponent_bits - 1023; let mantissa = bits & 0xf_ffff_ffff_ffff; - (sign, 1, mantissa, exponent) + (1, mantissa, exponent) }; let mut s = match (precision, force_decimal) { - (0, ForceDecimal::No) => format!("{sign}0x{first_digit}p{exponent:+}"), - (0, ForceDecimal::Yes) => format!("{sign}0x{first_digit}.p{exponent:+}"), - _ => format!("{sign}0x{first_digit}.{mantissa:0>13x}p{exponent:+}"), + (0, ForceDecimal::No) => format!("0x{first_digit}p{exponent:+}"), + (0, ForceDecimal::Yes) => format!("0x{first_digit}.p{exponent:+}"), + _ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+}"), }; if case == Case::Uppercase { @@ -675,22 +675,14 @@ mod test { assert_eq!(f(0.125), "0x1.0000000000000p-3"); assert_eq!(f(256.0), "0x1.0000000000000p+8"); assert_eq!(f(65536.0), "0x1.0000000000000p+16"); - assert_eq!(f(-0.00001), "-0x1.4f8b588e368f1p-17"); - assert_eq!(f(-0.125), "-0x1.0000000000000p-3"); - assert_eq!(f(-256.0), "-0x1.0000000000000p+8"); - assert_eq!(f(-65536.0), "-0x1.0000000000000p+16"); let f = |x| format_float_hexadecimal(x, 0, Case::Lowercase, ForceDecimal::No); assert_eq!(f(0.125), "0x1p-3"); assert_eq!(f(256.0), "0x1p+8"); - assert_eq!(f(-0.125), "-0x1p-3"); - assert_eq!(f(-256.0), "-0x1p+8"); let f = |x| format_float_hexadecimal(x, 0, Case::Lowercase, ForceDecimal::Yes); assert_eq!(f(0.125), "0x1.p-3"); assert_eq!(f(256.0), "0x1.p+8"); - assert_eq!(f(-0.125), "-0x1.p-3"); - assert_eq!(f(-256.0), "-0x1.p+8"); } #[test] @@ -716,11 +708,6 @@ mod test { 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] @@ -731,9 +718,5 @@ mod test { 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"); } } From 92a291b71de716657102224bb32d1c3e410e8d14 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Fri, 7 Mar 2025 12:48:22 +0100 Subject: [PATCH 3/4] test: printf: Add nan, inf, negative zero Add a few end-to-end tests for printf of unusual floats (nan, infinity, negative zero). --- tests/by-util/test_printf.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 5d1d300dd..098218d6c 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -886,6 +886,30 @@ fn float_with_zero_precision_should_pad() { .stdout_only("-01"); } +#[test] +fn float_non_finite() { + new_ucmd!() + .args(&[ + "%f %f %F %f %f %F", + "nan", + "-nan", + "nan", + "inf", + "-inf", + "inf", + ]) + .succeeds() + .stdout_only("nan -nan NAN inf -inf INF"); +} + +#[test] +fn float_zero_neg_zero() { + new_ucmd!() + .args(&["%f %f", "0.0", "-0.0"]) + .succeeds() + .stdout_only("0.000000 -0.000000"); +} + #[test] fn precision_check() { new_ucmd!() From 0c0d11941352936aacaa6cfb28e5b9557f0b3db3 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Tue, 11 Mar 2025 11:50:33 +0100 Subject: [PATCH 4/4] test: printf: Add a test for scientific printing of negative number This was broken before the last few commits. --- tests/by-util/test_printf.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 098218d6c..f33959ea0 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -380,6 +380,14 @@ fn sub_num_dec_trunc() { .stdout_only("pi is ~ 3.14159"); } +#[test] +fn sub_num_sci_negative() { + new_ucmd!() + .args(&["-1234 is %e", "-1234"]) + .succeeds() + .stdout_only("-1234 is -1.234000e+03"); +} + #[cfg_attr(not(feature = "test_unimplemented"), ignore)] #[test] fn sub_num_hex_float_lower() {