Monday, February 27, 2012

PHP fun with numbers

An integer in PHP is from the set Z (..-2, -1, 0, 1, 2..). Typically on a 32-bit machine this will be 2^31 (signed int) which is equal to 2,147,483,648 (approx 2 billion). You can get the size of an int from the constant PHP_INT_SIZE, which on my machine = 4. There is another constant PHP_INT_MAX which tells you the max value given above (roughly 2 billion). In PHP any value above that limit is automatically a float value.
$x = PHP_INT_MAX; 
var_dump($x); // gives int(2147483647)

$y = PHP_INT_MAX + 1; 
var_dump($y); // gives float(2147483648)
If you attempt to cast a float to an integer and the value is within the bounds for an integer value then it will cast as expected (always rounded towards zero).
$x = 34.5;
$y = (int)$x;
var_dump($y); // gives int(34)
Now comes the funny business...If you attempt to cast a float to an int that is outside the boundaries for an integer value you get...well, officially an undefined output. With no warnings and no errors. In my experiments it is always zero.
$x = PHP_INT_MAX + 1;
$y = (int)$x;
var_dump($y); // gives 0. Nice...
Here's an even crazier one. The official docs recommend NEVER casting an unknown fraction to an int or you will suffer the consequences and hell shall rise through the earth and buildings will fall and all will perish. Or something similar.
$x = (0.1+0.7) * 10;
var_dump($x); // gives float(8). Okay.

$y = (int)$x;
var_dump($y); // gives int(7). WTF?!
And ALWAYS be careful when comparing floating point values. Because of the way a base10 float is stored internally on a machine in base2 you can get some quirky results.
$x = (0.1+0.7)*10;
$y = 8.0;
var_dump($x); // gives float(8)
var_dump($y); // gives float(8)
So they are equal right? Wrong.
echo($x == $y); // gives 0 (i.e. false)
$x is stored internally probably as something like 7.9999999999999991118. From php.net offical documentation

"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"

The workaround is to provide an epsilon value that provides an allowable difference for two floats to still be considered equal.
if (abs($x-$y)<0.00001) 
Also, as a footnote, converting a string to an int can also result in zero if the string is not an integer value.
$x = "peter";
$y = (int)$x;
var_dump($y); // gives int(0)
:)

1 comment: