在PHP中,数组键(下标)只接受整数和字符串两种类型,但如果你传入一个浮点数,解释器不会抛出错误,而是会静默地把它转换为整数。这种隐式转换很容易让人误以为浮点数可以原样作为键使用,实际上并非如此。

一、浮点下标如何被转换为整数键
PHP数组本质上是有序映射,键的类型在底层强制为整型或字符串型。当你写入$arr[5.7] = 'value'时,PHP会对下标进行一次从浮点数到整数的转换,转换规则是直接截断小数部分,也就是向零取整,与(int)5.7的结果完全一致。因此$arr[5.7]最终存储的键是整数5,而不是浮点数5.7,也不是字符串"5.7"。
这一规则可以概括为:正数向下取整,负数向上取整,但更准确的说法是去掉小数点及之后的所有数字。例如1.9转换为1,-1.9转换为-1,2.0转换为2。下面的代码可以验证这个过程。
$arr = []; $arr[5.7] = 'A'; $arr[5.0] = 'B'; $arr[-5.7] = 'C'; var_dump($arr);
运行结果中只会出现三个键:5、5和-5。注意第二个写入会覆盖第一个键5的值,最终键5对应的值是B。这就是浮点下标最常见的坑:两个不同的浮点数因为截断后等于同一个整数而发生覆盖。
此外,如果浮点数来自计算表达式,由于二进制浮点数的精度限制,结果可能与你预期的小数不同。比如0.1 + 0.7在计算机内部并不是精确的0.8,而是接近0.8的浮点数,将其作为下标时截断结果仍然是0;但如果计算得到2.999999,截断后键为2而不是3。因此依赖浮点计算结果作为数组键时,一定要先手动取整。
二、字符串数字键与浮点键的转换差异
字符串键的转换规则比浮点数更复杂,也更容易与浮点键产生冲突。PHP会检查字符串键是否为合法的十进制整数。如果字符串内容是一个十进制整数,并且没有前导加号、没有小数点、没有指数部分,那么该字符串会被转换为整数键。例如$arr["8"]实际键是8,$arr["+8"]或者$arr["08"]则保持字符串键。
浮点键则没有这种“合法性检查”,任何浮点数都会被强制截断为整数。换句话说,$arr[8.0]会被转换为整数8,而$arr["8.0"]因为包含小数点,不会转换为整数,仍然保持字符串键。这意味着$arr[8.0]和$arr["8"]会写入同一个位置,而$arr["8.0"]是独立的位置。
$arr = []; $arr["8"] = '来自字符串整数'; $arr["8.0"] = '来自字符串小数'; $arr[8.0] = '来自浮点数'; var_dump($arr);
运行以上代码可以发现,最终数组中有两个键:整数8和字符串"8.0"。整数8的值被最后一次写入覆盖,变成“来自浮点数”;而"8.0"仍然保持第一次写入的值。这说明浮点下标会与整数键或可转换的字符串整数键发生冲突,但不会与包含小数点的字符串键冲突。理解这一点对排查数组元素意外丢失非常关键。
还有一个常见误区:$arr[1.0]与$arr[true]都会得到整数1,但$arr["1"]也会转换为整数1,三者写入同一个键。如果代码中混用了这些类型作为下标,就可能出现后写覆盖先写的情况,而且不会产生任何警告。
三、实战中的浮点下标陷阱与安全处理方案
在业务代码中,数组下标通常来自计算,例如根据金额分档、根据分数分桶、根据坐标生成格子编号。这时计算结果经常是浮点数。假设需要把分数按10分一档分组,$score = 59.9,如果直接写$group[$score / 10],下标的实际值是5.99,截断后为5,而不是四舍五入后的6。对于边界分数,这种差异会导致分组错误。
更隐蔽的是,当计算结果刚好是整数时,如$total = 30; $groupSize = 10;,$total / $groupSize得到3.0,浮点下标被转换为整数3,这看起来没问题;但如果后续把键写入时混用字符串"3",也会发生覆盖。而当计算结果为3.0000000000000004时,截断仍然是3,通常无影响;但如果为2.9999999999999996,键变成2,问题就出现了。由于浮点误差的方向不确定,这类bug很难复现。
要避免浮点下标造成的混乱,最直接的方法是在写入前显式转换为整数,并明确取整策略。如果业务要求四舍五入,使用round();如果要求向下取整,使用floor();如果要求向零取整,使用(int)或intval()。下面的示例展示如何安全地生成整数键。
$score = 59.9; $bucketIndex = (int) round($score / 10); $buckets[$bucketIndex] = $score; $total = 30; $groupSize = 10; $groupKey = (int) ($total / $groupSize); $groups[$groupKey][] = $total;
如果业务确实需要使用小数作为标识,那么不要直接把浮点数当作数组键,而应该把它转换为字符串后再作为键。例如使用$key = (string) $floatValue;,或者用sprintf('%.2f', $floatValue)格式化成稳定的字符串,避免浮点精度差异。格式化成字符串后,键的唯一性和可读性都能得到保证。
$price = 19.99;
$key = sprintf('%.2f', $price);
$priceMap[$key] = $price;
echo isset($priceMap['19.99']) ? '存在' : '不存在';
另外,在判断元素是否存在时也要注意,isset($arr[5.9])等价于isset($arr[5]),因为键在底层已经转换。如果数组中有键5,判断浮点下标5.9也为真,可能造成逻辑误判。统一键的生成规则,避免在同一数组混用整数、浮点数和字符串数字键,是保持数据完整性的关键。