绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
也许你知道 0.1 + 0.2 === 0.3 为 false,但是 1.1 + 0.2 === 1
2020-03-18 11:07:26

因吹斯挺

在浏览器调试窗口中输入下面两段代码,会发现一个因吹斯挺的现象:

console.log(0.1+0.2===0.3) // false
console.log(1.1+0.2===1.3) // true
复制代码

明明都是浮点数的加法,为什么表现出来的效果不一样呢?让我们一步步来揭晓谜底。

十进制转二进制

首先我们需要知道十进制是怎么转为二进制的,下面以 6.1 为例来进行说明。

整数部分

整数部分转为二进制如下图所示:

6 / 2 = 3...0  => 0
3 / 2 = 1...1  => 1
1 / 2 = 0...1  => 1

6 => 110
复制代码

也就是不断的将商除以二得到余数,直到商为0。

小数部分

小数部分转为二进制如下图所示:

0.1 * 2 = 0.2 => 0
0.2 * 2 = 0.4 => 0
0.4 * 2 = 0.8 => 0
0.8 * 2 = 1.6 => 1
0.6 * 2 = 1.2 => 1
0.2 * 2 = 0.4 => 0
…

0.1 => 000110011001100110011001100110011001100110011001100110011...
复制代码

不断的乘以二然后拿掉整数部分,直到积为0。

结合两部分,得到:

110.00011001100110011001100110011001100110011001100110011
复制代码

转化为科学计数法:

1.1000011001100110011001100110011001100110011001100110011×2^(2)
复制代码

浮点数在计算机中如何存储

双精度浮点数在计算机中存储原理如下图所示:

其中,sign 为 0 表示正数,为 1 表示负数,exponent 表示科学计数法中的指数部分,加上一个偏移值 1023,fraction 表示小数点后的部分,整数部分永远为 1,计算机不存储,但是运算的时候会加上。

下面推导下 6.1 的表示方法:

sign: 0
exponent: 2 + 1023 => 10000000001
fraction: 1000011001 1001100110 0110011001 1001100110 0110011001 10 011 (只能保留52位,多余部分向偶舍入)
       => 1000011001 1001100110 0110011001 1001100110 0110011001 10
复制代码

其中,向偶舍入可参考浮点数向偶数舍入的问题

浮点数加法

知道了浮点数的表示方法,下面我们来看看0.1+0.2的运算过程(方括号表示实际不存储的整数部分):

0.1 => 0 01111111011[1]1001100110011001100110011001100110011001100110011010
+
0.2 => 0 01111111100[1]1001100110011001100110011001100110011001100110011010

1. 对齐指数,小的往大的对齐。所以 0.1 指数部分加一,小数点需要往左移一位,超出部分向偶舍入
0.1 => 0 01111111100[0]1100110011001100110011001100110011001100110011001101 0
0.1 => 0 01111111100[0]1100110011001100110011001100110011001100110011001101

2. 小数部分相加
0.1 => 0 01111111100[0]1100110011001100110011001100110011001100110011001101
+
0.2 => 0 01111111100[1]1001100110011001100110011001100110011001100110011010
Res =>             [10]0110011001100110011001100110011001100110011001100111

3. 小数部分相加的结果超出了52位,小数点要左移一位,多余部分要向偶舍入
Res => 0 01111111101[1]0011001100110011001100110011001100110011001100110011 1
Res => 0 01111111101[1]0011001100110011001100110011001100110011001100110100

4. 推导 0.3 的表示
0.3 => 0 01111111101[1]0011001100110011001100110011001100110011001100110011
复制代码

显然,小数部分后四位是不相等的,并且通过对比我们可以知道 0.1+0.2 其实是大于 0.3 的。

下面继续推导 1.1+0.2 的运算过程:

1.1 => 0 01111111111[1]0001100110011001100110011001100110011001100110011010
+
0.2 => 0 01111111100[1]1001100110011001100110011001100110011001100110011010

1. 对齐指数,小的往大的对齐。所以 0.2 指数部分加三,小数点需要往左移三位,超出部分向偶舍入
0.2 => 0 01111111111[0]0011001100110011001100110011001100110011001100110011 010
0.2 => 0 01111111111[0]0011001100110011001100110011001100110011001100110011

2. 小数部分相加
1.1 => 0 01111111111[1]0001100110011001100110011001100110011001100110011010
+
0.2 => 0 01111111111[0]0011001100110011001100110011001100110011001100110011
Res => 0 01111111111[1]0100110011001100110011001100110011001100110011001101

3. 推导 1.3 的表示
1.3 => 0 01111111111[1]0100110011001100110011001100110011001100110011001101
复制代码

经过对比发现,两者确实是相等的。

问题

可以再提供一个例子吗?

通过观察我们发现,造成不相等的原因是因为小数部分超过52位长度的时候有向偶进位的过程,所以我们只要绕过这个过程就好了。比如,我们对 0.1+0.2 稍加改造,变成这样:

0 01111111011[1]0000000000000000000000000000000000000000000000000000
+
0 01111111100[1]0000000000000000000000000000000000000000000000000000

=>

0 01111111100[0]1000000000000000000000000000000000000000000000000000 0
+
0 01111111100[1]0000000000000000000000000000000000000000000000000000

=>

0 01111111100[0]1000000000000000000000000000000000000000000000000000
+
0 01111111100[1]0000000000000000000000000000000000000000000000000000
=
0 01111111100[1]1000000000000000000000000000000000000000000000000000
复制代码

 0.0625+0.125

更一般的,我们有 2^(-m) + 2^(-n)

分享好友

分享这个小栈给你的朋友们,一起进步吧。

JAVA玩具小屋
创建时间:2019-08-16 16:54:49
分享程序开发方面的小经验,思考一些比较简单易懂的技术问题
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • Yios5092
    栈主

小栈成员

查看更多
  • 栈栈
  • coyan
  • 25minutes
  • ?
戳我,来吐槽~