Frank DENIS random thoughts.

PHP : notes about integers

Here’s a classical scenario. You get an identifier as $_POST[‘id’] and you need to check that the value is actually a PHP integer value, that has just been converted into a string because everything becomes a string in the $_POST[] array.

is_int() obviously doesn’t work, as $_POST[‘id’] is a string.

is_numeric() is also plenty wrong. is_numeric() is not designed to check whether the string contains only digits, neither it is designed to check whether it is something that would fit into a PHP integer value.

PHP’s is_numeric() relies on Zend Engine’s is_numeric_string() function. A great deal of PHP odd behaviors depend on that internal function, like those described in that article.

Here’s what is_numeric() actually does :

  • it skips leading spaces, tabs and \r, \n, \t, \v and \f characters.
  • it then skips any leading + or -, but it then bugs out if the first characters after the spaces are “0x” or “0X”.
  • if there’s no + or -, but “0x” or “0X”, it understands that the rest should be hexadecimal digits.
  • it then skips leading zeros.
  • if it’s not in hex mode, it looks for ‘.’, ‘e’, ‘E’ and ‘+’ or ‘-‘ after the ‘e’ and ‘E’. If a ‘.’ is found, it understands that it is in a floating-point number context.
  • by default, it is in “integer mode”. But depending on the compiler, if more than 10 or 19 digits are found, it compares subtrings in order to eventually switch to the floating-point mode.

Don’t rely on is_numeric() if what you actually want is to check whether a string contains something like “8928”, ie. a pure PHP integer, casted as a string. is_numeric() is designed to return TRUE if the string looks like a constant, regardless of the base and the type.


is_numeric("4E2") = TRUE
is_numeric("\r\n\r\n\t\f0X0") = TRUE
is_numeric("     0xDeadBeef") = TRUE
is_numeric(str_repeat("9", 9999)) = TRUE     (way out of bounds for a PHP integer object)

If you want to check that a string contains a casted integer, here’s a way to do it:


if ($v === (string) (int) $v) { ... }

Also, don’t forget that integer objects have minimal and maximal values in PHP. Actually, the limits are the same as the one of the “signed long” type of your compiler. Unlike Ruby that automatically switches to big (infinite) numbers, if there’s an arithmetic overflow with PHP, the result is undefined. Since integers are always signed within PHP, the result is really undefined.

Casting a string into an integer can obviously give very different results: <pre> $a = "10293847569"; if (is_numeric($a)) { $b = (int) $a; $c = (int) 2147483648; echo "[$a] != [$b] != [$c]\n"; } </pre>

Sample result:


[10293847569] != [2147483647] != [1703912977]

The value you get for $b the upper limit of an integer value. If you application mixes types in order to reach a single attribute, this can be the root of weird bugs.

In order to know the upper and lower limits of integer values, PHP provides two constants : PHP_INT_MAX and PHP_INT_LOW. So, before multiplying two numbers, you can check whether an overflow would occur that way:


if (PHP_INT_MAX / $a < $b) {
  throw new Exception("Arithmetic overflow");
}
$c = $a * $b;