C / CONSOLE INPUT AND OUTPUT
Field width, precision and padding in printf
Align printf output with field width, choose the padding side and character, and set precision correctly for floats, integers and strings.
What you will learn
- Read %-8.3s as convert, cut to 3 bytes, then pad to 8 columns on the right.
- Use width to align columns; a value wider than the field breaks the alignment.
- Zero-pad with %0Nd, and remember that - or a precision cancels the 0 flag.
- Pass width and precision at run time with %*.*f and two int arguments.
Understanding Field width, precision and padding in printf
printf first converts an argument into a sequence of characters, and only then places that text into a field. The width is that field's minimum size: shorter text gets padding, longer text is printed in full and the field simply grows. That is why %5s never truncates "identifier", and why a single long value knocks an entire table out of line. Padding sits on the left by default, which right-aligns the text inside the field.
The precision, a dot followed by a number, is applied while the value is being converted, and its meaning is decided by the conversion letter: digits after the decimal point for %f and %e, significant digits for %g, a minimum number of digits for %d and %x, and a maximum number of bytes for %s. So for strings the precision is the only part that can shrink output, while for numbers it can only add digits or round the fraction. A bare dot means precision zero, so %.f is the same as %.0f and %.s prints nothing at all. Precision runs first and width pads whatever precision produced, so reading %8.3s as "cut to 3, then pad to 8" gives the right answer every time.
Flags change how the padding is applied rather than how much of it there is. The - flag moves the padding after the text; the 0 flag swaps space padding for zeros on numeric conversions and inserts those zeros after any sign or base prefix, so -314 stays a negative number and 0xff keeps its prefix. Because zeros on the right would change a number's value, - cancels 0, and a precision on an integer conversion cancels it too. An asterisk in place of the width or precision makes printf read that number from an int argument first, in left-to-right order, which is how you build columns whose size is only known at run time; a negative width passed that way means the same thing as the - flag.
<stdio.h>
int main(void)
{
printf("[%8d]\n", 42); /* minimum width 8, padded on the left */
printf("[%-8d]\n", 42); /* '-' moves the padding to the right */
printf("[%08d]\n", 42); /* '0' pads with zeros instead of spaces */
printf("[%.5d]\n", 42); /* precision on an int: minimum digits */
printf("[%8.3f]\n", 3.14159); /* 3 digits after the point, then pad */
printf("[%8.3s]\n", "abcdef"); /* precision on a string: a maximum */
printf("[%-8.3s]\n", "abcdef");
printf("[%*.*f]\n", 9, 2, 1.5); /* width 9, precision 2, from arguments */
return 0;
}
Width is a minimum applied to the already-converted text, while precision changes the conversion itself and means something different for each conversion letter.
Worked examples
A table that lines up
Combines a left-aligned text column with right-aligned integer and fixed-decimal columns.
<stdio.h>
int main(void)
{
const char *name[] = {"bolt", "washer", "hex nut"};
int qty[] = {7, 250, 42};
double price[] = {0.35, 0.0125, 1.5};
for (int i = 0; i < 3; i++)
printf("%-10s%5d%9.4f\n", name[i], qty[i], price[i]);
return 0;
}
Example explained
Line 1%-10s reserves ten columns for the name and pads on the right, so the numeric columns always start at column 11.
Line 2%5d is right-aligned, which lines up units under units: the 7 and the 0 of 250 share the same column.
Line 3%9.4f pins the decimal point because every value is given exactly four digits after it, whatever its magnitude.
Line 4The alignment only holds while every name fits in ten bytes; "countersunk screw" would push its own row three columns right.
Runtime width with * and .*
Shows how width and precision taken from arguments keep a column a fixed size no matter how long the string is.
<stdio.h>
int main(void)
{
const char *title = "precision truncates";
int col = 12;
printf("|%-*.*s|\n", col, col, title);
printf("|%-*.*s|\n", col, col, "short");
printf("%*s^ last column of the field\n", col, "");
return 0;
}
Example explained
Line 1%-*.*s consumes three arguments in order: the width, then the precision, then the pointer, so col is read twice before title.
Line 2Precision 12 cuts the 19-byte title down to "precision tr", which is what keeps the closing bar from moving.
Line 3The same specifier pads "short" with seven spaces on the right, because - put the padding after the text.
Line 4printf("%*s", col, "") prints col spaces and nothing else, a compact way to indent to a known column.
Where the zeros go
Demonstrates how the 0 flag interacts with signs, base prefixes, the - flag and a precision.
<stdio.h>
int main(void)
{
printf("[%06d] [%06d]\n", 314, -314);
printf("[%+06d] [%-06d]\n", 314, 314);
printf("[%06.2f] [%#06x]\n", -1.5, 255);
printf("[%06.3d]\n", 7);
return 0;
}
Example explained
Line 1The zeros are inserted after the sign, so -314 prints as -00314 and the field is still exactly six columns wide.
Line 2In [%-06d] the 0 flag is ignored because - is present; padding the right of a number with zeros would multiply it, so spaces are used instead.
Line 3With # the zeros land after the 0x prefix for the same reason: anything that indicates sign or base has to stay in front.
Line 4A precision on d, i, o, u, x or X switches the 0 flag off, so %06.3d makes 007 and then space-pads to six; gcc -Wall warns about both of these ignored flags.
Important notes
Width and precision count bytes, not screen columns: "Zoë" is four bytes in UTF-8, so %-10s pads it to nine visible columns and the column edge drifts.
Half-way values do not always round upwards: with glibc %.0f prints 2 for 2.5 and 4 for 3.5, because exact ties round to the even digit.
Common mistakes
Expecting width to truncate: printf("[%5s]", "identifier") prints all ten characters, the field grows to ten, and every later column on that row shifts right.
Reading %.2d as two decimal places: precision on an integer is a minimum digit count, so printf("%.2d", 5) prints 05 and the decimal point you wanted is simply absent.
Forgetting that each * eats an int argument: printf("%*d\n", 42) uses 42 as the width and then reads an argument that was never passed, which is undefined behaviour.
Try it yourself
Change, predict, then run
Print three rows of name, quantity and unit price with printf("%-10s%5d%9.2f\n", ...), then replace one name with a 14-character one and add a precision to the string field so the numeric columns stay put.
Open the C workspaceCheck your understanding
A column of numbers is printed with %8.3f and one of the values is 12345.6789. What happens?
- The value is truncated to 12345.67 so that the column stays eight characters wide.
- printf drops the leading digit to make the value fit the width of eight.
- It prints as 12345.679, nine characters wide, and the fields after it shift one column right.
- The .3 limits the output to three characters, so the field is far too narrow.
Show answer
The precision rounds the fractional part to three digits, giving the nine-character text 12345.679; since width 8 is only a minimum and the text is already longer, printf adds no padding and prints all of it, so later fields move. The first option is tempting because precision does shorten strings, but on %f the precision controls decimal digits, and no printf conversion ever cuts a number down to fit a width.