Skip to content
Advertisement

PHP inconsistent var_dump, wrong floor result with a float calulation

For this code:

$value = 200.1;
$denominator = 0.1;
echo "value: $valuen";
echo "denominator: $denominatorn";

$resultInt = ($value / $denominator);
echo "($value / $denominator) = ";
printf ("%fn", $resultInt);

$resultInt = (int) ($value / $denominator);
echo "(int) ($value / $denominator) = ";
printf ("%fn", $resultInt);

$resultInt = floor($value / $denominator);
echo "floor($value / $denominator) = ";
printf ("%fn", $resultInt);

$resultInt = floor((int) ($value / $denominator));
echo "floor((int) ($value / $denominator)) = ";
printf ("%fn", $resultInt);

$resultInt = floor((float) 2001);
echo "floor((float) 2001) = ";
printf ("%fn", $resultInt);

$resultInt = round($value / $denominator, PHP_ROUND_HALF_DOWN);
echo "round($value / $denominator, PHP_ROUND_HALF_DOWN) = ";
printf ("%fn", $resultInt);

$valueMul = $resultInt * $denominator;
if ($valueMul !== $value) {
    echo "they are not the samen";
    var_dump($value);
    var_dump($valueMul);
}

$valueDiff = $value - $valueMul;
if ($valueDiff !== 0) {
    echo "valueDiff is not zeron";
    var_dump($valueDiff);
}

I have this result:

value: 200.1
denominator: 0.1
(200.1 / 0.1) = 2001.000000
(int) (200.1 / 0.1) = 2000.000000
floor(200.1 / 0.1) = 2000.000000
floor((int) (200.1 / 0.1)) = 2000.000000
floor((float) 2001) = 2001.000000
round(200.1 / 0.1, PHP_ROUND_HALF_DOWN) = 2001.000000
they are not the same
float(200.1)
float(200.1)
valueDiff is not zero
float(-2.8421709430404E-14)

The expected result for all calculations above is 2001, but in some cases it is 2000.

I am aware about fractions not being stored in variables as decimals due to the fact the computer operates on binaries.

But as manual of floor states:

floor — Round fractions down

therefore I would expect the same result as with:

round(200.1 / 0.1, PHP_ROUND_HALF_DOWN)

but round returns 2001 as expected and floor returns 2000 (wrong).

Do you know why?

I also see that var_debug is inconsistent:

for the code:

$resultInt = round($value / $denominator, PHP_ROUND_HALF_DOWN);
echo "round($value / $denominator, PHP_ROUND_HALF_DOWN) = ";
printf ("%fn", $resultInt);

$valueMul = $resultInt * $denominator;
if ($valueMul !== $value) {
    echo "they are not the samen";
    var_dump($value);
    var_dump($valueMul);
}

it displays both values as the same (but they aren’t):

they are not the same
float(200.1)
float(200.1)

but for diff of them

$valueDiff = $value - $valueMul;
if ($valueDiff !== 0) {
    echo "valueDiff is not zeron";
    var_dump($valueDiff);
}

it prints non zero value

So in one case it prints both values as the same but a result of subtracting one from the other is not printed as a zero – why?

What function one can use in PHP to always see the true value of a float or an integer and not its fussy-decimal representation? I mean for the output:

they are not the same
float(200.1)
float(200.1)

I should see different values, not the same.

And what is a function to print decimal representation of floats in consistent manner? I mean if in the example

they are not the same
float(200.1)
float(200.1)

floats are printed exactly the same (even if they aren’t in fact) then in the example:

valueDiff is not zero
float(-2.8421709430404E-14)

the same function should print 0 consequently.

Advertisement

Answer

You’re running into precision issues. You should never trust floating point numbers to the last digit in PHP.

From the manual (Floating Point Numbers), there’s even a point about your specific test numbers in the second paragraph:

Floating point numbers have limited precision. Although it depends on the system, PHP typically uses the IEEE 754 double precision format, which will give a maximum relative error due to rounding in the order of 1.11e-16. Non elementary arithmetic operations may give larger errors, and, of course, error propagation must be considered when several operations are compounded.

Additionally, rational numbers that are exactly representable as floating point numbers in base 10, like 0.1 or 0.7, do not have an exact representation as floating point numbers in base 2, which is used internally, no matter the size of the mantissa. Hence, they cannot be converted into their internal binary counterparts without a small loss of precision. This can lead to confusing results: for example, floor((0.1+0.7)*10) will usually return 7 instead of the expected 8, since the internal representation will be something like 7.9999999999999991118….

So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available.

For a “simple” explanation, see the » floating point guide that’s also titled “Why don’t my numbers add up?”

Try using the BC Math or gmp functions if you need that kind of precision.

And here’s a relevant page from the floating point guide referenced in the manual: https://floating-point-gui.de/languages/php/

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement