【问题标题】:Crossing the integer limits in PHP跨越 PHP 中的整数限制
【发布时间】:2014-01-26 12:54:47
【问题描述】:

有人能解释一下最后两个案例的情况吗?

$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 遇到超出整数类型范围的数字,它将被解释为浮点数。此外,一个操作 结果超出整数类型范围的数字将返回 一个浮点数。

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

【问题讨论】:

  • 这与计算机存储数字的方式有关。特别是负数和浮点数。

标签: php math integer integer-overflow


【解决方案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

【讨论】:

    【解决方案2】:

    关于第四次测试: 整数是有符号的。在内部,它们以这样的方式存储,当你加一个时,值会换行并变成最小的负整数。如果是 4 位有符号半字节,则如下所示:

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

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

    但是您会认为 x+1-1 是 x,但这可能是有效数字的问题。 PHP 只存储 14 位有效数字,这不足以完全存储像 9223372036854775807 这样的数字而不会丢失信息。

    我会用第五个测试来解释:

    浮点可以包含比整数大得多的数字,但有效数字的数量有限。这就是为什么它看起来像 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 中的工作原理。

    【讨论】:

      【解决方案3】:

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

      为此,我将使用一个 8 位示例。计算机可以存储从-128127 的号码。 -128 表示为 1000 0000,而 127 表示为 0111 1111。第一位表示数字是负数还是正数。每一点都值2^n,分别是n是从右到左的位置。对于 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。

      希望这能澄清一些。

      【讨论】:

      • 你的整数溢出解释是正确的,但你没有正确解释var_dump($x+1-$x); // float(0)。在这种情况下,浮点数不会溢出。
      猜你喜欢
      • 2023-03-25
      • 2014-12-11
      • 2015-01-25
      • 1970-01-01
      • 1970-01-01
      • 2020-05-25
      • 2018-04-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多