ZetCode

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.

Basic usage

The first argument is the format string; the other arguments are the values that replace the specifications, in order.

basic.php
<?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.

types.php
<?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.

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.

padding.php
<?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.

positional.php
<?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.

variants.php
<?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.

table.php
<?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.

practical.php
<?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.

rounding.php
<?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.

errors.php
<?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.

locale.php
<?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

PHP sprintf manual

PHP number_format manual

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

My name is Jan Bodnar and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all PHP tutorials.