Why 0.1 + 0.2 ≠ 0.3 in PHP: Floating-Point Pitfalls and Correct Calculation Methods
This article explains why PHP's IEEE 754 floating-point arithmetic causes 0.1 + 0.2 ≠ 0.3, demonstrates integer overflow issues near PHP_INT_MAX, and details correct approaches: tolerance-based comparison, integer fixed-decimal scaling, and arbitrary-precision extensions BCMath and GMP with string inputs.
How PHP Floats Work
PHP's float type uses IEEE 754 binary64 (double precision) with a sign bit, exponent, and 53-bit binary significand, yielding about 14 decimal digits of precision and a maximum relative rounding error of ~1.11e-16. The key is binary representation: fractions like 0.5 (1/2) and 0.125 (1/8) have finite binary expansions and are stored exactly, but 0.1 (1/10) and 0.2 (1/5) have infinite repeating binary expansions, so PHP stores the closest representable binary approximation.
Printing with printf("%.17g\n", 0.1) reveals the stored values:
0.10000000000000001
0.20000000000000001
0.30000000000000004 // 0.1 + 0.2Precision Loss Scenarios
Floating-Point Arithmetic Precision Issues
The expression 0.1 + 0.2 === 0.3 evaluates to false because the left side computes to 0.30000000000000004 while the right side is the nearest binary approximation to 0.3, which is a different binary value. The manual recommends tolerance-based comparison:
$sum = 0.1 + 0.2;
var_dump(abs($sum - 0.3) < 1e-12); // bool(true)Large Integer Overflow
On 64-bit builds, PHP_INT_MAX is 9223372036854775807. Adding 1 overflows to float: var_dump(PHP_INT_MAX + 1) yields float(9.223372036854776E+18). The float range can hold the magnitude but lacks precision to distinguish adjacent integers at that scale. Consequently:
var_dump(((PHP_INT_MAX + 1) - 1) === PHP_INT_MAX); // bool(false)
var_dump((9223372036854775808 - 1) === 9223372036854775807); // bool(false)Once an integer overflows to float, converting back cannot recover the lost low-order bits.
Correct Floating-Point Calculation in PHP
Safe Native Float Usage
Never Blindly Cast Floats to Integers!
0.58 * 100yields a float slightly less than 58. Both intval() and (int) truncate toward zero, producing 57:
$value = 0.58 * 100;
echo $value, "
"; // prints 58 (due to precision formatting)
var_dump(intval($value)); // int(57)
var_dump((int)$value); // int(57)Use round() before casting to get the nearest integer:
$result = (int)round($value); // int(58)INI precision Does Not Fix Calculations
ini_set('precision', '14')(default) controls only the string representation when echoing floats. The underlying binary value remains unchanged, so strict comparisons still fail:
$sum = 0.1 + 0.2;
ini_set('precision', '17');
echo $sum, "
"; // 0.30000000000000004
ini_set('precision', '14');
echo $sum, "
"; // 0.3
var_dump($sum === 0.3); // bool(false) serialize_precisionsimilarly affects serialize(), json_encode(), and var_dump() output but not arithmetic.
Tolerance-Based Float Comparison
For approximate equality, compare absolute difference against a context-appropriate epsilon:
$actual = 0.1 + 0.2;
$expected = 0.3;
var_dump(abs($actual - $expected) < 1e-12); // bool(true)The tolerance must be chosen per scenario; PHP_FLOAT_EPSILON (the gap at 1.0) is not a universal constant.
Integer Fixed-Decimal Arithmetic
If values have a fixed smallest unit (e.g., cents), store and compute as integers:
$unitPriceInCents = 58;
$quantity = 3;
$totalInCents = $unitPriceInCents * $quantity; // int(174)Parsing must be done from validated decimal strings at the input boundary; (int)($floatValue * 100) repeats the truncation bug. Native integers still have PHP_INT_MAX limits; beyond that, use arbitrary-precision extensions.
Core Extensions for Exact Arithmetic
BCMath for Decimal Arithmetic
BCMath performs arbitrary-precision decimal arithmetic on strings, avoiding binary float conversion entirely:
$sum = bcadd('0.1', '0.2', 1); // string(3) "0.3"
$scaled = bcmul('0.58', '100', 0); // string(2) "58"PHP 8.4+ introduces immutable BcMath\Number objects supporting natural operators:
use BcMath\Number;
$sum = new Number('0.1') + new Number('0.2');
echo $sum, "
"; // 0.3Inputs must be quoted decimal strings; converting an already-approximate float to BCMath cannot restore the original exact decimal.
GMP for Large Integers
GMP handles arbitrary-length integers. It requires the external GMP library and is not enabled by default. Large values must be passed as strings to avoid intermediate float conversion:
$number = gmp_init('9223372036854775808', 10);
$result = gmp_sub($number, '1');
echo gmp_strval($result), "
"; // 9223372036854775807GMP stores integers, not decimal fractions. For fixed-decimal work, scale to integers first; BCMath is clearer when fractional digits are part of the data.
Conclusion
How to Correctly Compute 0.1 + 0.2 in PHP
If the values are inherently approximate, native float addition with tolerance comparison and formatted output is fine. For exact decimal results like 0.3, compute from decimal string representations using BCMath: echo bcadd('0.1', '0.2', 1); // 0.3 No php.ini switch can turn binary floats into exact decimals; correctness comes from choosing the right representation before calculation.
Ongoing Evolution
BCMath and GMP continue to improve. PHP 8.6 adds gmp_prev_prime and gmp_powm_sec. The language iterates rapidly to make PHP development easier.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
