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(
|
fn try_format_exact_int_without_suffix_scaling(
|
||||||
value: ParsedNumber,
|
value: ParsedNumber,
|
||||||
opts: &TransformOptions,
|
opts: &TransformOptions,
|
||||||
precision: usize,
|
precision: usize,
|
||||||
) -> Option<String> {
|
) -> Option<Result<String>> {
|
||||||
if opts.to != Unit::None {
|
if opts.to != Unit::None {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -557,7 +566,14 @@ fn try_format_exact_int_without_suffix_scaling(
|
|||||||
|
|
||||||
let scaled = integer / to_unit;
|
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()
|
scaled.to_string()
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
@@ -565,7 +581,23 @@ fn try_format_exact_int_without_suffix_scaling(
|
|||||||
locale_decimal_separator(),
|
locale_decimal_separator(),
|
||||||
"0".repeat(precision)
|
"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(
|
fn transform_to(
|
||||||
@@ -577,7 +609,7 @@ fn transform_to(
|
|||||||
is_precision_specified: bool,
|
is_precision_specified: bool,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
if let Some(result) = try_format_exact_int_without_suffix_scaling(s, opts, precision) {
|
if let Some(result) = try_format_exact_int_without_suffix_scaling(s, opts, precision) {
|
||||||
return Ok(result);
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = s.to_f64();
|
let s = s.to_f64();
|
||||||
|
|||||||
@@ -1513,6 +1513,32 @@ fn test_invalid_utf8_input() {
|
|||||||
.stderr_is("numfmt: invalid number: '\\377'\n");
|
.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]
|
#[test]
|
||||||
#[cfg_attr(wasi_runner, ignore = "WASI: locale env vars not propagated")]
|
#[cfg_attr(wasi_runner, ignore = "WASI: locale env vars not propagated")]
|
||||||
fn test_locale_fr_output() {
|
fn test_locale_fr_output() {
|
||||||
|
|||||||
Reference in New Issue
Block a user