mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
numfmt: reject %f values too large to format exactly
This commit is contained in:
committed by
Daniel Hofstetter
parent
99d362039b
commit
375fc3cd48
@@ -539,11 +539,20 @@ fn consider_suffix(
|
||||
}
|
||||
}
|
||||
|
||||
fn is_too_large_to_format(scaled: i128, precision: usize) -> bool {
|
||||
const MAX_FORMATTED: u128 = 10_000_000_000_000_000_000;
|
||||
let precision_factor = 10_u128.pow(precision.min(19) as u32);
|
||||
scaled
|
||||
.unsigned_abs()
|
||||
.checked_mul(precision_factor)
|
||||
.is_none_or(|v| v >= MAX_FORMATTED)
|
||||
}
|
||||
|
||||
fn try_format_exact_int_without_suffix_scaling(
|
||||
value: ParsedNumber,
|
||||
opts: &TransformOptions,
|
||||
precision: usize,
|
||||
) -> Option<String> {
|
||||
) -> Option<Result<String>> {
|
||||
if opts.to != Unit::None {
|
||||
return None;
|
||||
}
|
||||
@@ -557,7 +566,14 @@ fn try_format_exact_int_without_suffix_scaling(
|
||||
|
||||
let scaled = integer / to_unit;
|
||||
|
||||
Some(if precision == 0 {
|
||||
if is_too_large_to_format(scaled, precision) {
|
||||
let value_sci = format_gnu_scientific(scaled as f64);
|
||||
return Some(Err(format!(
|
||||
"value/precision too large to be printed: '{value_sci}/{precision}' (consider using --to)"
|
||||
)));
|
||||
}
|
||||
|
||||
Some(Ok(if precision == 0 {
|
||||
scaled.to_string()
|
||||
} else {
|
||||
format!(
|
||||
@@ -565,7 +581,23 @@ fn try_format_exact_int_without_suffix_scaling(
|
||||
locale_decimal_separator(),
|
||||
"0".repeat(precision)
|
||||
)
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn format_gnu_scientific(v: f64) -> String {
|
||||
// 6 significant figures with trimmed trailing zeros and signed exponent
|
||||
let s = format!("{v:.5e}");
|
||||
let Some(e_pos) = s.find('e') else {
|
||||
return s;
|
||||
};
|
||||
let (mantissa, rest) = s.split_at(e_pos);
|
||||
let exp = &rest[1..];
|
||||
let mantissa = mantissa.trim_end_matches('0').trim_end_matches('.');
|
||||
if exp.starts_with('-') {
|
||||
format!("{mantissa}e{exp}")
|
||||
} else {
|
||||
format!("{mantissa}e+{exp}")
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_to(
|
||||
@@ -577,7 +609,7 @@ fn transform_to(
|
||||
is_precision_specified: bool,
|
||||
) -> Result<String> {
|
||||
if let Some(result) = try_format_exact_int_without_suffix_scaling(s, opts, precision) {
|
||||
return Ok(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
let s = s.to_f64();
|
||||
|
||||
@@ -1513,6 +1513,32 @@ fn test_invalid_utf8_input() {
|
||||
.stderr_is("numfmt: invalid number: '\\377'\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_value_too_large_issue_11936() {
|
||||
// value * 10^precision needing 20+ digits should be rejected
|
||||
let cases = [
|
||||
(vec!["--format=%5.1f", "1000000000000000000"], "1e+18/1"),
|
||||
(vec!["--format=%.2f", "100000000000000000"], "1e+17/2"),
|
||||
(vec!["--format=%.3f", "10000000000000000"], "1e+16/3"),
|
||||
];
|
||||
for (args, hint) in cases {
|
||||
new_ucmd!()
|
||||
.args(&args)
|
||||
.fails_with_code(2)
|
||||
.stderr_contains("value/precision too large")
|
||||
.stderr_contains(hint);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_value_below_large_threshold_ok() {
|
||||
// one below the cutoff still formats
|
||||
new_ucmd!()
|
||||
.args(&["--format=%5.1f", "999999999999999999"])
|
||||
.succeeds()
|
||||
.stdout_is("999999999999999999.0\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(wasi_runner, ignore = "WASI: locale env vars not propagated")]
|
||||
fn test_locale_fr_output() {
|
||||
|
||||
Reference in New Issue
Block a user