PHP 8.0: Locale-independent
PHP 8.0 changes the way PHP coerces
float values to
Prior to PHP 8.0,
string conversions depended on the current locale set with the
setlocale function is not thread-safe, which means setting locale will affect all threads in a PHP process.
setlocale(LC_ALL, "fr_FR"); echo 1.618; // 1,618 // ^^^ comma
PHP does not inherit the system locale by default, but calling
setlocale without a specific locale makes it inherit the system locale. If
setlocale() is called without an explicit locale set (e.g.
setlocale(LC_ALL, null), PHP will take the system locale from
LC_ALL environment variable. System locale can be obtained by running
locale in terminal.
Both comma (
,) and period (
.) symbols are accepted as a valid decimal separator throughout the world, and there is no international standard that prefers one over another. However, PHP can only accept the period notation for floats:
$val = 1,618; // PHP Parse error: syntax error, unexpected ',' in ... on line ...
PDO extension, and functions such as
serialize try to minimize the locale-dependence with special handling for float values.
In PHP 8, all
float conversions will be locale-independent. This means even if PHP is running under a different locale that prefers periods for the decimal separator, PHP will not use that locale when float values are presented.
setlocale(LC_ALL, "de_DE"); echo 1.618; // 1.618 // ^^^ period
printf-line of functions provide several modifiers to format and convert data formats.
*printf specifiers will continue to use the
%f specifier in a locale-dependent way. It is already noted in the documentation that
%f output is locale-aware.
For locale-independent float formatting, use
setlocale(LC_ALL, "fr_FR"); printf("%f", 1.618); // 1,618000 printf("%.3f", 1.618); // 1,618 printf("%F", 1.618); // 1.618000 printf("%.3F", 1.618); // 1.618
In PHP 8, using the string specifier (
%s) will have the locale-independent float to string effect.
setlocale(LC_ALL, "fr_FR"); // PHP < 8.0 printf("%s", 1.618); // 1,618 // PHP >= 8.0 printf("%s", 1.618); // 1.618
%G specifiers are locale-aware, and continue to be so in PHP >= 8.0 as well. There are new
%H specifiers in PHP 8.0, that provide the same functionality as
%G specifiers, but in a locale-independent way.
setlocale(LC_ALL, "fr_FR"); // All PHP versions printf("%g", 1.618); // 1,618 printf("%G", 1.618); // 1,618 // New in PHP 8.0 printf("%h", 1.618); // 1.618 printf("%H", 1.618); // 1.618
|PHP < 8.0||PHP >= 8.0|
To make a locale-aware
string coercion, it is still possible to use the
sprintf function with
%g specifiers. These specifiers are in locale-aware in all PHP versions.
setlocale(LC_ALL, "fr_FR"); - echo (string) 1.618; // 1.618 + echo sprintf("%g", 1.618); // 1,618
setlocale function can be the root of side-effects due to the way it works; the locale is set per-process, and not per-thread, which opens up the possibility of one script/request setting the locale, and having that effect in other threads in the same process.
An ideal approach would be to use the
intl extension and its format helpers which are arguably much more flexible, easy to test, and use with different locales simultaneously.
$formatter = new \NumberFormatter("fr_FR", \NumberFormatter::DECIMAL); echo $formatter->format(1.618); // 1,618
100K iterations of the
0.012 sec, compared to
sprintf. While it is a 3x performance difference, it is an absolutely minimal performance impact that wouldn't even register.
Backwards Compatibility Impact
This change can cause backwards-compatibility issues, which can be difficult to spot at once.
When strict types are not enabled, PHP silently converts float and string types back and forth when cast, or when used in parameter, property, or return types. Applications with strict types enabled would have already raised on errors when type silent coercion occur; For the rest this change will silently treat float-strings as locale-independent.
If you use
setlocale function to set a locale, and relied on this PHP behavior to automatically format float values, it will now be necessary to explicitly format them to a locale-aware format.
- PHP 7.4: Underscore numeric separator
- Union Types
*precision and width modifiers in