为什么 JS 中 0.1 加 0.2 不等于 0.3

又是一个古老的话题。最近辞职在家看书,写了很多篇博客都是一些常见的面试题。嗯,作为一个稍微比较有追求的程序员,是不应该去死记硬背那些题目的。真正去理解这些题目,对实际开发也是很有帮助的。比如这篇要写的内容,因为之前公司是做电商的,所以经常会遇到这个问题。

首先要知道 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,M1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010E 为 -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

后记

本来以为半天时间就能搞定这个知识点,最后面花了大概两天的时间,还写了一个相互转换的工具。写完之后发现另外一篇文章也把这个过程介绍得很详细,感觉比我这篇写得好,所以就当作是整理和总结吧。

(完)

2018.08.03
Powered by Cubi,  Hosted by Coding Pages