又是一个古老的话题。最近辞职在家看书,写了很多篇博客都是一些常见的面试题。嗯,作为一个稍微比较有追求的程序员,是不应该去死记硬背那些题目的。真正去理解这些题目,对实际开发也是很有帮助的。比如这篇要写的内容,因为之前公司是做电商的,所以经常会遇到这个问题。
首先要知道 JS 的数值表示规则。JS 中只有 Number 这一数值类型,整数和小数都是统一用这种类型去表示的。而 Number 用的是 IEEE 754 64 位双精度编码,所以实际上采用 754 这个标准的语言都存在这个问题。
具体标准的内容可以看看上面的链接,这里再推荐两个还不错的教程
看完之后,基本就知道怎样把一个数用 IEEE 754 去表示了。然后来看下题目中的两个小数,随便找个 JS 环境,运行 0.1 + 0.2
,可以得到下面的结果。
0.30000000000000004
下面就来分析下这个看起来很奇怪的数字是怎么来的。先把 0.1 换成二进制表示
0.0 0011 0011 0011 ...
为了直观一点,用空格区分开每组重复的数字。这里已经可以看到 0.1 用二进制表示的位数是无限的,所以是没办法把结果完整地保存下来。IEEE 754 会把这种情况做下取整,就得到下面这个 64 位的结果。
0-01111111011-1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010
上面的 -
和空格这里只是为了显示得直观一点。可能会有疑问为什么最后 4 位是 1010
不是 1001
?这是 IEEE 754 的取整规则,如果溢出的第一位是 1
,则前面的数要加上 1。比如 10001
取整 4 位就变成 1001
。
按照上面的规则转换下 0.2
0-01111111100-1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010
对于这种形式的两个数要怎样相加呢?一开始我是直接把每个二进制数相加,算了好久发现果然不对。后面看到了一个教程
实际上当要做计算的时候,需要把二进制用下面的形式表示
对于 0.1,对应的 S
为 0,M
为 1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010
,E
为 -4。对于 0.2,则为 0, 1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010
,-3。转换之后就可以直接做运算了,把得到的结果再转成 IEEE 754 标准的形式
0-01111111101-0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0100
转成十进制的话,就是 0.30000000000000004
。
本来以为半天时间就能搞定这个知识点,最后面花了大概两天的时间,还写了一个相互转换的工具。写完之后发现另外一篇文章也把这个过程介绍得很详细,感觉比我这篇写得好,所以就当作是整理和总结吧。
(完)