一道送命题:0.1+0.2 等于 0.3 吗?

2021-03-25 10:57:35 浏览数 (1)

前言

去互联网金融或电商行业的公司面试时,一般都会遇类似“ 0.1 0.2 等于 0.3吗?”这道题,对于非科班出身的前端人是一道送命题,有些知道 0.1 0.2 不等于 0.3,但是继续深问为什么,就无法很清晰地回答。

本专栏总结一下回答 0.1 0.2 不等于 0.3 的思路,在回答之前要先弄清楚 0.1 0.2 的计算过程。

0.1 0.2 的计算过程计算过程

1、十进制转成二进制

在JS内部所有的计算都是以二进制方式计算的。 所以运算 0.1 0.2 时要先把 0.1和 0.2 从十进制转成二进制。

  • 0.1转化成二进制的算法: 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.4*2=0.8======取出整数部分0 0.8*2=1.6======取出整数部分1 0.6*2=1.2======取出整数部分1 所以0.1转化成二进制是:0.0001 1001 1001 1001......
  • 0.2转化成二进制的算法: 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.4*2=0.8======取出整数部分0 0.8*2=1.6======取出整数部分1 0.6*2=1.2======取出整数部分1 所以0.2转化成二进制是:0.0011 0011 0011 0011......

这里要注意 0.1 和 0.2 转成的二进制是无穷的。另外在现代浏览器中是用浮点数形式的二进制来存储二进制,所以还要把上面所转化的二进制转成浮点数形式的二进制。

2、转成浮点数

浮点数分为单精度对应32位操作系统和双精度对应64位操作系统。目前的操作系统大多是64位操作系统,故这里只解释一下二进制如何转成双精度浮点数的二进制。

双精度浮点数用1位表示符号位,11位表示指数位,52位表示小数位,如下图所示:

  • 符号位:正数为0,负数为1;
  • 指数位:阶数 偏移量,阶数是:

,e为阶码的位数。偏移量是把小数点移动到整数位只有1时移动的位数,正数表示向左移,负数表示向右移;

  • 小数位:即二进制小数点后面的数。

接下来把0.1转成的二进制0.0001100110011001 ......转成浮点数形式的二进制。

  • 先要把小数点移动到整数位只有1,要向右移动4位,故偏移量为−4,通过指位数的计算公式

,把1019转成二进制为1111111011,不够11位要补零,最终得出指位数为01111111011;

  • 小数位为100110011001...... ,因为小数位只能保留52位,第53位为1故进1。

转换结果如下图所示:

同理,再把 0.2 转成的二进制0.0011 0011 0011 0011...... 转成浮点数形式的二进制,转换结果如下图所示:

浮点数相加

浮点数相加时,需要先比较指位数是否一致,如果一致则小数位直接相加,如果不一致,要先把指位数调成一致的,指位数小的向大的调整。

为了行文方便,把0.1转成的浮点数称为为0.1,把0.2转成的浮点数称为0.2。

0.1的指数位是1019 ,0.2的指数位是1020 。故要把0.1的指数位加1,即把0.1的小数点向左移动1位,另外浮点数的整数位固定为1,过程如下所示

代码语言:javascript复制
1.1001100110011001100110011001100110011001100110011010   原先
0.11001100110011001100110011001100110011001100110011010  移动后  
0.1100110011001100110011001100110011001100110011001101   将小数的第53位舍去,因为为0故不需进1


导致0.1的小数位变成如下所示:

现在0.1和0.2的指数位相同了,把小数位直接相加。

代码语言:javascript复制
    1100110011001100110011001100110011001100110011001101 0.1的小数位
    1001100110011001100110011001100110011001100110011010 0.2的小数位
=  10110011001100110011001100110011001100110011001100111


会发现现在的小数位多出了一位,超出了52位,故要把小数位最后一位截掉,小数位最后一位是1,故要进1,如下所示:

代码语言:javascript复制
10110011001100110011001100110011001100110011001100111
1011001100110011001100110011001100110011001100110100


截掉小数位的最后一位相当把小数点向左移了一位,故指数位要加1,此时的指数是0.2的指数1021 ,加1后变成1021 ,转成二进制为01111111101 ,那么相加后的浮点数如下所示:

浮点数转成十进制

二进制浮点数计算结束后,把结果(二进制的浮点数)转成十进制,其转换公式为

,s是符号位为0或1,e为浮点数指数位转成十进制的值,i表示小数位从左到右的位数,第一位 i=1 ,

表示每一位的值为0或1。

那么按着公式把二进制的浮点数转成十进制:

结果如下所示:

0.3000000000000000444089209850062616169452667236328125

由于精度问题,只取到0.30000000000000004。

答案

0.1 0.2 不等于 0.3 ,因为在 0.1 0.2 的计算过程中发生了两次精度丢失。第一次是在 0.1 和 0.2 转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位,导致小数点后第53位的数要进行为1则进1为0则舍去的操作,从而造成一次精度丢失。第二次在 0.1 和 0.2 转成二进制浮点数后,二进制浮点数相加的过程中,小数位相加导致小数位多出了一位,又要让第53位的数进行为1则进1为0则舍去的操作,又造成一次精度丢失。最终导致 0.1 0.2 不等于0.3 。

拓展

若你回答出来,面试官还可能继续问你:“ 0.1 0.2 不等于 0.3 会引起那些BUG?”

可以这样回答:“ 会引起统计页面展示错乱的BUG,还有 300.01 优惠300 元后,支付金额不足0.01 元等类似的BUG。”

还可能继续问道:“怎么解决 0.1 0.2 不等于 0.3 这个问题”。

可以这样回答:“可以用Math.js数学计算库来解决,或者用toFixed()给计算结果四舍五入,但是toFixed()在chrome或者火狐浏览器下四舍五入也有精度误差。可以用Math.round来解决精度误差,比如要把 2.55 四舍五入保留 1 位小数,先把 2.55∗10 得到 25.5 ,再用Math.round取整25.5 ,会得到25,再把 25÷10 得到 2.5 ,就这样间接实现了四舍五入。可以用Math.pow来做个简单的封装Math.round(Math.pow(10, m) * number) / Math.pow(10, m),其中number是要四舍五入的数,m是保留几位小数。

0 人点赞