Number Formatting
A raw PHP number and a number a human wants to read are two different things. 1234567.5 is correct as a value, but nobody wants to see it that way on an invoice or a dashboard — they want 1,234,567.50, or in Germany 1.234.567,50. PHP separates these concerns cleanly: number_format() handles the common "add separators and fix decimal places" case with zero setup, while the intl extension's NumberFormatter handles the harder problem of formatting correctly for a specific locale, including currency symbols and locale-specific separator conventions.
number_format() for the common case
number_format() takes a number and up to three extra arguments: how many decimal places to show, what character to use as the decimal point, and what character to use as the thousands separator. With no extra arguments it defaults to zero decimals and US-style comma separators.
number_format basics
<?php echo number_format(1234567.891) . PHP_EOL; // 1,234,568 (rounds, 0 decimals default) echo number_format(1234567.891, 2) . PHP_EOL; // 1,234,567.89 echo number_format(1234567.891, 2, ',', '.') . PHP_EOL; // 1.234.567,89 (German-style) echo number_format(0.5) . PHP_EOL; // 1 (rounds half up)
1,234,568 1,234,567.89 1.234.567,89 1
Manually swapping separators for one locale isn't localization
Passing custom separator characters to number_format(), as shown above, gets you the right punctuation for one specific locale, but it's a hardcoded guess — it doesn't know the user's actual locale, doesn't handle currency symbols or their placement, and doesn't adapt to right-to-left number systems or non-Latin digit sets used in some locales. For real internationalization, use the intl extension's NumberFormatter, which is aware of proper locale conventions.
Locale-aware formatting with NumberFormatter
<?php
$number = 1234567.891;
$us = new NumberFormatter('en_US', NumberFormatter::DECIMAL);
echo $us->format($number) . PHP_EOL; // 1,234,567.891
$de = new NumberFormatter('de_DE', NumberFormatter::DECIMAL);
echo $de->format($number) . PHP_EOL; // 1.234.567,891
$in = new NumberFormatter('en_IN', NumberFormatter::DECIMAL);
echo $in->format($number) . PHP_EOL; // 12,34,567.891 (Indian digit grouping)1,234,567.891 1.234.567,891 12,34,567.891
Currency formatting
Currency is more than decimal places and separators — the symbol, its position (before or after the number), and even the number of decimal places vary by currency and locale (Japanese yen typically shows zero decimal places, for example). NumberFormatter's CURRENCY style handles all of that for you, given a locale and an ISO 4217 currency code.
Currency-aware formatting
<?php
$amount = 1234.5;
$usFormatter = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
echo $usFormatter->formatCurrency($amount, 'USD') . PHP_EOL; // $1,234.50
$deFormatter = new NumberFormatter('de_DE', NumberFormatter::CURRENCY);
echo $deFormatter->formatCurrency($amount, 'EUR') . PHP_EOL; // 1.234,50 €
$jpFormatter = new NumberFormatter('ja_JP', NumberFormatter::CURRENCY);
echo $jpFormatter->formatCurrency($amount, 'JPY') . PHP_EOL; // ¥1,235 (yen has no decimals)$1,234.50 1.234,50 € ¥1,235
Notice the yen example rounded 1234.5 up to 1,235 with no decimal places at all — that's the correct convention for that currency, and it's exactly the kind of detail that's easy to get wrong when you hand-roll formatting with string concatenation instead of delegating it to NumberFormatter.
Rounding modes
Both round() and NumberFormatter support alternative rounding strategies beyond "round half away from zero," which matters for financial or scientific contexts with specific rounding requirements. round() accepts a mode constant as its third argument.
Rounding modes with round()
<?php
echo round(2.5, 0, PHP_ROUND_HALF_UP) . PHP_EOL; // 3 (default behavior)
echo round(2.5, 0, PHP_ROUND_HALF_DOWN) . PHP_EOL; // 2
echo round(2.5, 0, PHP_ROUND_HALF_EVEN) . PHP_EOL; // 2 ("banker's rounding")
echo round(3.5, 0, PHP_ROUND_HALF_EVEN) . PHP_EOL; // 4 (rounds to the nearest even)3 2 2 4
PHP_ROUND_HALF_EVEN — banker's rounding — rounds a tied value (exactly halfway between two numbers) toward whichever neighbor is even. It's used in some accounting and statistical contexts because it avoids a consistent upward bias when rounding many values.
Tool | Best for |
|---|---|
| Quick, fixed-format output when you already know the exact separators you want |
| Locale-correct plain number formatting |
| Locale- and currency-correct formatting with symbols |
| Controlling tie-breaking behavior for calculations, not just display |
number_format()rounds (it does not truncate) and always needs explicit separator arguments for non-US conventions.NumberFormatter(from theintlextension) is locale-aware and handles grouping, decimals, and currency symbols correctly per locale.Currencies differ in decimal places (yen has none) — let
NumberFormatter::CURRENCYhandle that rather than hardcoding two decimals everywhere.round()supports rounding modes beyond the default "half away from zero," including banker's rounding viaPHP_ROUND_HALF_EVEN.