Числа с плавающей точкой
Предупреждение из руководства PHP
Числа с плавающей точкой имеют ограниченную точность. Хотя это зависит от системы, PHP обычно использует формат двойной точности IEEE 754, который даёт максимальную относительную ошибку округления порядка 1.11e-16. Неэлементарные арифметические операции могут давать большие ошибки, и, разумеется, необходимо учитывать распространение погрешности при выполнении цепочки операций.
Кроме того, рациональные числа, точно представимые как числа с плавающей точкой в десятичной системе (основание 10), такие как 0.1 или 0.7, не имеют точного представления в двоичной системе (основание 2), которая используется внутри, вне зависимости от размера мантиссы. Поэтому они не могут быть преобразованы во внутренние двоичные аналоги без небольшой потери точности. Это может приводить к неожиданным результатам: например, floor((0.1+0.7)*10) обычно вернёт 7 вместо ожидаемых 8, потому что внутреннее представление будет чем-то вроде 7.9999999999999991118....
Поэтому никогда не доверяйте результатам вычислений с плавающей точкой до последнего знака и не сравнивайте числа с плавающей точкой напрямую на равенство. Если необходим а повышенная точность, доступны функции произвольной точности (BCMath) и функции GMP.
Простой пример (с floating-point-gui.de)
Во многих языках программирования, включая PHP:
echo 0.1 + 0.2; // 0.30000000000000004
Это происходит потому, что 0.1 и 0.2 не могут быть точно представлены в двоичной системе.
Почему так происходит? (Простое объяснение)
Числа с плавающей точкой обычно реализованы как двоичная дробь (основание 2), а не как десятичная дробь (основание 10).
- В десятичной системе дробь вроде
1/3не может быть представлена точно (это0.33333...). - Аналогично, в двоичной системе дроби вроде
1/10(0.1) или1/5(0.2) становятся периодическими дробями.
Исключение для 0.5:
Число 0.5 может быть точно представлено в двоичной системе, поскольку является степенью двойки ($2^-1$ или $1/2$).
var_dump(0.5 + 0.5 == 1.0); // true (0.5 точно представимо)
Подождите, что?
Когда вы складываете 0.1 + 0.2, на самом деле складываются:
0.1(внутреннее представление:0.1000000000000000055511151231257827021181583404541015625)0.2(внутреннее представление:0.200000000000000011102230246251565404236316680908203125)
Результат — 0.3000000000000000444089209850062616169452667236328125, именно поэтому 0.1 + 0.2 == 0.3 возвращает false.
Что можно сделать?
- Никогда не используйте
==для сравнения float. Используйте малое значение «эпсилон» (точность) для сравнения:$epsilon = 0.00001;if (abs(($a + $b) - $c) < $epsilon) {// Значения «достаточно равны»} - Используйте библиотеки произвольной точности, такие как BCMath или GMP (см. ниже).
- Используйте целые числа для денег. Храните
$1.23как123центов, чтобы полностью избежать проблем с плавающей точкой.
Как этого избежать: BC Math (арифметика произвольной точности)
Для арифметики произвольной точности PHP предоставляет BC Math Binary Calculator, который поддерживает числа любого размера и точности, представленные в виде строк.