ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

为什么0.1+0.2 !== 0.3,而 0.1+0.3 === 0.4

2020-11-04 15:35:14  阅读:902  来源: 互联网

标签:Exponent 0.1 0.3 0.2 二进制 浮点数


最近看了一本书《代码之髓》,里面提到浮点数在计算机的存储方式——IEEE 754 会引起浮点数的精度丢失问题。这让我想起了“著名”的 JS 问题:为什么 0.1 + 0.2 !== 0.3 ?

迷迷糊糊的就记得是浮点数精度丢失原因造成的,但问到具体是怎么回事儿就傻眼了。

今天就尝试用基本知识来推理下。

十进制浮点数转二进制

众所周知,所有数据都是以二进制形式保存在计算机中的。浮点数如何转化为二进制数呢?

  • 整数部分:除以2,取出余数,商继续除以2,直到得到0为止,将取出的余数逆序。
  • 小数部分:乘以2,然后取出整数部分,将剩下的小数部分继续乘以2,然后再取整数部分,一直取到小数部分为零为止。如果永远不为零,则按要求保留足够位数的小数,最后一位做0舍1入。将取出的整数顺序排列。

譬如对于 8.75, 转二进制计算过程如下:

8/2:4 余 0,
4/2:2 余 0,
2/2:1 余 0,
1/2:0 余 1
所以 8 的二进制为 1000

0.75*2 = 1.5,取整 1,小数部分为 0.5,
0.5*2 = 1.0,取整 1,小数部分为 0
所以 0.75 的二进制是 0.11

最终得到 8.75 等于二进制数 1000.11。

0.1,0.2,0.3,0.4 的二进制转化

通过上面的计算方式,可以得出 0.1,0.2,0.3,0.4 对应的二进制数:

// 括号内表示数字无限循环
0.1 -> 0.000110011(0011)
0.2 -> 0.00110011(0011)
0.3 -> 0.010011(0011)
0.4 -> 0.0110011(0011)

JS 数字存储方式

在实际存储中,不可能保存无限长度的数据,JS 采用 IEEE 754 双精度64位浮点数来保存数字,格式为s * m * (2^e),其中 s 表示符号位,m 表示尾数占52位,e 表示指数占11位。

我们来看看上面几个数在计算机内的表示,也可以在这个网站验证结果:

// 0.1
e = -4;
m = 1.1001100110011001100110011001100110011001100110011010 (52位)

// 0.2
e = -3;
m = 1.1001100110011001100110011001100110011001100110011010 (52位)

// 0.3
e = -2
m = 1.0011001100110011001100110011001100110011001100110011 (52位)

// 0.4
e = -2
m = 1.1001100110011001100110011001100110011001100110011010 (52位)

特别注意的是,对于无限长度的数据,在存储过程中,会有数据的舍入【二进制向最近偶数舍入】,这造成了后面数据计算的误差。

0.1 + 0.2 !== 0.3

数学中计算时,我们需要将指数位置对齐,但需要指明的是JS中没有采用Exponent Bias,而是将尾数Mantissa视为为整数计算的,这样误差会增大,但是实现算法简单。
1.1001100110011001100110011001100110011001100110011010 (Exponent:-4)+ // 0.1
1.1001100110011001100110011001100110011001100110011010 (Exponent:-3)= // 0.2

这里有一个问题,就是指数不一致时,应该怎么处理,一般是往右移,因为即使右边溢出了,损失的精度远远小于左移时的溢出。
0.11001100110011001100110011001100110011001100110011010 (Exponent:-3)+ // 0.1
1.10011001100110011001100110011001100110011001100110100 (Exponent:-3)= // 0.2
10.01100110011001100110011001100110011001100110011001110 (Exponent:-3)
上式结果经过两步转化,得到最终结果:
1.001100110011001100110011001100110011001100110011001110 (Exponent:-2) // 54 位,要转化为 52位,需要进行舍入
1.0011001100110011001100110011001100110011001100110100 (Exponent:-2)

转换为IEEE754双精度为 1.0011001100110011001100110011001100110011001100110100 * 2(-2),如果用二进制转成十进制为(2(-2)+2(-5)+2(-6)...)。 结果大约是0.30000000000000004419,去小数点后面17位精度为0.30000000000000004。

0.1 + 0.3 === 0.4

1.1001100110011001100110011001100110011001100110011010 (Exponent:-4)+ // 0.1
1.0011001100110011001100110011001100110011001100110011 (Exponent:-2) // 0.3

这里有一个问题,就是指数不一致时,应该怎么处理,一般是往右移,因为即使右边溢出了,损失的精度远远小于左移时的溢出。

0.011001100110011001100110011001100110011001100110011010 (Exponent:-2)+ // 0.1
1.001100110011001100110011001100110011001100110011001100 (Exponent:-2)= // 0.3
1.100110011001100110011001100110011001100110011001100110 (Exponent:-2) // 54 位,二进制舍入后
1.1001100110011001100110011001100110011001100110011010 (Exponent:-2) // 52 位

最终的结果,恰好等于 0.4。

结论

JS 浮点数转化为二进制数进行存储,由于存储的长度有限制,就会有数据的舍入而导致精度丢失。所以在浮点数的计算,都会有精度丢失,即使结果看起来正确,也只是碰巧而已。

标签:Exponent,0.1,0.3,0.2,二进制,浮点数
来源: https://www.cnblogs.com/fayin/p/13926186.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有