有人可以解释最近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个转储没问题。第三个转储给出了预期的结果,但是为什么最后两个转储给出一个负值并且为零?
答案 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
而不是9223372036854800000
或9223372036854775808
。
由于1
等较低的数字超出了该数字的有效数字,我认为9.2233720368548E+18 + 1
仍为9.2233720368548E+18
。这也意味着9.2233720368548E+18 + 1 - 9.2233720368548E+18
为0
,因为+1
只是没有任何效果。
我认为这实际上是两种情况的解释。
奇怪的是,您通常不必知道任何这些实现细节,特别是在像PHP这样的脚本语言中,但在这样的测试中,它们可能无论如何都会出现,有时会导致奇怪的行为。
另外,您可能需要阅读The PHP floating point precision is wrong by default。它包含了使用浮点数可能发生的其他舍入错误的一个很好的解释,它将使您更深入地了解浮点数在一般情况下以及在PHP中的工作方式。
答案 2 :(得分:0)
这与计算机如何存储数字有关。在这种情况下,带符号的数字,意味着它们都可以是负数和正数。
为了它,我将使用一个8位的例子。计算机可以存储从-128
到127
的号码。
-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。
希望这能澄清一些。