今天在处理后端数据时,需要将一数组值求和,由于传递的是字符型,所以就直接使用Number()转化后直接遍历相加了, 结果发现得出的值是一串神秘数字.
经典示例
0.1 + 0.2
//0.30000000000000004
Number('0.11') + Number('0.12')
//0.22999999999999998
了解了一下,在 JavaScript 中,由于采用了 IEEE 754 标准的浮点数表示方法,可能会导致精度丢失问题。这主要是因为浮点数在内存中以二进制的形式存储,而某些十进制数无法精确地转换成二进制表示。当进行计算时,就会出现舍入误差。
关于转化成二进制
整数部分遵从:除2取余 + 逆序排列
即:
Number 8 to binary
8 / 2 = 4 ...... 0
4 / 2 = 2 ...... 0
2 / 2 = 1 ...... 0
1 / 2 = 0 ...... 1
8 ==> 1000
Number 10 to binary
10 / 2 = 5 ...... 0
5 / 2 = 2 ...... 1
2 / 2 = 1 ...... 0
1 / 2 = 0 ...... 1
10 ==> 1010
而对于小数部分,则是:乘2取整 + 顺序排列
Number 0.1 to binary
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.00110011......
而刚才提到的 IEEE 754 标准 64 位存储 分为 3 个部分,包括 符号位(1)、指数位(11) 和 小数位(52) , 这就导致了当小数第53位为1时,需要往前进一位再保存,这里就造成了第一次的精度丢失,
而当小数相加时,位数再次超过52位,就会再次进位截取,导致第二次精度丢失,从而出现问题.
解决方法
明细了问题所在,想要解决就很简单了.既然小数会丢失精度,就直接扩大倍数使用整数计算,最后再还原即可.
(Math.round(Number('1.11') * 100) + Math.round(Number('1.12') * 100)) / 100
//2.23
(Math.round(Number('0.1111') * 10000) + Math.round(Number('0.1112') * 10000)) / 10000
//0.2223