array_unique带有SORT_NUMBERIC行为

5

我发现了一些奇怪的东西,不明白它为什么会这样运作。

我有一个数字数组,里面所有的数字都是唯一的:

$array = [
    98602142989816970,
    98602142989816971,
    98602142989816980,
    98602142989816981,
    98602142989816982,
    98602142989816983,
    98602142989820095,
    98602142989820096,
    98602142989822060,
    98602142989822061,
];
var_dump($array);

array(10) {
  [0]=>
  int(98602142989816970)
  [1]=>
  int(98602142989816971)
  [2]=>
  int(98602142989816980)
  [3]=>
  int(98602142989816981)
  [4]=>
  int(98602142989816982)
  [5]=>
  int(98602142989816983)
  [6]=>
  int(98602142989820095)
  [7]=>
  int(98602142989820096)
  [8]=>
  int(98602142989822060)
  [9]=>
  int(98602142989822061)
}

如果我执行print_r(array_unique($array));,一切都很好,我会得到:

Array
(
    [0] => 98602142989816970
    [1] => 98602142989816971
    [2] => 98602142989816980
    [3] => 98602142989816981
    [4] => 98602142989816982
    [5] => 98602142989816983
    [6] => 98602142989820095
    [7] => 98602142989820096
    [8] => 98602142989822060
    [9] => 98602142989822061
)

但如果我添加SORT_NUMERIC标志 print_r(array_unique($array, SORT_NUMERIC));,我会得到:

Array
(
    [0] => 98602142989816970
    [6] => 98602142989820095
    [8] => 98602142989822060
)

为什么只返回这三个数字?

更新: 我正在64位系统上。

对于sort函数,我手动打乱了一些值,因为在原始数组中它们已经排序。

如果我执行sort($array);,则响应如预期:

Array
(
    [0] => 98602142989816970
    [1] => 98602142989816971
    [2] => 98602142989816980
    [3] => 98602142989816981
    [4] => 98602142989816982
    [5] => 98602142989816983
    [6] => 98602142989820095
    [7] => 98602142989820096
    [8] => 98602142989822060
    [9] => 98602142989822061
)

但是使用sort($array, SORT_NUMERIC);排序时,它们的顺序不正确:

Array
(
    [0] => 98602142989816970
    [1] => 98602142989816982
    [2] => 98602142989816983
    [3] => 98602142989816980
    [4] => 98602142989816981
    [5] => 98602142989816971
    [6] => 98602142989820095
    [7] => 98602142989820096
    [8] => 98602142989822060
    [9] => 98602142989822061
)

2
可能是因为您的值超出了整数范围,所以当您强制使用数字上下文时它们会被转换为浮点数……然后浮点数固有的不精确性开始发挥作用。您使用的是什么系统,32位还是64位? - CBroe
这些绝对不是整数,用gettype(var)找出来。 - Andrea Golin
1
在32位系统上,如果没有应用array_unique函数打印数组,输出的值可能是9.8602142989817E+169.8602142989817E+16等。如果你使用的是64位系统,这些整数本来就可以正确表示,但当SORT_NUMERIC函数被调用时,可能会出现问题——也许它再次强制使用32位或将其转换为浮点数内部处理等。 - CBroe
这绝对很有趣,我认为@CBroe的建议是正确的。如果您使用sort()而不是使用标志,它是否有效? - Mark Overton
如果将它们转换为字符串再进行比较呢? - Justinas
显示剩余2条评论
2个回答

5
你在这个规模上遇到了精度和浮点运算的问题。如果你有兴趣,可以在Is floating point math broken?找到更多信息,但我认为这并不完全属于那个问题的重复。
看一下你的前两个数字:
php > var_dump((float) 98602142989816970 === (float) 98602142989816971);
bool(true)

php > var_dump((float) 98602142989816970, (float) 98602142989816971);
float(9.8602142989817E+16)
float(9.8602142989817E+16)

当PHP使用SORT_NUMERIC比较数组中的值时,内部发生了什么事情,在numeric_compare_function中进行了深入的处理。

sort也存在相同的问题,请参见https://3v4l.org/02UUB。(显然,由于这种情况只在array_unique中才会发生值的删除-它们只是不能正确排序)

简而言之,对于这样大小的数字(或者特别是相对于其比例非常接近的数字),SORT_NUMERIC将不可靠。如果可以,请坚持将它们作为字符串进行比较。


0

代码运行在32位PHP或64位版本下会有所不同,因为整数的长度也是32位或64位。

$array = [
    98602142989816970,
    98602142989816971,
    98602142989816980,
    98602142989816981,
    98602142989816982,
    98602142989816983,
    98602142989820095,
    98602142989820096,
    98602142989822060,
    98602142989822061,
];
echo '<pre>';
var_dump(PHP_INT_MAX,$array);

32位系统的结果:
int(2147483647)
array(10) {
  [0]=>
  float(9.8602142989817E+16)
  [1]=>
  float(9.8602142989817E+16)
  [2]=>
  float(9.8602142989817E+16)
  [3]=>
  float(9.8602142989817E+16)
  [4]=>
  float(9.8602142989817E+16)
  [5]=>
  float(9.8602142989817E+16)
  [6]=>
  float(9.860214298982E+16)
  [7]=>
  float(9.860214298982E+16)
  [8]=>
  float(9.8602142989822E+16)
  [9]=>
  float(9.8602142989822E+16)
}

PHP会立即将这些值转换为浮点数,因为它们都大于PHP_INT_MAX。

64位系统的结果:

int(9223372036854775807)
array(10) {
  [0]=>
  int(98602142989816970)
  [1]=>
  int(98602142989816971)
  [2]=>
  int(98602142989816980)
  [3]=>
  int(98602142989816981)
  [4]=>
  int(98602142989816982)
  [5]=>
  int(98602142989816983)
  [6]=>
  int(98602142989820095)
  [7]=>
  int(98602142989820096)
  [8]=>
  int(98602142989822060)
  [9]=>
  int(98602142989822061)
}

在32位系统中,array_unique会减少数组的大小,因为一些值也超出了float的精度。

如果不使用SORT_NUMERIC选项,array_unique()和sort()在64位版本中可以正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接