Press "Enter" to skip to content

PHP_INT_MIN and -9223372036854775808

While trying to decide what to write about over dinner just now, I suddenly remembered that a few days ago someone commented on Weibo about one of my PHP interview questions:

var_dump(PHP_INT_MIN);
var_dump(-9223372036854775808);

Is the output the same? If not, why?

I don't think this kind of question is appropriate as a PHP interview question. But as an interesting piece of trivia, let me talk through this little issue today.

We know that the representable range of a 64-bit integer is -9223372036854775808 to 9223372036854775807.

On a 64-bit system, PHP internally uses a signed 64-bit integer to represent IS_LONG, and there are two constants for these two values: PHP_INT_MIN and PHP_INT_MAX.

On a 64-bit platform, PHP_INT_MIN equals -9223372036854775808. So what is the question really getting at? Let's print it and see:

int(9223372036854775807)
float(-9.2233720368548E+18)

The output is indeed different. So what causes this discrepancy?

When the PHP compiler processes the input file, the way it handles a negative numeric literal is:

-{LNUM}
   => '-' expr { $$ = zend_ast_create(ZEND_AST_UNARY_MINUS, $2); }
   => UNARY_MINUS: 0 - expr 

That is, it first takes the number after the minus sign in as an integer, and then negates it.

And that is exactly what causes the problem. As I said at the start, the largest positive value a 64-bit integer can represent is 9223372036854775807. So when PHP processes -9223372036854775808, the 9223372036854775808 exceeds the largest positive value a 64-bit integer can represent; PHP has no way to store it in a signed 64-bit integer, so it automatically converts it to a DOUBLE. And from there...

Actually, in C, if you write code like the following:

int main() {
    long a = -9223372036854775808;

    return 0;
}

you should get a warning like this:

warning: integer constant is so large that it is unsigned

That is, if you write 9223372036854775808 directly, the C compiler will tell you the number is too large and that it would take an unsigned type to represent it; by default it converts it to unsigned long, and then negates it.

Of course, you can also force the issue by specifying UL to suppress the warning and assign directly. But more often — to explicitly flag that there's a pitfall here — people write it this way (for example, in Ubuntu's /usr/include/stdint.h):

#define INT64_MIN  (-__INT64_C(9223372036854775807)-1)

That is, using an expression instead of writing a literal directly. Correspondingly, we can write this in a PHP script:

$min = -9223372036854775807 - 1;

to sidestep the limitation, and then we can express PHP_INT_MIN normally. In fact, before PHP 7 — when PHP_INT_MIN wasn't defined — we were in the habit of writing it this way too, for instance in some test scripts in the PHP source (ext/date/tests/date_create-relative.phpt):

if (!defined('PHP_INT_MIN')) {
    define('PHP_INT_MIN', intval(-PHP_INT_MAX - 1));
}

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.