From e642ca90ddcd43545ce8dcd0a9e801939f8f42ef Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 1 Aug 2022 09:48:13 +0200 Subject: [PATCH] numfmt: preserve trailing zeros --- src/uu/numfmt/src/format.rs | 35 ++++++++++++++++++++++++++++++++++- src/uu/numfmt/src/options.rs | 16 +++++++++------- tests/by-util/test_numfmt.rs | 20 ++++++++++++++++++++ 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index 8bddb7ad1..3550f3bc2 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -97,6 +97,18 @@ fn parse_suffix(s: &str) -> Result<(f64, Option)> { Ok((number, suffix)) } +// Returns the implicit precision of a number, which is the count of digits after the dot. For +// example, 1.23 has an implicit precision of 2. +fn parse_implicit_precision(s: &str) -> usize { + match s.split_once('.') { + Some((_, decimal_part)) => decimal_part + .chars() + .take_while(|c| c.is_ascii_digit()) + .count(), + None => 0, + } +} + fn remove_suffix(i: f64, s: Option, u: &Unit) -> Result { match (s, u) { (Some((raw_suffix, false)), &Unit::Auto) | (Some((raw_suffix, false)), &Unit::Si) => { @@ -288,11 +300,19 @@ fn format_string( None => source, }; + let precision = if let Some(p) = options.format.precision { + p + } else if options.transform.from == Unit::None && options.transform.to == Unit::None { + parse_implicit_precision(source_without_suffix) + } else { + 0 + }; + let number = transform_to( transform_from(source_without_suffix, &options.transform)?, &options.transform, options.round, - options.format.precision, + precision, )?; // bring back the suffix before applying padding @@ -422,4 +442,17 @@ mod tests { assert_eq!(0.1234, round_with_precision(0.12345, rm, 4)); assert_eq!(0.12345, round_with_precision(0.12345, rm, 5)); } + + #[test] + fn test_parse_implicit_precision() { + assert_eq!(0, parse_implicit_precision("")); + assert_eq!(0, parse_implicit_precision("1")); + assert_eq!(1, parse_implicit_precision("1.2")); + assert_eq!(2, parse_implicit_precision("1.23")); + assert_eq!(3, parse_implicit_precision("1.234")); + assert_eq!(0, parse_implicit_precision("1K")); + assert_eq!(1, parse_implicit_precision("1.2K")); + assert_eq!(2, parse_implicit_precision("1.23K")); + assert_eq!(3, parse_implicit_precision("1.234K")); + } } diff --git a/src/uu/numfmt/src/options.rs b/src/uu/numfmt/src/options.rs index f59cc8ce5..acccf83e5 100644 --- a/src/uu/numfmt/src/options.rs +++ b/src/uu/numfmt/src/options.rs @@ -78,7 +78,7 @@ impl RoundMethod { pub struct FormatOptions { pub grouping: bool, pub padding: Option, - pub precision: usize, + pub precision: Option, pub prefix: String, pub suffix: String, pub zero_padding: bool, @@ -89,7 +89,7 @@ impl Default for FormatOptions { Self { grouping: false, padding: None, - precision: 0, + precision: None, prefix: String::from(""), suffix: String::from(""), zero_padding: false, @@ -206,10 +206,12 @@ impl FromStr for FormatOptions { if !precision.is_empty() { if let Ok(p) = precision.parse() { - options.precision = p; + options.precision = Some(p); } else { return Err(format!("invalid precision in format '{}'", s)); } + } else { + options.precision = Some(0); } } @@ -302,10 +304,10 @@ mod tests { fn test_parse_format_with_precision() { let mut expected_options = FormatOptions::default(); let formats = vec![ - ("%6.2f", Some(6), 2), - ("%6.f", Some(6), 0), - ("%.2f", None, 2), - ("%.f", None, 0), + ("%6.2f", Some(6), Some(2)), + ("%6.f", Some(6), Some(0)), + ("%.2f", None, Some(2)), + ("%.f", None, Some(0)), ]; for (format, expected_padding, expected_precision) in formats { diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index e062837e1..721e3e5a4 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -10,6 +10,14 @@ fn test_should_not_round_floats() { .stdout_is("0.99\n1.01\n1.1\n1.22\n0.1\n-0.1\n"); } +#[test] +fn test_should_preserve_trailing_zeros() { + new_ucmd!() + .args(&["0.1000", "10.00"]) + .succeeds() + .stdout_is("0.1000\n10.00\n"); +} + #[test] fn test_from_si() { new_ucmd!() @@ -823,6 +831,18 @@ fn test_format_with_precision_and_to_arg() { } } +#[test] +fn test_format_preserve_trailing_zeros_if_no_precision_is_specified() { + let values = vec!["10.0", "0.0100"]; + + for value in values { + new_ucmd!() + .args(&["--format=%f", value]) + .succeeds() + .stdout_is(format!("{}\n", value)); + } +} + #[test] fn test_format_without_percentage_directive() { let invalid_formats = vec!["", "hello"];