PHP printf and sprintf
last modified November 14, 2026
In this article, we show how to format output with the PHP functions
printf, sprintf and their relatives. We cover format
specifiers, padding, precision, positional arguments, tables, and the pitfalls
of rounding.
Formatting means converting values to text in a controlled way: a number with
exactly two decimal places, an integer padded with zeros, a column of aligned
words. The printf family does this with a format string
that contains ordinary text and conversion specifications that start
with a percent sign.
The family comes from the C language, so the syntax will look familiar to C programmers. The examples were run with PHP 8.5.
The printf family
The functions differ in where the formatted text goes.
- printf: prints the text and returns its length.
- sprintf: returns the text as a string.
- vprintf, vsprintf: the same, but the values come from an array.
- fprintf, vfprintf: write to a stream, such as a file or
STDERR. - number_format: formats a number with thousands separators; not part of the family, but closely related.
Basic usage
The first argument is the format string; the other arguments are the values that replace the specifications, in order.
<?php
$name = 'Alice';
$age = 30;
$height = 1.7;
printf("Name: %s\n", $name);
printf("%s is %d years old and %.2f m tall\n", $name, $age, $height);
$s = sprintf('%s-%s', 'PHP', 8);
echo $s, "\n";
$len = printf("Hello\n");
echo "printf returned $len\n";
echo 100 . '%', "\n";
printf("Progress: %d%%\n", 75);
%s inserts a string, %d an integer, and
%.2f a floating-point number with two decimal places. The
sprintf function returns the text instead of printing it, so we can
store it or pass it on.
The printf function returns the number of characters printed. To
print a literal percent sign, we double it: %%.
$ php basic.php Name: Alice Alice is 30 years old and 1.70 m tall PHP-8 Hello printf returned 6 100% Progress: 75%
Format specifiers
The letter after the percent sign selects how the value is converted. The most important specifiers are shown in the following example.
<?php
printf("[%s] string\n", 'text');
printf("[%d] integer\n", 42);
printf("[%d] integer from float\n", 3.99);
printf("[%u] unsigned\n", 3);
printf("[%f] float\n", 3.14159);
printf("[%.3f] float, 3 decimals\n", 3.14159);
printf("[%e] scientific\n", 12345.6789);
printf("[%.2E] scientific, upper\n", 12345.6789);
printf("[%g] shortest\n", 0.00001234);
printf("[%b] binary\n", 10);
printf("[%o] octal\n", 64);
printf("[%x] hex\n", 255);
printf("[%X] hex upper\n", 255);
printf("[%c] character\n", 65);
printf("[%%] percent sign\n");
printf("[%s] [%s] [%s]\n", true, false, null);
Here is what the letters mean.
%sstring,%dsigned integer,%uunsigned integer,%cthe character with the given ASCII code.%fand%Ffloating point,%eand%Escientific notation,%gand%Gthe shorter of the two.%b,%o,%xand%Xbinary, octal, lowercase and uppercase hexadecimal.%%a literal percent sign.
Note that %d truncates a float towards zero (3.99 becomes 3) and
that the exponent of %e has no leading zeros (e+4).
Booleans are converted as strings: true is 1, while
false and null are empty strings.
$ php types.php [text] string [42] integer [3] integer from float [3] unsigned [3.141590] float [3.142] float, 3 decimals [1.234568e+4] scientific [1.23E+4] scientific, upper [1.234e-5] shortest [1010] binary [100] octal [ff] hex [FF] hex upper [A] character [%] percent sign [1] [] []
Width, padding and alignment
Between the percent sign and the letter we can place optional parts, in this
order: flags, width, and precision, written as .n.
<?php
printf("[%8d]\n", 42);
printf("[%-8d]\n", 42);
printf("[%08d]\n", 42);
printf("[%+d] [%+d]\n", 42, -42);
printf("[%10s]\n", 'PHP');
printf("[%-10s]\n", 'PHP');
printf("[%'*10s]\n", 'PHP');
printf("[%'.10d]\n", 42);
printf("[%.3s]\n", 'PHP language');
printf("[%10.3s]\n", 'PHP language');
printf("[%10.2f]\n", 3.14159);
printf("[%-10.2f]\n", 3.14159);
printf("[%010.2f]\n", 3.14159);
printf("[%'#10.2f]\n", 3.14159);
The number after the percent sign (or after the flags) is the minimum
width. Shorter values are padded with spaces on the left, so numbers
align to the right. The - flag aligns the value to the left, the
0 flag pads with zeros, and + always prints the sign.
An apostrophe followed by any character, such as '*, selects that
character as the padding. For strings, the precision limits the number of
characters printed; for floats, it is the number of decimal places.
$ php padding.php [ 42] [42 ] [00000042] [+42] [-42] [ PHP] [PHP ] [*******PHP] [........42] [PHP] [ PHP] [ 3.14] [3.14 ] [0000003.14] [######3.14]
Positional arguments
A specification can name the argument it uses with the syntax
%n$, where n starts at 1. This lets us use an argument
several times or in a different order than it is given.
<?php
$format = '%2$s likes %1$s, and %1$s likes %2$s' . "\n";
printf($format, 'PHP', 'Alice');
printf("%1\$s %1\$s %1\$s\n", 'echo');
printf("%1\$s costs %2\$.2f (%2\$d rounded down)\n", 'Tea', 3.75);
$templates = [
'en' => '%1$s %2$s',
'ja' => '%2$s %1$s',
];
foreach ($templates as $lang => $tpl) {
echo $lang, ': ', sprintf($tpl, 'Taro', 'Yamada'), "\n";
}
In double-quoted strings, the dollar sign must be escaped with a backslash,
%1\$s, otherwise PHP tries to interpolate a variable. Single-quoted
strings do not have this problem.
The last example is the real use case: translations. Different languages put the words in a different order, and with positional arguments the translator can reorder the placeholders without touching the code.
$ php positional.php Alice likes PHP, and PHP likes Alice echo echo echo Tea costs 3.75 (3 rounded down) en: Taro Yamada ja: Yamada Taro
vsprintf, fprintf and number_format
When the values are already in an array, vsprintf and
vprintf take the array instead of separate arguments.
fprintf writes to a stream.
<?php
$args = ['Alice', 30];
echo vsprintf("%s is %d years old\n", $args);
vprintf("%s is %d years old\n", ['Bob', 25]);
fprintf(STDOUT, "To stdout: %05.1f\n", 3.14159);
fprintf(STDERR, "To stderr: %s\n", 'warning');
$file = fopen('php://output', 'w');
fprintf($file, "To a stream: %s\n", 'ok');
fclose($file);
echo number_format(1234567.891, 2), "\n";
echo number_format(1234567.891, 2, ',', '.'), "\n";
echo sprintf('%.2f', 1234567.891), "\n";
The messages written with fprintf go to the standard output, to the
standard error stream, and to the php://output stream. The standard
error is not part of the normal output, so it can be redirected separately
(2>/dev/null).
The number_format function groups thousands and lets us choose the
decimal point and the thousands separator, which sprintf cannot do.
Use it for display; the plain %.2f is better for exchange formats.
$ php variants.php Alice is 30 years old Bob is 25 years old To stdout: 003.1 To stderr: warning To a stream: ok 1,234,567.89 1.234.567,89 1234567.89
Printing a table
Width and alignment flags make it easy to print neat text tables in console scripts.
<?php
$products = [
['Laptop', 1, 999.9],
['Mouse', 12, 19.5],
['Monitor', 3, 249.0],
['USB cable', 100, 2.25],
];
$row = "| %-10s | %5s | %10s |\n";
$line = '+' . str_repeat('-', 12) . '+' . str_repeat('-', 7) . '+' . str_repeat('-', 12) . "+\n";
echo $line;
printf($row, 'Item', 'Qty', 'Price');
echo $line;
$total = 0;
foreach ($products as [$item, $qty, $price]) {
printf("| %-10s | %5d | %10.2f |\n", $item, $qty, $price);
$total += $qty * $price;
}
echo $line;
printf("| %-10s | %5s | %10.2f |\n", 'Total', '', $total);
echo $line;
The row format "| %-10s | %5s | %10s |\n" is stored in a variable and
used for the header, so the columns of the header and of the body line up. The
text is aligned to the left with %-10s and the numbers to the right
with %5d and %10.2f.
The total is computed in the loop and printed under the table. The format decides the look, and the data stay raw numbers.
$ php table.php +------------+-------+------------+ | Item | Qty | Price | +------------+-------+------------+ | Laptop | 1 | 999.90 | | Mouse | 12 | 19.50 | | Monitor | 3 | 249.00 | | USB cable | 100 | 2.25 | +------------+-------+------------+ | Total | | 2205.90 | +------------+-------+------------+
Practical formats
The same specifiers solve many everyday tasks.
<?php
printf("#%02X%02X%02X\n", 255, 128, 0);
printf("%08b\n", 37);
printf("%s\n", sprintf('%032b', 5));
$seconds = 3725;
printf("%02d:%02d:%02d\n", intdiv($seconds, 3600), intdiv($seconds % 3600, 60), $seconds % 60);
printf("%04d-%02d-%02d\n", 2026, 3, 9);
printf("ID-%06d\n", 421);
$percent = 0.4567;
printf("%.1f%%\n", $percent * 100);
$bytes = 1536000;
$units = ['B', 'KB', 'MB', 'GB'];
$i = (int) floor(log($bytes, 1024));
printf("%.1f %s\n", $bytes / 1024 ** $i, $units[$i]);
$done = 7;
$total = 20;
$bar = str_repeat('#', $done) . str_repeat('.', $total - $done);
printf("[%s] %3d%%\n", $bar, $done / $total * 100);
printf("%'010d|%-10s|%5.1f\n", 42, 'left', 2.55);
We format a colour as hexadecimal, an integer as a zero-padded binary number, a
number of seconds as HH:MM:SS, a date, an identifier with leading
zeros, a percentage, a file size in readable units, and a text progress bar.
Note the %% after the percent value, and the %3d that
keeps the progress percentage right-aligned. The last line combines three
specifications with different paddings.
$ php practical.php #FF8000 00100101 00000000000000000000000000000101 01:02:05 2026-03-09 ID-000421 45.7% 1.5 MB [#######.............] 35% 0000000042|left | 2.5
Rounding and conversion pitfalls
Floating-point numbers are stored in binary, so many decimal fractions are not represented exactly. This shows when we round.
<?php
printf("%.2f\n", 1.005);
printf("%.2f\n", 2.675);
printf("%.2f\n", 1.255);
echo round(1.005, 2), "\n";
echo number_format(1.005, 2), "\n";
printf("%d\n", '12abc');
printf("%d\n", 1e3);
printf("%.15f\n", 0.1);
printf("%.20f\n", 0.1 + 0.2);
printf("%s\n", 0.1 + 0.2);
printf("%d items\n", '3');
The value 1.005 is really slightly less than 1.005, so
%.2f prints 1.00. The round function and
number_format pre-round the value and give 1.01. The
same effect is visible with other values; do not expect sprintf to
round "half up" like a calculator.
When exact decimal arithmetic matters, as with money, store amounts as integer
cents or use a library such as brick/math. See the
PHP floating-point numbers tutorial.
The %d specifier converts its argument like (int): the
string 12abc becomes 12 and 1e3 becomes 1000. Precision
beyond 15 digits exposes the binary representation, as in
0.1 + 0.2.
$ php rounding.php 1.00 2.67 1.25 1.01 1.01 12 1000 0.100000000000000 0.30000000000000004441 0.3 3 items
Errors
Since PHP 8, the format functions throw exceptions for invalid input instead of printing warnings.
<?php
try {
printf("%s and %s\n", 'one');
} catch (ArgumentCountError $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
try {
printf("%z\n", 'one');
} catch (ValueError $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
try {
printf("%0\$s\n", 'one');
} catch (ValueError $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
printf("%s\n", 'extra args are ignored', 'ignored');
Too few arguments cause an ArgumentCountError. An unknown
specifier or an invalid argument number throws a ValueError. Extra
arguments are silently ignored.
Never pass user input as the format string: a string such as %s%s%s
would consume arguments the code did not provide. Keep the format fixed and
pass user data as arguments.
$ php errors.php ArgumentCountError: 3 arguments are required, 2 given ValueError: Unknown format specifier "z" ValueError: Argument number specifier must be greater than zero and less than 2147483647 extra args are ignored
Locale
The %f, %e and %g specifiers honour the
current LC_NUMERIC locale, while %F, %E,
%G and (since PHP 8.0) %h and %H do not.
<?php
setlocale(LC_NUMERIC, 'sk_SK.utf8');
$x = 3.14159;
printf("%%f: %.2f\n", $x);
printf("%%F: %.2F\n", $x);
echo $x, "\n";
printf("%%g: %g\n", $x);
printf("%%h: %h\n", $x);
printf("%%H: %H\n", $x);
echo number_format($x, 2), "\n";
After we set the Slovak locale, whose decimal separator is a comma,
%f prints 3,14, while %F still prints a
period. Plain echo and number_format are independent
of the locale as well. The example needs the locale to be installed on the
system.
For output that is read by programs, such as CSV or JSON, always use the
locale-independent %F.
$ php locale.php %f: 3,14 %F: 3.14 3.14159 %g: 3,14159 %h: 3.14159 %H: 3.14159 3.14
Source
In this article, we have formatted strings and numbers with the
printf family of functions. See also the
PHP string functions,
PHP floating-point numbers and
PHP echo tutorials.
Author
List all PHP tutorials.