1
votes

I'm adding together two numerical strings $a and $b and then comparing the result against another numerical string $c. All three numbers are stored as strings, and being converted to floats by PHP at the comparison step.

For some reason, the test $a+$b == $c does not evaluate as true, even though it should.

You can recreate the problem with this script:

<?php
$a = "-111.11";
$b = "-22.22";
$c = "-133.33";

echo '$a is '.$a."\n";
echo '$b is '.$b."\n";
echo '$c is '.$c."\n";
echo '$a + $b is '.($a+$b). "\n";

if ($a + $b == $c) {
    echo 'a + b equals c'."\n";
} else {
    echo 'a + b does not equal c'."\n"; 
}
?>

Weirdly, if I change the values slightly so that $a=-111.11, $b=-22.23 and $c=-133.34 it works as expected.

Am I missing something obvious, or is this a bug with PHP?

3
Floats have limited precision. You lose exactness as soon as you introduce the first float. Comparisons therefore won't work.mario
possible duplicate of PHP Math Precisionmario
Get it to print out the values as floats, I suspect you'll find one of them is not exactly what you thought. ususally the difference is very small Abs((a + b) - c) < 0.01, usually does the trickTony Hopkinson

3 Answers

2
votes

You're running into a limitation of floating point arithmetic. Just as there are certain numbers you can't represent exactly in decimal (1/3 for instance), so there are certain numbers you can't represent exactly in floating point binary.

You should never try and compare floating point numbers for equality, as the limitations of floating point make it unlikely that the variables you're comparing have an actual value that matches exactly the value you think they have. You need to add a "fudge factor", that is if the two numbers are similar to within a certain tolerance, then you should consider them to be equal.

You can do this by subtracting one number from another and seeing if the absolute result is below your threshold (in my example, 0.01):

if (abs ($someFloatingPointNumber - $someOtherFloatingPointNumber) <= 0.01)
{
    // The values are close enough to be considered equal
}

Of course, this combined with rounding errors that can creep in with successive mathematical operations mean that floating point numbers are often not the best choice anyway, and should be avoided where possible. For example, if you're dealing with currency, store your values as integers in the minor unit (pennies for GBP, cents for USD, etc), and only convert to the major unit by dividing by 100 for display.

5
votes

From the large red box on this page: http://php.net/manual/en/language.types.float.php

never compare floating point numbers for equality.

Basically, you're not getting the correct numbers, because they are saved in a slightly different format, so when you compare, it gets screwed.

That link of @Corbin is really good, So I'm adding it just for the love :)
http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

What Every Computer Scientist Should Know About Floating-Point Arithmetic

This paper presents a tutorial on those aspects of floating-point that have a direct impact on designers of computer systems. It begins with background on floating-point representation and rounding error, continues with a discussion of the IEEE floating-point standard, and concludes with numerous examples of how computer builders can better support floating-point.

1
votes

Do your number have always two decimal positions?

If so, you can try this:

$aDec = round($a * 100);
$bDec = round($b * 100);
$cDec = round($c * 100);

if ($aDec + $bDec == $cDec) {
    ...
}