skip to content

In PHP, how do sprintf() conversion specifications like %05d, %'*10s, %.2f and %1$s work when formatting invoice lines?

level: middleimportance: should knowfreq 45%

answer

  1. percent, argnum, flags, width, precision
  2. 0 flag pads numbers with zeros
  3. apostrophe sets a custom pad character
  4. %d truncates a float
  5. too few values: ArgumentCountError

basics

~10 s

Each sprintf() specification is %[argnum$][flags][width][.precision]specifier: %05d zero-pads an integer to five characters, %'*10s pads a string with asterisks, %.2f prints two decimals and %1$s reuses the first argument. Too few values throw ArgumentCountError.

solid answer

~40 s

`sprintf(string $format, mixed ...$values): string` copies the format and replaces each conversion specification, shaped `%[argnum$][flags][width][.precision]specifier`. The **specifier** picks the conversion: `d` integer, `s` string, `f` or `F` float, `x` hex, `%` a literal percent. **Width** is a minimum length for the whole result, padded with spaces by default; the `0` flag pads numbers with zeros, `'` followed by a character pads with that character, and `-` left-justifies. **Precision** means digits after the point for `f`, but a maximum length for `s`. `argnum$` selects an argument by position, so `%1$s` can be reused or reordered. So `sprintf('INV-%d-%05d', 2026, 42)` gives `INV-2026-00042`. Watch for `%d` truncating `19.99` to `19`, and since PHP 8.0 too few values throw `ArgumentCountError` instead of returning `false`.

code

php · 19 lines
php
<?php
declare(strict_types=1);

$year = 2026;
$seq = 42;
$net = 119.9;

echo sprintf('INV-%d-%05d', $year, $seq), "\n";   // INV-2026-00042
echo sprintf('%-10s|%10.2F|', 'Net', $net), "\n"; // Net       |    119.90|
echo sprintf("%'*12s", 'TOTAL'), "\n";            // *******TOTAL
echo sprintf('%.3s', 'INVOICE'), "\n";            // INV
echo sprintf('%d', 19.99), "\n";                  // 19 (truncated, not rounded)
echo sprintf('%2$s %1$s', 'EUR', '119.90'), "\n"; // 119.90 EUR

try {
    $line = sprintf('%s-%s', 'INV');
} catch (ArgumentCountError $e) {
    echo get_class($e), "\n";                      // ArgumentCountError
}

go deeper

for a junior

Recall the everyday specifiers d, s, f and %%, and how to zero-pad a number to a fixed width.

for a middle

Explain the full argnum, flags, width, precision, specifier grammar, why width counts the whole value, and the PHP 8 ArgumentCountError for missing values.

for a senior

Show you would keep money out of %d, use F rather than f for machine-readable exports, and give translators positional argnum formats.

for a principal

Decide where formatting belongs in a system that serves several locales and file formats, and which output paths must be locale-independent by contract.

## The shape of a conversion specification `sprintf(string $format, mixed ...$values): string` returns a new string built from `$format`. Ordinary characters are copied as they are; each **conversion specification** fetches one value and formats it. The manual gives the prototype: ``` %[argnum$][flags][width][.precision]specifier ``` Only the `%` and the specifier are required. The parts, in order: - **argnum$**: an integer followed by `$` that selects which value to use, counting from 1. - **flags**: `-` left-justifies; `+` prints a plus sign on positive numbers; a space pads with spaces (the default); `0` pads numbers with zeros on the left; `'` followed by any character pads with that character. - **width**: the minimum number of characters for the **whole** converted value, including sign and decimal point. Since PHP 8.0 it can be `*`, taking the width from an extra argument. - **.precision**: for `e`, `E`, `f`, `F`, the number of digits after the point (6 when omitted); for `s`, a cutoff, the maximum number of characters kept. - **specifier**: the conversion itself. ## The specifiers you use every day | Specifier | Treats the value as | Example | Result | |---|---|---|---| | `d` | signed integer | `sprintf('%05d', 42)` | `00042` | | `u` | unsigned integer | `sprintf('%u', 42)` | `42` | | `s` | string | `sprintf('%-6s|', 'Net')` | `Net |` | | `f` | float, locale-aware point | `sprintf('%.2f', 119.9)` | `119.90` in the C locale | | `F` | float, always a `.` | `sprintf('%8.2F', 119.9)` | ` 119.90` | | `x` / `X` | integer in hex | `sprintf('%04X', 255)` | `00FF` | | `b` | integer in binary | `sprintf('%b', 5)` | `101` | | `e` | scientific notation | `sprintf('%.1e', 1200)` | `1.2e+3` | | `%` | a literal percent sign | `sprintf('%d%%', 20)` | `20%` | The difference between `f` and `F` matters for machine-readable output: `f` uses the decimal point of the current numeric locale, `F` never does. ## Formatting an invoice line An accounting export typically needs fixed-width, zero-padded identifiers and amounts with a fixed number of decimals: 1. **Invoice number**: `sprintf('INV-%d-%05d', $year, $sequence)` turns `2026` and `42` into `INV-2026-00042`. 2. **Amount column**: `sprintf('%10.2F', $net)` right-aligns the amount in ten characters with two decimals. 3. **Label column**: `sprintf('%-12s', $label)` left-aligns a label and pads it with spaces on the right. 4. **Filler**: `sprintf("%'.20s", $total)` pads with dots, a common look for printed totals. 5. **Reordering**: `sprintf('%2$s %1$s', 'EUR', '119.90')` prints `119.90 EUR`; translators can move arguments without changing the calling code. Remember that width counts every character: `sprintf('%06.2f', 3.14159)` is `003.14`, six characters including the point and both decimals, not six digits before the point. ## Traps - **`%d` truncates floats.** The value is converted to an integer first, so `sprintf('%d', 19.99)` gives `19`, not `20`. Format amounts with `f`/`F`, or keep them as integer minor units. - **Precision on `s` cuts.** `sprintf('%.3s', 'INVOICE')` returns `INV`; it does not pad. - **Too few values.** Since PHP 8.0 a format that needs more values than you pass throws `ArgumentCountError`; before 8.0 it returned `false` with a warning. An `argnum` of zero throws `ValueError`. - **A literal percent sign** must be written `%%`; a lone `%` starts a specification. ## The rest of the family - `printf()` writes the result to output and returns its **length** as an `int`. - `vsprintf()` and `vprintf()` take the values as one **array**, handy when they are already collected. - `fprintf()` writes to a stream resource, for example an open export file. Since PHP 8.4, a `sprintf()` call that uses only `%s` and `%d` is compiled into the equivalent string interpolation, so simple formats cost no more than concatenation. For amounts shown to people, `number_format()` adds thousands separators, which `sprintf()` never does. ## What interviewers listen for A good answer reads the prototype aloud and applies it to a concrete line rather than listing specifiers from memory. Expect probing on three points: - **Width versus precision**: width is a minimum for the whole value, precision is decimals for floats and a cutoff for strings. - **Integer conversion**: `%d` on a money amount truncates, which is a silent data bug rather than a crash. - **Failure mode**: in PHP 8 a missing value is an `ArgumentCountError`, so a broken format fails loudly in tests instead of writing `false` into an export file. Mentioning that `F` exists because `f` is locale-aware shows you have shipped files that other systems parse.

  • What is the difference between sprintf(), printf() and vsprintf() in PHP?
    `sprintf()` returns the formatted string. `printf()` writes it to output and returns the number of bytes written. `vsprintf()` behaves like `sprintf()` but takes the values as a single array, which is convenient when they are already collected, for example from a database row.
  • Why does sprintf('%06.2f', 3.14159) return '003.14' rather than '000003.14'?
    The width is the minimum length of the whole converted value, counting the digits on both sides and the decimal point. `3.14` is four characters, so two zeros bring it to six. To get more leading digits, increase the width to cover the decimals too.
  • How do argnum specifiers such as %1$s help when the format string is translated?
    They select values by position instead of by order of appearance, so a translated format can place the amount before the currency or reuse a value twice without changing the PHP call. A format may use `%1$s` several times; the value is fetched once per use.

saying these in an interview costs you the question

  • sprintf('%d', 19.99) rounds the value to 20.
  • The width in %06.2f counts only the digits before the decimal point.
  • sprintf returns false when too few values are passed in PHP 8.
  • A precision on %s pads the string to that length.
  • printf returns the formatted string, just like sprintf.
  • %f and %F are interchangeable in every environment.