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.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
Why 0.1 + 0.2 ≠ 0.3 in PHP: Floating-Point Pitfalls and Correct Calculation Methods

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.2

Precision 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 * 100

yields 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_precision

similarly 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.3

Inputs 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), "
"; // 9223372036854775807

GMP 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PHPprecisionGMPfloating-pointinteger overflowBCMathIEEE 754numerical computation
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.