问题 为什么PHP和JavaScript在处理八进制和十六进制数时有问题?


我注意到PHP和JavaScript处理八进制和十六进制数字时遇到一些困难,同时键入juggling和cast:

PHP:

echo 16 == '0x10' ? 'true' : 'false'; //true, as expected
echo 8  == '010'  ? 'true' : 'false'; //false, o_O

echo (int)'0x10';    //0, o_O
echo intval('0x10'); //0, o_O
echo (int)'010';     //10, o_O
echo intval('010');  //10, o_O

JavaScript的:

console.log(16 == '0x10' ? 'true' : 'false'); //true, as expected
console.log(8  == '010'  ? 'true' : 'false'); //false, o_O

console.log(parseInt('0x10')); //16, as expected
console.log(parseInt('010'));  //8, as expected
console.log(Number('0x10'));   //16, as expected
console.log(Number('010'));    //10, o_O

我知道PHP有 octdec() 和 hexdec() 修复八进制/十六进制不良行为的功能,但我希望能够解决这个问题 intval() 处理八进制和十六进制数字就像JavaScript一样 parseInt() 确实。

无论如何,这种奇怪行为背后的理由是什么?


1052
2017-11-24 19:43


起源

不是那样的 0o10? - Madara Uchiha♦
@Truth:不,PHP和JS中的AFAIK八进制表示法只是一个零前缀,而不是 0o 字首。 - BoltClock♦
打印最后4个PHP行 0, 0, 10 和 10 在我的盒子上,似乎用以下方式划过: php.net/manual/en/... - Bart Kiers
不,PHP和JavaScript都不了解 0o10 符号。 - mingos
@Bart:是的,手册的那一部分包括科学记数法和浮点数。我对八进制和十六进制感兴趣,这看起来似乎没有覆盖任何我看过的地方。 - mingos


答案:


想象一下有人指明 035 作为一些产品的购买数量(领先 0 仅用于填充,因此它匹配列表中的其他三位数量)。 035 显然有望被解释为 35 对于非程序员。但是如果PHP要在字符串中解释八进制数字,结果会突然变成 29 => WTF?!?另一方面,十六进制表示法不是问题,因为人们通常不使用a来指定数字 0x23 符号。

顺便说一下,这不仅发生在最终用户身上,也发生在程序员身上。经常程序员试图用前导零填充他们的数字 - 呵呵,一切都错了!这就是为什么JS不再允许在严格模式下使用八进制表示法而其他语言使用更明确的表示法 0o 字首。

顺便说一句,我确实同意这种行为是不一致的。在我看来,十六进制表示法也不应该被解析。就像八进制和二进制表示法一样。特别是考虑到明确的 (int) cast也不解析十六进制,而只是读取所有内容到第一个非数字。


解决问题 intval 例如,它实际上就像记录的那样: intval 不是用于解析PHP的本机整数表示法,而是用于解析 指定基数的整数。如果你看一下 文档,你会发现它需要第二个参数 $base 默认为 10。 (该 (int) 通过内部方式转换为相同的方式 convert_to_long_base 打电话给 base = 10,所以它总是表现得很像 intval。)


9
2017-11-24 19:59



我当然可以想象那没关系。 0644 == '0644' 但是直觉上对我来说也是显而易见的。编辑:好的,我看到你编辑了答案。好吧,我会决定不允许所有非小数点播作为一致但尽管不舒服的行为。但它不一致,我试图理解它... - mingos
@mingos请为了您自己的利益,不要试图理解它。 PHP是一种“有机种植”产品。大多数古怪的行为都没有理由,这就是事情的演变方式。 - NikiC
好的,JavaScript parseInt() 还为基础采用可选的第二参数。从@Esailija对ECMA标准的评论来看,两种语言似乎都是如此 意 以相同的方式工作:除非特别指示,否则将字符串解释为十进制整数... - mingos


在javascript中,只有十进制和十六进制被定义为标准的一部分,而八进制是依赖于实现的,这可以解释为什么八进制解析在您给出的示例之间不一致。

您可以在严格模式下删除八进制文字,但在我测试的所有浏览器中, parseInt 仍然试图解析八进制而不是十进制。这有点奇怪,因为规范没有说任何关于试图解释隐含八进制的东西 parseInt 并且在严格模式下明确禁止八进制扩展。所以没有八进制文字,没有关于试图转向的规范 "010" 进入八进制时 parseInt'd,即使在严格模式下,行为仍然存在。

所以 Number("012") === 12 是正确的 parseInt("012") === 10 根据我对您可以阅读的规范的解释,这是不正确的 这里

但十六进制有一个很好的理由,它使位数上的数字操作更容易。如果他不是指十六进制,那么“0xFF”不是某人输入的东西。


3
2017-11-24 20:11



好的,这可以解释JavaScript部分。不知道PHP是否一样? - mingos
@mingos,我不知道,我决定不在PHP野兽身上挥杆:D。也许他们的规格发生了变化,他们必须留下遗留的东西,以便遗产程序在升级时不会破坏?这只是一个猜测。 - Esailija


没有读过其他答案,但至少在PHP中,八进制或十六进制数没有问题;你做错了

"0x12" // String with content "0x12"
0x12 // Integer "18"
010 // integer "8"

将字符串转换为整数将...是的,将其转换为整数,就像PHP总是这样:它将取任何数字并形成整数,直到它找到任何非数字字符。在这种情况下它唯一 0

hexdec() 适用于字符串,但此字符串只有十六进制而没有前缀 0x

echo hexdec('A0`); // 16

前缀 0 (八进制)和 0x (十六进制)用于区分不同的整数表示法,但只要您将其写为字符串,PHP就会将其视为字符串。

我假设你用javascript做了类似的错误。


1
2017-11-24 20:25



好的,有道理...... - mingos
我认为OP很清楚他正在使用字符串。问题是为什么在某些情况下解释十六进制字符串而在其他情况下不解释为什么不解析oct的原因。 - NikiC
@NikiC:字符串总是被“解释”(意思是:“铸造”),但可能不是OP期望它的方式。 - KingCrunch
@KingCrunch总是,但是以不同的,不一致的方式。 - NikiC