因吹斯挺
在浏览器调试窗口中输入下面两段代码,会发现一个因吹斯挺的现象:
1 | console.log(0.1 + 0.2 === 0.3) // false |
明明都是浮点数的加法,为什么表现出来的效果不一样呢?让我们一步步来揭晓谜底。
十进制转二进制
首先我们需要知道十进制是怎么转为二进制的,下面以 6.1 为例来进行说明。
整数部分
整数部分转为二进制如下图所示:
1 | 6 / 2 = 3...0 => 0 |
也就是不断的将商除以二得到余数,直到商为 0。
小数部分
小数部分转为二进制如下图所示:
1 | 0.1 * 2 = 0.2 => 0 |
不断的乘以二然后拿掉整数部分,直到积为 0。
结合两部分,得到:
1 | 110.00011001100110011001100110011001100110011001100110011 |
转化为科学计数法:
1 | 1.1000011001100110011001100110011001100110011001100110011×2^(2) |
浮点数在计算机中如何存储
双精度浮点数在计算机中存储原理如下图所示:
其中,sign
为 0 表示正数,为 1 表示负数,exponent
表示科学计数法中的指数部分,加上一个偏移值 1023,fraction
表示小数点后的部分,整数部分永远为 1,计算机不存储,但是运算的时候会加上。
下面推导下 6.1 的表示方法:
1 | sign: 0 |
其中,向偶舍入可参考浮点数向偶数舍入的问题
浮点数加法
知道了浮点数的表示方法,下面我们来看看0.1+0.2
的运算过程(方括号表示实际不存储的整数部分):
1 | 0.1 => 0 01111111011[1]1001100110011001100110011001100110011001100110011010 |
显然,小数部分最后四位是不相等的,并且通过对比我们可以知道 0.1+0.2 其实是大于 0.3 的。
下面继续推导 1.1+0.2
的运算过程:
1 | 1.1 => 0 01111111111[1]0001100110011001100110011001100110011001100110011010 |
经过对比发现,两者确实是相等的。
问题
可以再提供一个例子吗?
通过观察我们发现,造成不相等的原因是因为小数部分超过 52 位长度的时候有向偶进位的过程,所以我们只要绕过这个过程就好了。比如,我们对 0.1+0.2
稍加改造,变成这样:
1 | 0 01111111011[1]0000000000000000000000000000000000000000000000000000 |
即 0.0625+0.125
。
更一般的,我们有 2^(-m) + 2^(-n)
。
附录
提供一段 c 语言代码,用来获取 double 型数据在内存中的表示:
1 |
|