Contents
When 0.1 + 0.2 isn't 0.3: The silent killer in PHP code
Fellow developers, have you ever stared at your screen at 2 AM, coffee gone cold, wondering why your simple calculation is lying to you? That moment when 0.1 + 0.2 == 0.3 returns false, and your e-commerce cart total is off by a penny. PHP floating point precision issues aren't just a quirk—they're a trap that bites when you least expect it. I've been there, shipping code that worked in tests but crumbled in production. Let's unpack this beast together, from the why to the fixes that actually stick.
Picture this: you're building a pricing module for an online store. Discounts, taxes, shipping—everything adds up to a clean total. But somewhere in the math, a ghost creeps in. Numbers that should be exact drift into something like 0.30000000000000004. It's not a bug in your logic. It's the binary heart of how computers handle decimals.
PHP uses IEEE 754 double-precision floats, giving about 14 decimal digits of precision. Sounds solid, right? Wrong for decimals like 0.1. In binary, 0.1 can't be represented exactly—it's an endless fraction, chopped to the closest approximation. Add 0.2, and the errors compound. Run this:
$a = 0.1;
$b = 0.2;
$sum = $a + $b;
var_dump($sum == 0.3); // false
echo sprintf("%.17f", $sum); // 0.30000000000000004
That tiny epsilon (around 1.11e-16 relative error) snowballs in loops or repeated ops. I've seen it tank inventory counts or miscalculate interest in fintech apps. The pain? It hides until a specific input triggers it.
Why your brain hates this (and so does your code)
Computers think in binary. Decimals? Messy. 1/10 is 0.0001100110011… repeating forever. Finite bits mean truncation. Simple as 2.05 becomes 2.0499999523162841796875 or 2.05000019073486328125. Everyday math masks it, but comparisons expose the lie.
Try this classic:
$x = 12.6;
$y = 10;
$z = $x - $y; // Expect 2.6
var_dump($z == 2.6); // false! It's ~2.5999999999999999
Logical? Sure. Real? Nope. Even var_dump(0.1) looks clean, but hidden precision lurks. Edge cases like 69.1 - floor(69.1) spit out 0.099999999999994 instead of 0.1. Low numbers might pass tests; scale up, and chaos.
PHP's precision ini setting (default 14) controls output, not storage. Errors propagate—compound them, and you're debugging ghosts.
Spotting the beast in your wild
Ever hit a wall where if ($total >= $threshold) flips unexpectedly? That's it. Loops amplify: add 0.1 ten times, expect 1.0, get 0.999999999. Financial apps? Disaster. Games? Physics glitches. Reports? Rounded totals mismatch sums.
Real-world hit: I once chased a billing bug for hours. Client totals off by 0.01. Turns out, repeated tax calcs drifted. Output looked fine with echo $total;, but sprintf("%.2f", $total) revealed the truth.
Constants help probe limits:
PHP_FLOAT_MAX: ~1.7976931348623E+308PHP_FLOAT_MIN: ~2.2250738585072E-308PHP_FLOAT_EPSILON: tiniest diff, ~2.22E-16
Check infinities or NaNs too: is_infinite($val) or is_nan($val). Vital for safe math.
Quick fixes that save your sanity
Don't fight the float—outsmart it. Here's your toolkit, battle-tested.
1. Round aggressively
Wrap ops in round(), floor(), or ceil(). Precision param controls decimals.
$x = 12.6;
$y = 10;
$z = $x - $y;
var_dump(round($z, 1) == 2.6); // true
echo round(2.34567, 2); // 2.35
For display: number_format($val, 2) prettifies without altering math.
2. Ditch == for smart compares
Never == floats. Use epsilon tolerance:
function nearlyEqual($a, $b, $epsilon = 1E-10) {
return abs($a - $b) < $epsilon || abs($a - $b) < $epsilon * max(abs($a), abs($b));
}
var_dump(nearlyEqual(0.1 + 0.2, 0.3)); // true
PHP_FLOAT_EPSILON tunes it for near-zero.
3. Scale to integers (the currency hack)
For money? Multiply by 100 (cents), use ints. Add/sub/mul safe; div carefully.
$price = 19.99 * 100; // 1999
$tax = 1.99 * 100; // 199
$total = ($price + $tax) / 100; // 21.98 exact
Limits range, but rock-solid for 2-4 decimals.
4. BC Math: Arbitrary precision powerhouse
PHP's hero for exact decimals. Slowish, but flawless.
bcscale(2); // 2 decimals
echo bcsub('20.01', '20.00'); // 0.01
$value1 = '10.00' + '2.88' + '2.88' + '2.88' + '9.00'; // Strings!
if (bccomp($value1, '27.64') === 0) {
echo 'Equal'; // Yes!
}
Funcs like bcadd(), bcmul(), bcdiv(). Pass strings, get precision. Set scale once.
5. When to bail on floats entirely
Fintech? BCMath or GMP. Science? Same. General? Stick to rounding + epsilon. Fixed-point libs exist, but overkill for most.
I've refactored carts with BC Math—deployed fearlessly. Integers for scale-up saved a payroll project.
Lessons from the trenches
Years back, a freelance gig: loyalty points system. Users earned 0.05 per action. Tallied wrong, rage-quit spike. Switched to cents + BC Math. Fixed.
Philosophy creeps in here. Code's like life—imperfect representations lead to suffering. We approximate, then adjust. PHP floats teach humility: test extremes, embrace tools.
What about you? That one bug that taught precision the hard way? Next project, round early, compare smart, scale when it counts.
In the quiet after the fix, code runs true, and you sleep sound—knowing the numbers won't betray you.