浏览器中的浮点,第1部分:不可能的期望

2020-09-30 01:08:12

几年前,我做了很多关于浮点数学的思考和写作。这很有趣,我在这个过程中学到了很多,但有时我很长一段时间都没有真正使用那些来之不易的知识。因此,当我最终处理一个需要一些专业知识的bug时,我总是非常高兴。下面是我在Chromium中调查过的(至少)三个浮点错误故事中的第一个。这是一个短的。

错误的标题是“JSON解析64位整数不正确”,听起来不像是浮点或浏览器问题,但它是在crbug.com上提交的,我被要求查看一下。Repro最简单的版本是打开Chrome开发人员工具(F12或Ctrl+Shift+I),并将以下代码粘贴到开发人员控制台:

Json=JSON.parse(‘{“x”:2940078943461317278}’);alert(json[‘x’]);

将未知代码粘贴到控制台窗口是获得pwn的好方法,但此代码足够简单,我可以断定它不是恶意的。Bug报告很好,包含了作者的期望和实际结果:

预期的行为是什么?应返回整数2940078943461317278。哪里出了问题?取而代之的是返回整数2940078943461317000。

这个“bug”实际上是在Linux上报告的,我在Chrome for Windows上工作,但它的行为是跨平台的,我有一些浮点方面的专业知识,所以我进行了调查。

整数的这种行为是一个潜在的“浮点”错误的原因是因为JavaScript实际上没有整数类型。这也是这实际上不是bug的原因。

输入的数字相当大。大约是2.9E18。这就是问题所在。因为JavaScript没有整数类型,所以它的数字使用IEEE-754浮点双精度。这个二进制浮点格式有一个符号位、一个11位指数和一个53位尾数(是的,也就是65位,隐藏的隐含的尾数是魔术的)。这种双精度类型可以很好地存储整数,以至于许多JavaScript程序员从来没有注意到没有整数类型,但是非常大的数字打破了这种错觉。

JavaScript数字可以精确存储最大2^53的任何整数。此后,它可以容纳最多2^54的所有偶数。在此之后,它可以容纳所有4的倍数,直到2^55,依此类推。

用二进制科学记数法表示的有问题的数字,大约是1.275*2^61。在这个范围内,可以表示的整数很少--可表示的数字之间的差距是512。以下是三个相关数字:

有问题的数字用两个双精度括起来,JSON模块(如JavaScript本身或任何其他正确实现的文本到双精度转换函数)尽其所能,返回最接近的双精度。需要明确的是,bug文件管理器希望存储的数字不能存储在内置的JavaScript numeric类型中。

到目前一切尚好。如果你突破了语言的极限,你需要更多地了解它是如何工作的。但还有一个未解之谜。Bug报告说,返回的号码实际上是这个号码:

这很奇怪,因为它不是输入数,也不是最接近的双精度数,事实上,它甚至不是可以表示为双精度数的数字!

JavaScript规范也解释了这个谜团。该规范规定,当打印一个数字时,实现应该打印足够多的数字来唯一标识它,然后就不能再打印了。当打印不能精确表示为双精度数的0.1这样的数字时,这很方便。例如,如果JavaScript要求将0.1打印为存储值,则必须打印:

这将是准确的,但它只会让人们感到困惑,而不会增加任何价值。确切的规则可以在这里找到(搜索“ToString Apply to the Number Type”)。我不认为他们实际上需要尾随零,但他们肯定允许这样做。

将原始数字存储为JavaScript数字时,原始数字的值会丢失

此条目发布在“计算机和互联网”、“浮点”和“标记的JavaScript”、“Precision”上。为固定链接添加书签。