越过PHP中的整数限制

时间:2014-01-26 12:54:47

标签: php math integer integer-overflow

有人可以解释最近2起案件的情况吗?

$x=PHP_INT_MAX;
var_dump($x);            // int(9223372036854775807)        no problem
var_dump($x+1);          // float(9.2233720368548E+18)      value is cast to float still no problem
var_dump($x+1-1);        // float(9.2233720368548E+18)      still okay
var_dump((int)($x+1-1)); // int(-9223372036854775808)       negative value?!!
var_dump($x+1-$x);       // float(0)                        zero?!!!!!!!!!!!

根据PHP manual

  
      
  • PHP_INT_MAX是最大整数。
  •   
  • 如果PHP遇到超出整数类型边界的数字,则会将其解释为float。另外,一个操作   超出整数类型边界的数字将返回   而不是浮动。
  •   

所以前2个转储没问题。第三个转储给出了预期的结果,但是为什么最后两个转储给出一个负值并且为零?

3 个答案:

答案 0 :(得分:1)

两种不同的组合:浮点舍入和整数溢出。

var_dump($x+1-$x);       // float(0)                        zero?!!!!!!!!!!!

这是因为floating point算术不精确(它不可能,因为实数可以是无穷大的)。 $x$x+1非常接近,它们会四舍五入到相同的浮点值,因此float($x+1)==float($x)。现在你知道为什么$x+1-$x==0

结合整数溢出,您得到以下结果:

var_dump((int)($x+1-1)); // int(-9223372036854775808)       negative value?!!

由于上述原因$x+1-1==float(9223372036854775808)。将此转换为int overflows并变为负值。

9223372036854775808==2^63,其中64位有符号整数变为-2^63

答案 1 :(得分:0)

关于第4次测试: 整数已签名。在内部,它们以这样的方式存储,当你添加一个时,值会换行并变成最低的负整数。如果是4位有符号半字节,则看起来像:

0111 (=7) + 0001 (=1) = 1000 (= -7)

这部分解释了一个数字如何突然变成一个非常大的负数。

但是你会期望x + 1-1是x,但它可能是有效数字的问题。 PHP只存储14位有效数字,这不足以完全存储9223372036854775807之类的数字而不会丢失信息。

我将解释使用第5次测试:

浮点数可能包含比整数大得多的数字,但只包含有限数量的有效数字。这就是为什么它看起来像9.2233720368548E+18而不是92233720368548000009223372036854775808

由于1等较低的数字超出了该数字的有效数字,我认为9.2233720368548E+18 + 1仍为9.2233720368548E+18。这也意味着9.2233720368548E+18 + 1 - 9.2233720368548E+180,因为+1只是没有任何效果。

我认为这实际上是两种情况的解释。

奇怪的是,您通常不必知道任何这些实现​​细节,特别是在像PHP这样的脚本语言中,但在这样的测试中,它们可能无论如何都会出现,有时会导致奇怪的行为。

另外,您可能需要阅读The PHP floating point precision is wrong by default。它包含了使用浮点数可能发生的其他舍入错误的一个很好的解释,它将使您更深入地了解浮点数在一般情况下以及在PHP中的工作方式。

答案 2 :(得分:0)

这与计算机如何存储数字有关。在这种情况下,带符号的数字,意味着它们都可以是负数和正数。

为了它,我将使用一个8位的例子。计算机可以存储从-128127的号码。 -128表示为1000 0000,而127表示为0111 1111。第一位表示数字是负数还是正数。每一位都值2^nn是从右到左的位置。对于127,它是2^0 + 2^1 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 + 2^7 = 127.相反的负值。

var_dump($x);            // int(9223372036854775807)        no problem

没问题,因为我们只是将其设置为最大值。

var_dump($x+1);          // float(9.2233720368548E+18)      value is cast to float still no problem
var_dump($x+1-1);        // float(9.2233720368548E+18)      still okay

这里有问题,+1和+ 1-1等于同一件事。他们不应该这样,问题确实发生在这里,更加微妙。

var_dump((int)($x+1-1)); // int(-9223372036854775808)       negative value?!!

在这种情况下,0111 (...) +1等于1000 (...)这是绝对值,请注意数字末尾的8,而正值则为7。 设置CPU中的溢出标志。

var_dump($x+1-$x);       // float(0)                        zero?!!!!!!!!!!!

$ x + 1强制转换为浮动,然后减去相同的值(因为浮点数的舍入错误),$ x + 1与$ x相同,所以$ x- $ x = 0。

希望这能澄清一些。