PHP 8.1: Implicit incompatible float to int conversion is deprecated
Converting a float number to an integer often involves losing the fractional value of the float number. For example, 6.8
, a float number, will be 6
when converted to an integer. In dynamically typed programming languages such as PHP, sometimes this conversion is unintended and undesirable.
PHP is steadily improving the rules of its dynamic type coercing rules to be more predictable and intuitive. PHP 8.0 has improvements such as locale-independent float
to string
conversion and numeric string comparison improvements.
From PHP 8.1, a deprecation notice is emitted when a float
value is coerced to an int
value implicitly, and lost the fractional value in the process. This deprecation notice is not emitted when a float value is explicitly converted to an integer.
The behavior of Strict Types has not changed. When Strict Typing is enabled, PHP continues to throw a TypeError
exception on every implicit type coercion. Further, explicitly casting to integers ((int) $floatNum
) or and the floatval
functions are not affected.
The deprecation notices are emitted when PHP implicitly coerces a float
value to an int
value.
function getMyInteger(): int {
return '6.8';
}
In the snippet above, getMyInteger
function is declared with an int
return type. When a float value such as 6.8
is returned, PHP automatically converts it to an int
value when Strict Typing is not enabled.
calling getMyInteger
in PHP 8.1 and later results in a PHP deprecation notice.
getMyInteger();
// int(6)
PHP Deprecated: Implicit conversion from float-string "6.8" to int loses precision in ... on line ...
float
array keys
Array in PHP must be keyed by string
or int
values. Invalid key types such as objects, bool
, callable
, Enums and null
are illegal, and results in a TypeError
. However, float
values are handled differently.
When using a float
value as the array key, it is silently coerced to an int
value. This is the case even if Strict Types are enabled.
$array = [
1.2 => 'foo',
1.4 => 'bar',
1.8 => 'baz',
];
var_dump($array);
array(1) {
[1]=> string(3) "baz"
}
Notice how the $array
is created with float
values 1.2
, 1.3
, and 1.8
. Because PHP coerces them silently to int
, the final $array
only holds the last array key/value.
In PHP 8.1, the snippet above helpfully emits the deprecation notice:
Deprecated: Implicit conversion from float 1.2 to int loses precision in ... on line ...
Deprecated: Implicit conversion from float 1.4 to int loses precision in ... on line ...
Deprecated: Implicit conversion from float 1.8 to int loses precision in ... on line ...
Note that it is possible to use numeric strings as array keys:
$array = [
'1.2' => 'foo',
'1.4' => 'bar',
'1.8' => 'baz',
];
var_dump($array);
array(3) {
["1.2"]=> string(3) "foo"
["1.4"]=> string(3) "bar"
["1.8"]=> string(3) "baz"
}
Float numeric strings
This deprecation applies to numeric float strings as well, when the value must be an int
value.
$mask = 16 | "128.4";
PHP Deprecated: Implicit conversion from float-string "128.4" to int loses precision in ... on line ...
In the snippet above, "128.4"
is a numeric string that contains a valid float number. PHP still evaluates this bit-wise operation by converting the string
to int
. The new deprecation notice is emitted here as well because the effective bit-wise operation is equal to:
$mask = 16 | 128
Another example of this deprecation notice is the modulo operator (%
).
$modulus = 10 % '3.14'; // int(1)
PHP Deprecated: Implicit conversion from float-string "3.14" to int loses precision in ... code on line ...
Explicit int
casting
Note that explicitly casting a float
value to an int
value does not emit the deprecation notice, even if it also loses the fraction part of the float
value.
$yolo_pi = (int) 3.14159265359;
var_dump($yolo_pi);
// int(3)
float
values without fraction
If the float
value being implicitly coerced does not contain a fractional part, there will be no deprecation notices.
$modulus = 10 % 3.0; // int(1);
Avoiding the deprecation notice
The deprecation notice is meant to prevent unintended lose of the fractional part of float values. Explicitly converting the float numbers to an integer makes the intention visible from the PHP code.
There are two approaches convert a float
value to an int
value.
- Cast to an
int
value: When afloat
value is cast to anint
value, it loses the fractional part of the number, regardless of the value.$int_value = (int) 2.87; // $int_value = 2;
Notice how the
.87
is lost when2.87
is cast to anint
. This behavior is different from thefloor
function, that always rounds down to the nearest integer. - Round to the nearest
int
: Using theround()
function, it is possible to round afloat
value to the nearest integer. It rounds a givenfloat
to a given precision, and returns afloat
value. Setting precision to0
(parameter default value).$float_value = 2.87; $int_value = (int) round($float_value); // $int_value = 3
round
function returns afloat
value (3.0
from the example), which is then cast to anint
value with the(int)
cast.
Backwards Compatibility Impact
Fixes to avoid and correct the deprecation notices should be compatible with older PHP versions as well. The actual rules of float
to int
conversion is not changed in PHP 8.1, but such conversions will result in a TypeError
exception in PHP 9.0 and later.
The following functionality are affected:
- Arrays with
float
keys. Numeric strings containing floats are allowed. - Parameter types, return types, and property types.
- Bit-wise operators:
|
/OR
,&
/AND
, ``/XOR
. e.g.:6.8 | 1024;
- Modulus operator (
%
). e.g.:45 % 5.2;
- Bit shift operators:
>>
and<<
. e.g.:2 << 4.1; 65536 >> 6.8;
When working with dynamic values for array keys, explicitly casting them to a string
might be better approach:
public function setCache(string|int|float $key, mixed $value): void {
- $this->cache[$key] = $value;
+ $this->cache[(string) $key] = $value;
}
The snippet above uses Union Types and mixed
type introduced in PHP 8.0.