原创: 蜀中亮子 玄说前端
小数运算的问题
在 js 中的小数运算中,一直存在着一个问题,
比如:0.1+0.2=0.30000000000000004 、0.4-0.3=0.10000000000000003。
那么为什么会出现这种情况呢?这种情况又如何解决呢?
为什么?
这就要讲到 js 小数的存储,在 js 找那个所有的数字包括小数和整数都只有一种类型—Number。它实现遵循 IEEE 754 标准。使用 64 位固定长度来表示。
这样的存储结构优点是可以归一化处理整数和小数,节省存储空间。
64 位比特又可分为三个部分:
- 符号位 S:第 1 位是正负数符号位(sign),0 代表正数,1 代表负数
- 指数位 E:中间的 11 位存储指数(exponent),用来表示次方数
- 尾数位 M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零
![5a13456f84c00902dbec2414d82af81d.png](https://i-blog.csdnimg.cn/blog_migrate/2c801ea8d858f1c8a516990e3147e9b0.png)
实际数字就可以用以下公式来计算:
![e66090386158043728a594287cfdb29b.png](https://i-blog.csdnimg.cn/blog_migrate/50d2ebec27a0ca2f1fed0859f911883f.jpeg)
注意以上的公式遵循科学计数法的规范,在十进制是为 0<M<10,到二进制就是 0<M<2。也就是说整数部分只能是 1,所以可以被舍去,只保留后面的小数部分(想不通这个道理的,可以看下下面0.1的例子)。
比如:十进制 4.5 转换成二进制就是 100.1,科学计数法表示是 1.001*2^2,舍去 1 后 M = 001;
E 是一个无符号整数,因为长度是 11 位,取值范围是 0~2047。但是科学计数法中的指数是可以为负数的,所以再减去一个中间数 1023,[0,1022]表示为负,[1024,2047] 表示为正。如 4.5 的指数 E = 1025,尾数 M 为 001。
最终的公式变成:
![f66997859f5da3a46543b49171c040c8.png](https://i-blog.csdnimg.cn/blog_migrate/f3514e4b920a9bc935d1cb777073788b.jpeg)
所以 4.5 最终表示为(S=1、E=1025、M=001);
再以 0.1 例解释浮点误差的原因, 0.1 转成二进制表示为 0.0001100110011001100(1100 循环),1.100110011001100x2^-4,所以 E=-4+1023=1019;M 舍去首位的 1,得到 100110011...。转化成十进制后为 0.100000000000000005551115123126,因此就出现了浮点误差。
那小数是怎么转为二进制的呢?
小数转二进制
比如数字 3.2 转为二进制的过程;
- 第一步:0.32*2=0.64,这个时候整数部分是 0,所以第一位是 0,那么就是 0.0
- 第二步:0.64*2=1.28,这个时候整数部分是 1,所以第二位是 1,那么就是 0.01
- 第三步:把取了一之后的 1.28 减去 1 之后转换为 0.28,拿这个部分再乘以二,那么就是 0.28*2=0.56,这个时候整数部分是 0,所以第三位是 0,那么就成了 0.010
- 第四步:0.56*2=1.12,这个时候整数部分是 1,所以第四位是 1,那么就是 0.0101,
- 第五步:和第三步的思路一样 这样一直到最后小数部分没有了的时候,就算是转换完成。为什么要乘以二,因为使用二进制数据表示数的时候,只有两位,0 和 1。
怎么解决这个问题
第一种
把小数转成整数后再运算。以加法为例:
0.1+0.2
把数字都乘以他们可以转换为整数的倍数,计算之后,再除以乘上的倍数。比如:(0.1*10+0.2*10)/10
第二种
还有另一个方案,是做一个自己的计算过程,比如 0.1+0.2,首先通过正则或者 AST 或者其他的解析方式,匹配出了符号和数字,再把字符按照四则运算法的规律来计算,0.1 和 0.2 取小数位后第一位相加减,是否进 1,发现不需要去整数位第一位加减,依次递推。这个方法的思路就和我们竖式计算一样,把竖式计算的思路,代码化。至此,小数的计算与原因都找到了。拓展一下,在计算机底层 cpu 是怎么进行加减乘除运算的呢?可以进群讨论最后附上想进前端群的可以加微信,备注:玄说前端。
![aac3e9cd990060fcb493726f21add00f.png](https://i-blog.csdnimg.cn/blog_migrate/f86f6fb82daf1cac43c1927664495a84.jpeg)
END
获得更多信息关注公众号
![6eb7a01881d8c6eed676c5b56cd5a4f1.png](https://i-blog.csdnimg.cn/blog_migrate/102e44895a0501423f51eaef33cd8f12.png)