1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

numfmt: preserve trailing zeros

This commit is contained in:
Daniel Hofstetter 2022-08-01 09:48:13 +02:00
parent 38679f1c1b
commit e642ca90dd
3 changed files with 63 additions and 8 deletions

View file

@ -97,6 +97,18 @@ fn parse_suffix(s: &str) -> Result<(f64, Option<Suffix>)> {
Ok((number, suffix)) 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<Suffix>, u: &Unit) -> Result<f64> { fn remove_suffix(i: f64, s: Option<Suffix>, u: &Unit) -> Result<f64> {
match (s, u) { match (s, u) {
(Some((raw_suffix, false)), &Unit::Auto) | (Some((raw_suffix, false)), &Unit::Si) => { (Some((raw_suffix, false)), &Unit::Auto) | (Some((raw_suffix, false)), &Unit::Si) => {
@ -288,11 +300,19 @@ fn format_string(
None => source, 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( let number = transform_to(
transform_from(source_without_suffix, &options.transform)?, transform_from(source_without_suffix, &options.transform)?,
&options.transform, &options.transform,
options.round, options.round,
options.format.precision, precision,
)?; )?;
// bring back the suffix before applying padding // 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.1234, round_with_precision(0.12345, rm, 4));
assert_eq!(0.12345, round_with_precision(0.12345, rm, 5)); 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"));
}
} }

View file

@ -78,7 +78,7 @@ impl RoundMethod {
pub struct FormatOptions { pub struct FormatOptions {
pub grouping: bool, pub grouping: bool,
pub padding: Option<isize>, pub padding: Option<isize>,
pub precision: usize, pub precision: Option<usize>,
pub prefix: String, pub prefix: String,
pub suffix: String, pub suffix: String,
pub zero_padding: bool, pub zero_padding: bool,
@ -89,7 +89,7 @@ impl Default for FormatOptions {
Self { Self {
grouping: false, grouping: false,
padding: None, padding: None,
precision: 0, precision: None,
prefix: String::from(""), prefix: String::from(""),
suffix: String::from(""), suffix: String::from(""),
zero_padding: false, zero_padding: false,
@ -206,10 +206,12 @@ impl FromStr for FormatOptions {
if !precision.is_empty() { if !precision.is_empty() {
if let Ok(p) = precision.parse() { if let Ok(p) = precision.parse() {
options.precision = p; options.precision = Some(p);
} else { } else {
return Err(format!("invalid precision in format '{}'", s)); 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() { fn test_parse_format_with_precision() {
let mut expected_options = FormatOptions::default(); let mut expected_options = FormatOptions::default();
let formats = vec![ let formats = vec![
("%6.2f", Some(6), 2), ("%6.2f", Some(6), Some(2)),
("%6.f", Some(6), 0), ("%6.f", Some(6), Some(0)),
("%.2f", None, 2), ("%.2f", None, Some(2)),
("%.f", None, 0), ("%.f", None, Some(0)),
]; ];
for (format, expected_padding, expected_precision) in formats { for (format, expected_padding, expected_precision) in formats {

View file

@ -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"); .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] #[test]
fn test_from_si() { fn test_from_si() {
new_ucmd!() 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] #[test]
fn test_format_without_percentage_directive() { fn test_format_without_percentage_directive() {
let invalid_formats = vec!["", "hello"]; let invalid_formats = vec!["", "hello"];