Perl Part I · Module 4 · Strings and Text
sprintf and printf: Formatted Output
A report is read by a person, and a person reads a table far more easily when the columns line up and every number carries the same number of decimal places. These two functions do that: you write a template with placeholders that specify width and precision, and hand over the values to drop into it. One of them prints the result and the other returns it as a string, which is the only difference between them. The part worth real attention is the rounding, because it is not the rounding you were taught. Asked to drop the decimals from two and a half, Perl gives two; asked the same of three and a half, it gives four. Both answers are correct, deliberate, and standard — and neither is what most people predict.
Foundation15 min readPerlPart IFundamentalsFormattingReports
Part I · Module 4 · Chapter 4.8 · sprintf and printf: Formatted Output
1. One Difference Between Them
use strict;
use warnings;
printf "printf : %-8s %5.1f%%\n", "alu", 96.195;
my $line = sprintf "sprintf : %-8s %5.1f%%", "alu", 96.195;
print "$line\n";printf : alu 96.2%
sprintf : alu 96.2%printfformats and prints.sprintfformats and returns the string.
That is the whole distinction. Use sprintf when you want to store the result, accumulate it with .= as Chapter 3.4 §3 showed, or compare it. Use printf when it goes straight out.
Both take a format string followed by the values to place into it. The format string is the template; each % in it is a placeholder, and Chapter 4.3's escapes work in it as usual — note the \n at the end of the printf and its absence from the sprintf.
2. The Conversions
use strict;
use warnings;
printf "%%s : [%s]\n", "alu";
printf "%%d : [%d]\n", 184;
printf "%%f : [%f]\n", 96.2;
printf "%%.1f : [%.1f]\n", 96.2;
printf "%%5d : [%5d]\n", 184;
printf "%%-5d : [%-5d]\n", 184;
printf "%%05d : [%05d]\n", 184;
printf "%%-10s: [%-10s]\n", "alu";
printf "%%x : [%x]\n", 255;
printf "%%X : [%X]\n", 255;
printf "%%o : [%o]\n", 8;
printf "%%b : [%b]\n", 10;
printf "%%e : [%e]\n", 12345.678;
printf "%%g : [%g]\n", 12345.678;
printf "%%%% : [%%]\n";%s : [alu]
%d : [184]
%f : [96.200000]
%.1f : [96.2]
%5d : [ 184]
%-5d : [184 ]
%05d : [00184]
%-10s: [alu ]
%x : [ff]
%X : [FF]
%o : [10]
%b : [1010]
%e : [1.234568e+04]
%g : [12345.7]
%% : [%]Reading the pieces of a placeholder, in the order they appear:
| Piece | Meaning | Example |
|---|---|---|
- | left-justify instead of right | %-10s |
0 | pad numbers with zeros instead of spaces | %05d |
| a number | minimum field width | %5d |
. then a number | precision — decimal places for f | %.1f |
| a letter | the conversion | d s f x b e g |
Three notes from that output:
%fdefaults to six decimal places.96.2printed as96.200000, which is almost never what a report wants. Always give a precision:%.1f,%.2f.%%prints a literal percent sign, because%starts a placeholder. This is the same doubling Chapter 3.2 §6 needed.- Width is a minimum, not a maximum.
%5don a six-digit number prints all six digits and breaks your alignment rather than truncating the value — which is the right choice, but it means a column can still overflow.
3. %d Truncates
use strict;
use warnings;
printf "%%d of 2.7 = %d\n", 2.7;
printf "%%d of 2.2 = %d\n", 2.2;
printf "%%d of -2.7 = %d\n", -2.7;%d of 2.7 = 2
%d of 2.2 = 2
%d of -2.7 = -2%d discards the fractional part. It does not round. 2.7 prints as 2.
That is the same truncate-toward-zero behaviour as int() in Chapter 3.2 §2, and it is the first thing to know about formatting a computed number: if you want rounding, you need %.0f, which is the next section — and which has a surprise in it.
4. %.0f Rounds Half to Even
use strict;
use warnings;
for my $v (0.5, 1.5, 2.5, 3.5, 4.5, 2.4, 2.6) {
printf "%%.0f of %-4s = %.0f\n", $v, $v;
}%.0f of 0.5 = 0
%.0f of 1.5 = 2
%.0f of 2.5 = 2
%.0f of 3.5 = 4
%.0f of 4.5 = 4
%.0f of 2.4 = 2
%.0f of 2.6 = 3Read the five halves: 0.5 → 0, 1.5 → 2, 2.5 → 2, 3.5 → 4, 4.5 → 4.
Every exact half went to the nearest even whole number. 0.5 rounded down to 0 and 2.5 rounded down to 2, while 1.5 and 3.5 rounded up. The non-halves, 2.4 and 2.6, behaved as you would expect.
This is round-half-to-even, also called banker's rounding, and it is what IEEE-754 and the C library specify. It is not a Perl quirk — the same rule applies in C, Python and most other languages that defer to the platform's formatting.
5. And Separately: Not Every Decimal Is Exactly Representable
There is a second effect, with a different cause, and merging the two into "printf rounds strangely" would leave you unable to predict either.
use strict;
use warnings;
for my $v (96.25, 0.125, 0.135) {
printf "%-7s %%.1f = %-6s %%.2f = %s\n", $v, sprintf("%.1f", $v), sprintf("%.2f", $v);
}96.25 %.1f = 96.2 %.2f = 96.25
0.125 %.1f = 0.1 %.2f = 0.12
0.135 %.1f = 0.1 %.2f = 0.14Two different things are happening:
96.25and0.125are exactly representable in binary — a quarter and an eighth. So §4's rule applies cleanly:%.1fof96.25is a tie between96.2and96.3, and2is even, giving96.2.%.2fof0.125is a tie between0.12and0.13, giving0.12.0.135is not exactly representable. The value actually stored is very slightly different from the decimal you typed, so there is no tie to break —%.2frounds according to the stored value and gives0.14.
This is Chapter 2.4's point about floating-point storage, arriving where you can see it, and Chapter 3.2 §2's callout warned it would. The practical consequence is narrow but worth stating:
Format a number for display; do not use the formatted string as a value. If a threshold matters, compare the number, not its two-decimal rendering. And if you need exact decimal arithmetic — currency, say — floating point is the wrong tool and you would reach for a module, which is beyond Part I.
6. Mismatched Argument Counts Both Warn and Both Exit Zero
use strict;
use warnings;
printf "a=%d b=%d\n", 1;a=1 b=0Missing argument in printf at too-few.pl line 4.0use strict;
use warnings;
printf "a=%d\n", 1, 2, 3;a=1Redundant argument in printf at too-many.pl line 4.The missing case is the dangerous one. Perl substituted 0 for the absent argument, and the output line a=1 b=0 is a perfectly plausible report row. A missing error count rendered as 0 reads as "no errors" — the best possible news, manufactured by a formatting bug.
The redundant case is harmless by comparison: the extra values are dropped and the line is right.
Two related cases, both warning and both producing plausible output:
use strict;
use warnings;
printf "count: [%d]\n", "alu";
my $missing;
printf "name : [%s]\n", $missing;count: [0]
name : []Argument "alu" isn't numeric in printf at wrong-types.pl line 4.
Use of uninitialized value $missing in printf at wrong-types.pl line 7.%d on non-numeric text gives 0 — the rule from Chapter 2.13 and Chapter 3.5 — and %s on undef gives the empty string. Both warn, and the same lesson applies as everywhere else in this module: the output looks like data.
7. The Payoff: An Aligned Report
A single printf cannot demonstrate alignment. Several rows can:
use strict;
use warnings;
my @rows = (
["alu", 184, 177],
["fifo", 92, 92],
["decoder", 1240, 1198],
);
printf "%-10s %6s %6s %7s\n", "block", "total", "passed", "rate";
printf "%-10s %6s %6s %7s\n", "-" x 10, "-" x 6, "-" x 6, "-" x 7;
for my $r (@rows) {
my ($name, $total, $passed) = @$r;
printf "%-10s %6d %6d %6.1f%%\n", $name, $total, $passed, $passed / $total * 100;
}block total passed rate
---------- ------ ------ -------
alu 184 177 96.2%
fifo 92 92 100.0%
decoder 1240 1198 96.6%Every column lines up, every rate has one decimal place, and the block names are left-justified while the numbers are right-justified — which is the convention that makes a numeric column scannable, because the digits line up by place value.
Four things are doing the work:
%-10sfor the name: left-justified, so the column has a straight left edge.%6dfor the counts: right-justified, so units line up under units.%6.1f%%for the rate: one decimal always, and a literal percent sign.- The same widths in the header, so the labels sit over their columns. Using
"-" x 6from Chapter 3.3 §3 for the rule means the separator cannot drift out of step with the width.
The list-of-lists and @$r are Module 5 and Module 7 syntax, present because a multi-row table is the only honest way to show alignment working. The printf lines are what this chapter is about.
Note fifo printed as 100.0% and still fits, because the field is six wide. Had the width been five, the value would have overflowed and pushed the column — §2's note that width is a minimum.
8. Why This Matters in Engineering Work
A report is a user interface. A regression summary gets read at speed by someone deciding what to look at. Aligned columns and consistent decimals are not cosmetic — they are what makes the outlier row visible.
Consistent precision prevents false comparisons. A column mixing 96.2, 96.15873015873016 and 100 is hard to scan and invites the reader to compare string lengths instead of values. One %.1f fixes it.
sprintf is how you build a sortable or comparable key. sprintf "%03d", $index gives 007, which sorts correctly as text — and Chapter 3.5 §3 warned that 007 and 7 are not eq, so generating the padded form consistently is what makes a text sort work.
The missing-argument bug is the one to watch. A format string edited to add a column, without the matching value added to the list, produces a 0 in a numeric column and a warning on STDERR. Zero is a legitimate value for almost every count in this trade, so nothing about the output looks wrong.
And the rounding rule matters for sign-off numbers. A utilisation figure reported as 85 when the true value is 84.5 is defensible; a test asserting it reports 85 will fail, because %.0f gives 84. Know which rule is in force before writing the assertion.
9. Common Mistakes
Using %f with no precision. Six decimal places. Always say %.1f or %.2f.
Expecting %d to round. It truncates. %.0f rounds.
Expecting %.0f to round halves up. 2.5 gives 2 and 0.5 gives 0. Half-to-even.
Treating a formatted string as a value. Format for display; compare the number.
Forgetting to double a literal percent sign. % starts a placeholder; %% prints one.
Letting the argument count drift from the format string. Too few substitutes 0 or "" with a warning and exits zero.
Assuming a width truncates. It is a minimum, so a long value overflows the column.
Using %s for a number you want aligned. %s does not right-justify a number the way %6d does — it pads a string, so %6s works but %6d also guarantees the value is read numerically.
10. Exercise
Exercise 1 — the two functions. Produce the same formatted line twice, once with printf and once by building it with sprintf and printing it. Say what the sprintf version lets you do that the printf version does not.
Exercise 2 — read the placeholders. Predict each before running: printf "[%5s]", "ab", printf "[%-5s]", "ab", printf "[%05.1f]", 3.14159, printf "[%x]", 4095. Then run all four.
Exercise 3 — truncate against round. Print 7.6 with %d and with %.0f. Record both and explain the difference in one sentence.
Exercise 4 — the five halves. Run the §4 script. Then write down, for each of the five halves, whether it went up or down, and state the one rule that explains all five.
Exercise 5 — a test that would fail. Write an assertion-style check that sprintf("%.0f", 2.5) equals "3", run it, and record that it fails. Then correct the expected value and say what you learned about writing tests over formatted output.
Exercise 6 — the missing argument. Write a printf with three placeholders and two values. Capture STDOUT, STDERR and the exit status. Say what appeared in place of the third value and why that is more dangerous than an error would be.
Exercise 7 — build the report. Reproduce §7's table, then add a fourth column showing the number of failures (total - passed) with a width of 5 and a right-justified header. Confirm the columns still align, then change one width to 3 and record what happens to the row for decoder.
Exercise 8 — the padded key. Using sprintf, turn the numbers 7, 42 and 365 into three-digit zero-padded strings. Confirm they sort correctly as text with a plain sort, and say why the unpadded numbers would not.
11. Summary
printfprints;sprintfreturns the string. Otherwise identical.- A placeholder is
%, then flags, then width, then.precision, then the conversion letter. %fdefaults to six decimals — always give a precision.- Width is a minimum, so a long value overflows rather than truncating.
%dtruncates toward zero;%.0frounds.%.0frounds exact halves to the nearest even digit:0.5→0,1.5→2,2.5→2,3.5→4. This is IEEE-754 behaviour, not a Perl quirk.- Separately, a decimal that is not exactly representable rounds by its stored value — which is why
%.2fof0.135is0.14while%.2fof0.125is0.12. - Too few arguments substitutes
0or"", warns, and exits zero — a plausible-looking report row produced by a formatting bug. %%is a literal percent sign.- Left-justify text and right-justify numbers to make a column scannable.
Chapter 4.9 covers the function you need before any of this works on data read from a file — the one that removes the line ending that reading a line leaves attached.
Where this fits
Part of the Perl curriculum.
