diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index 9c03801ef7..d926f67ed6 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -544,6 +544,7 @@ fn consider_suffix( u: Unit, round_method: RoundMethod, precision: usize, + is_precision_specified: bool, ) -> Result<(f64, Option)> { use crate::units::RawSuffix::{E, G, K, M, P, Q, R, T, Y, Z}; @@ -582,7 +583,10 @@ fn consider_suffix( } else { precision }; - let v = if precision > 0 { + let v = if is_precision_specified { + // An explicit `--format` precision, `%.0f` included, rounds the scaled + // value to exactly that many decimals. `div_round` is for the default + // presentation instead, which keeps one decimal below the next base. round_with_precision(n / bases[i], round_method, effective_precision) } else { div_round(n, bases[i], round_method) @@ -699,7 +703,7 @@ fn transform_to( let s = s.to_f64(); let i2 = s / (opts.to_unit as f64); - let (i2, s) = consider_suffix(i2, opts.to, round_method, precision)?; + let (i2, s) = consider_suffix(i2, opts.to, round_method, precision, is_precision_specified)?; let dec_sep = locale_decimal_separator(); let localize = |s: String| -> String { if dec_sep == "." { @@ -1216,7 +1220,7 @@ mod tests { use crate::options::RoundMethod; use crate::units::Unit; - let result = consider_suffix(1e27, Unit::Si, RoundMethod::FromZero, 0); + let result = consider_suffix(1e27, Unit::Si, RoundMethod::FromZero, 0, false); assert!(result.is_ok()); let (value, suffix) = result.unwrap(); assert!(suffix.is_some()); @@ -1224,7 +1228,7 @@ mod tests { assert_eq!(raw_suffix as i32, RawSuffix::R as i32); assert_eq!(value, 1.0); - let result = consider_suffix(1e30, Unit::Si, RoundMethod::FromZero, 0); + let result = consider_suffix(1e30, Unit::Si, RoundMethod::FromZero, 0, false); assert!(result.is_ok()); let (value, suffix) = result.unwrap(); assert!(suffix.is_some()); @@ -1232,7 +1236,7 @@ mod tests { assert_eq!(raw_suffix as i32, RawSuffix::Q as i32); assert_eq!(value, 1.0); - let result = consider_suffix(5e30, Unit::Si, RoundMethod::FromZero, 0); + let result = consider_suffix(5e30, Unit::Si, RoundMethod::FromZero, 0, false); assert!(result.is_ok()); let (value, suffix) = result.unwrap(); assert!(suffix.is_some()); diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index 5d3382bd6e..e9ce88f763 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -1613,6 +1613,46 @@ fn test_format_precision_zero_with_to_scale_issue_11667() { .stdout_is("5M\n"); } +// `--format=%.0f` with `--to=` must round the scaled value with the +// selected `--round` method, instead of first keeping a fractional digit and +// then rounding that to nearest. +#[test] +fn test_format_precision_zero_with_to_scale_honors_round_method() { + let cases = [ + (vec!["--to=si", "--format=%.0f", "1234567"], "2M"), + (vec!["--to=si", "--format=%.0f", "--", "-1234567"], "-2M"), + (vec!["--to=iec", "--format=%.0f", "1234567"], "2M"), + ( + vec!["--to=si", "--format=%.0f", "--round=up", "1234567"], + "2M", + ), + ( + vec!["--to=si", "--format=%.0f", "--round=down", "1934567"], + "1M", + ), + ( + vec!["--to=si", "--format=%.0f", "--round=nearest", "1234567"], + "1M", + ), + ( + vec![ + "--to=si", + "--format=%.0f", + "--round=towards-zero", + "1934567", + ], + "1M", + ), + ]; + + for (args, expected) in cases { + new_ucmd!() + .args(&args) + .succeeds() + .stdout_only(format!("{expected}\n")); + } +} + #[test] fn test_invalid_utf8_input() { // 0xFF is invalid UTF-8