前言
0.1+0.2=0.3在我们的日常生活中是简单到不用动脑子就能得出的等式。但是在计算机的世界中恐怕并非如此,你或许在编写代码时见过0.1+0.2=0.30000000000000004,是计算机算错了吗?让我们来探究其背后的原理。
原理
首先我们要知道:Javascript的数字型数据并不区分整数还是小数,而只有number,即1===1.00,同时采用的是IEEE754的64位双精度版本,由三部分组成:
- 1位数符(sign bit,记为S):标记正负,0为正,1为负
- 11位阶码(exponent,记为E):数字的整数部分
- 52位尾数(mantissa,记为M):数字的小数部分的期望
计算机是以二进制的方式存储数据的,而0.1的二进制表达为:
0.1 = 0.0(0011)(0011)(0011)(0011)....
是一个无限循环小数,在 IEEE 754 中,循环位就不能在无限循环下去了,在双精确度 64 位下最多存储的有效整数位数为 52 位,会采用 就近舍入(round to nearest)模式(进一舍零) 进行存储
11001100110011001100110011001100110011001100110011001 // M 舍去首位的 1,得到如下
1001100110011001100110011001100110011001100110011001 // 0 舍 1 入,得到如下
1001100110011001100110011001100110011001100110011010 // 最终存储
结论
因为浮点数自身小数位数的限制而截断的二进制在转化为十进制,就变0.30000000000000004,所以在计算时会产生误差。
运算过程
算法:
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.0 0011 0011 0011 ..... "0011的无限循环"
0.2的二进制结果 0.0011 0011 0011 0011 .... "0011的无限循环"
取52位进行计算
0.1 = 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001
0.2 = 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011
计算的结果是: 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 ...也是无限循环
将二进制转换为十进制:
浮点型数据存储转换为十进制的话 只会取前17位
0.300000000000000044408920958 .... = 0.30000000000000004(四舍五入)
所以0.1+0.2的结果是等于0.30000000000000004
解决办法
使用toFixed方法,toFixed方法可以指定运算结果的小数点后的指定位数的数字,使保留一位小数就是toFixed(1)
let x=parseFloat((0.1+0.2).toFixed(1));
//使用toFixed方法将number转换成了string,再使用parseFloat转回number
console.log(x===0.3);
//打印结果:true