JS数字精度丢失的原因及解决方案
前言
在JavaScript中计算两个十进制数的和,有时候会出现令人惊讶的结果,相信这个大家也都知道了!
精度丢失
例如,我们在计算0.1 + 0.1得到的结果是0.2,但是计算0.1 + 0.2的结果并不是0.3,而是0.30000000000000004
这种现象不仅出现在加法,在减法中也会出现类似的结果。
例如1.2 - 1的结果是0.19999999999999996
不过这并不是JavaScript独有的,其他编程语言也会存在同样的问题。
例如在Python环境下输出。
print(.1 + .2)
得到的结果同样是:0.30000000000000004
原因
这个问题的主要原因是计算机将数据存储为二进制。
如何将整数从十进制转换为二进制
十进制整数可以通过将其除以 2 转换为二进制。取商,并继续除以 2,直到达到零。
每次执行此除法时,请记下余数。现在反转余数列表,得到二进制形式的数字。
举个例子,我想把29转成二进制:
29÷2=14余1
14÷2=7余0
7÷2=3余1
3÷2=1余1
1÷2=0余1
代表十进制 29 的二进制数是11101.
例如:
1 也就是1
10 也就是1010
将小数从十进制转换为二进制
十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,此时0或1为二进制的最后一位。或者达到所要求的精度为止。
举个例子:我想把0.375转成二进制:
0.375*2=0.75 得到0
0.75*2=1.5得到1
0.5*2=1 得到1,小数没了,结束。最终转为二进制是0.011
例如:
- 0.1 也就是0.0001100110011001100110011001100110011001100110011001101
- 0.2 也就是0.001100110011001100110011001100110011001100110011001101
并非每个十进制数都可以用这种二进制格式完美表示,因为有些数字可能转成
JavaScript 在计算时,会先将10进制转为二进制,在进行计算。
0.00011001100110011001100110011001100110011001100110011010 + 0.0011001100110011001100110011001100110011001100110011010 = 0.0100110011001100110011001100110011001100110011001100111
这里得到的结果0.0100110011001100110011001100110011001100110011001100111再转换成十进制就是0.30000000000000004
解决方案
第三方库
Decimal
x = new Decimal(0.1) y = x.plus(0.2)
bignumber
x = new BigNumber(0.1) y = x.plus(0.2)
变成整数
主要思想是:将小数先转换成拆分两个字符串,然后计算小数部分的字符串的长度,然后利用这个长度将小数变成整数!
function add(num1, num2) { const num1Len = (num1.toString().split('.')[1] ).length; const num2Len = (num2.toString().split('.')[1] ).length; const maxLen = Math.pow(10, Math.max(num1Len, num2Len)); return (num1 * maxLen + num2 * maxLen) / maxLen; }