Skip to content

Fix printf and seq %f padding past 1000 zeros and panics on zero - #13460

Merged
sylvestre merged 3 commits into
uutils:mainfrom
eyupcanakman:fix/printf-large-width-precision-panic
Oct 10, 2026
Merged

sylvestre merged 3 commits into
uutils:mainfrom
eyupcanakman:fix/printf-large-width-precision-panic

Conversation

@eyupcanakman

@eyupcanakman eyupcanakman commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

printf '%.1500f' 1 printed 1, and printf '%f' 1e1001 printed 1e+1001. bigdecimal stops zero-padding past 1000 digits and falls back to exponent notation, so %f now writes out integer digits and pads the fraction itself.

Zero still aborted with a precision above 65535 in %e, %a and %#g, for example printf '%.70000e' 0. Those print their zeros directly now, and %A of zero gets the uppercase 0X prefix it was missing.

The width and large %f precision panics were fixed on main by #13857, c22ee24 and 2f675af, so I dropped my version of those and rebased on top.

Fixes #12708

@codspeed

codspeed Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 9.07%

⚡ 1 improved benchmark
✅ 396 untouched benchmarks
⏩ 54 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ Simulation five_38_bit_primes 1.8 s 1.6 s +9.07%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing eyupcanakman:fix/printf-large-width-precision-panic (a7ab2b1) with main (9edd4e3)

Open in CodSpeed

Footnotes

  1. 54 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@sylvestre

Copy link
Copy Markdown
Contributor

Please fix the job issue

@github-actions

github-actions Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

GNU testsuite comparison:

Skipping an intermittent issue tests/date/resolution (passes in this run but fails in the 'main' branch)

decimal_digits(bd, precision)
}

/// Render `bd` in decimal notation with exactly `precision` fractional digits.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure these long comments are valuable ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimmed them, kept only why each branch is there. Same in the tests.

hubot pushed a commit to coreutils/coreutils that referenced this pull request Aug 12, 2026
* tests/printf/printf.sh: Ensure a large field width or precision
produces fully padded output, and that a precision beyond 1000
digits is not truncated.
* tests/seq/seq-precision.sh: Ensure a large format precision is
fully honored.

Link: uutils/coreutils#13460

Link: #325
Copilot AI lite review requested due to automatic review settings August 19, 2026 15:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR fixes printf/seq aborts when formatting with very large field widths or precisions by avoiding Rust formatting panics and by bypassing bigdecimal’s Display padding limits for %f.

Changes:

  • Add chunked padding writer utilities to avoid std::fmt panics for large widths.
  • Rework decimal %f formatting to assemble digits directly (fixes >1000 fractional zero padding and large precision panics).
  • Add regression tests for large width/precision and bigdecimal padding behavior in printf and seq.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/by-util/test_seq.rs Adds a regression test for large seq -f precision formatting.
tests/by-util/test_printf.rs Adds regression tests for large width/precision and %f padding edge cases.
src/uucore/src/lib/features/format/spec.rs Switches width padding implementation away from std::fmt to chunked padding writes.
src/uucore/src/lib/features/format/num_format.rs Implements manual decimal digit rendering and replaces std::fmt padding with chunked padding writes.
src/uucore/src/lib/features/format/mod.rs Introduces a shared write_padding helper for chunked padding output.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +194 to +203
fn write_padding(mut writer: impl Write, pad: u8, count: usize) -> std::io::Result<()> {
let chunk = [pad; 64];
let mut remaining = count;
while remaining > 0 {
let n = remaining.min(chunk.len());
writer.write_all(&chunk[..n])?;
remaining -= n;
}
Ok(())
}
Comment on lines +194 to +195
fn write_padding(mut writer: impl Write, pad: u8, count: usize) -> std::io::Result<()> {
let chunk = [pad; 64];
Comment on lines +478 to +488
let mut out = String::with_capacity(digits.len() + precision + 2);
if scale <= 0 {
// An integer: `digits` followed by `-scale` zeros, except for zero itself.
out.push_str(&digits);
if digits != "0" {
out.extend(std::iter::repeat_n('0', -scale as usize));
}
if precision > 0 {
out.push('.');
out.extend(std::iter::repeat_n('0', precision));
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@eyupcanakman

Copy link
Copy Markdown
Contributor Author

Merged main. write_spaces and write_padding do the same thing, so I dropped write_spaces and kept write_padding, which also takes a pad byte for zero-padding.

Copilot AI review requested due to automatic review settings August 28, 2026 20:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment on lines +191 to +203
/// Write `count` copies of `pad` to `writer`, in fixed-size chunks.
///
/// `write!(writer, "{s:>width$}")` panics past a width of `u16::MAX`.
fn write_padding(mut writer: impl Write, pad: u8, count: usize) -> std::io::Result<()> {
let chunk = [pad; 64];
let mut remaining = count;
while remaining > 0 {
let n = remaining.min(chunk.len());
writer.write_all(&chunk[..n])?;
remaining -= n;
}
Ok(())
}
bigdecimal stops padding past 1000 zeros and goes to exponent notation.
Write integer digits and the fraction padding ourselves.

Zero with a precision above 65535 still panicked in %e, %a and %#g.
Build those zeros directly, and use the uppercase prefix for %A.
@eyupcanakman
eyupcanakman force-pushed the fix/printf-large-width-precision-panic branch from aa5b79f to 833fab9 Compare September 28, 2026 17:15
Copilot AI review requested due to automatic review settings September 28, 2026 17:16
@eyupcanakman eyupcanakman changed the title printf, seq: fix abort on large field width and precision Fix printf and seq %f padding past 1000 zeros and panics on zero Sep 28, 2026
@eyupcanakman

Copy link
Copy Markdown
Contributor Author

Cut this down after rebasing on main, since #13857, c22ee24 and 2f675af now cover the aborts on wide fields and big %f precision. What is left is what main still gets wrong.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return String::from("0");
}
let mut s = String::from("0.");
s.extend(std::iter::repeat_n('0', precision));
Comment on lines +1912 to +1917
fn test_zero_precision_above_formatter_limit() {
let zeros = "0".repeat(70000);
new_ucmd!()
.args(&["%.70000e", "0"])
.succeeds()
.stdout_only(format!("0.{zeros}e+00"));
Copilot AI lite review requested due to automatic review settings October 4, 2026 15:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else if let Some(s) = integer_digits(bd) {
s + "."
} else {
return format!("{bd:.0}");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we silently drop the requested precision (and the .), is that intended? if so, could you please add a short comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only kicks in past MAX_FORMAT_WIDTH zeros (%.2f 1e2000000), where main already printed 1e+2000000 and GNU prints inf with an error, so I kept that rather than writing out all the digits. Added a comment in a7ab2b1.

Copilot AI lite review requested due to automatic review settings October 5, 2026 12:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +466 to 479
let digits = usize::try_from(bd.fractional_digit_count().max(0)).unwrap_or(usize::MAX);
let written = digits.min(MAX_FMT_PRECISION);
if precision > written {
let mut s = if written > 0 {
format!("{bd:.written$}")
} else if let Some(s) = integer_digits(bd) {
s + "."
} else {
// Past `MAX_FORMAT_WIDTH` zeros, print exponent form and skip the precision and `.`.
return format!("{bd:.0}");
};
s.extend(std::iter::repeat_n('0', precision - written));
return s;
}
Comment on lines +443 to +444
.filter(|zeros| *zeros <= super::MAX_FORMAT_WIDTH)?;
let mut s = bi.to_str_radix(10);
Comment on lines +457 to +462
let s = integer_digits(bd).unwrap_or_else(|| format!("{bd:.0}"));
return if force_decimal == ForceDecimal::Yes {
format!("{s}.")
} else {
s
};
@sylvestre
sylvestre merged commit 38336e9 into uutils:main Oct 10, 2026
222 of 224 checks passed
sylvestre added a commit to sylvestre/coreutils that referenced this pull request Oct 10, 2026
A value with more than u16::MAX fractional digits was formatted to
u16::MAX digits and then padded with zeros, dropping the real digits.

Round the BigDecimal to the precision ourselves (half to even, only when
that drops digits) and write its digits out instead of going through
format!, which panics past u16::MAX.

Follow-up to uutils#13460.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

printf/seq still panic on large float precision and field width after #12572 integer-precision fix

3 participants