在做项目的时候,涉及到金额加减时,经常会出现计算精度的问题,常见例子如下:
加法
0.1 + 0.2 = 0.30000000000000004
0.7 + 0.1 = 0.7999999999999999
0.2 + 0.4 = 0.6000000000000001
减法
1.5 - 1.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998
乘法
0.8 * 3 = 2.4000000000000004
35.41 * 100 = 3540.9999999999995
除法
0.3 / 0.1 = 2.9999999999999996
0.69 / 10 = 0.06899999999999999
在遇到浮点数运算后出现的精度问题时,起初想等加完之后使用toFixed(2)来解决的,toFixed()方法可把Number四舍五入为指定小数位数的数字
测试发现还是存在一定误差
为什么会产生
JavaScript中所有数字包括整数和小数都只有一种类型 — Number。它的实现遵循 IEEE 754 标准,使用64位固定长度来表示,也就是标准的 double 双精度浮点数,
这样的存储结构优点是可以归一化处理整数和小数,节省存储空间。
64位比特又可分为三个部分:
符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数
指数位E:中间的 11 位存储指数(exponent),用来表示次方数
尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零
然后当javaScript运算时,会先把十进制的0.1和0.2会被转换成二进制的,但是由于浮点数用二进制表示时是无穷的:
0.1 -> 0.0001 1001 1001 1001...(1100循环)
0.2 -> 0.0011 0011 0011 0011...(0011循环)
IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持53位二进制位,所以两者相加之后得到二进制为:
0.0100110011001100110011001100110011001100110011001100
因浮点数小数位的限制而截断的二进制数字,再转换为十进制,就成了0.30000000000000004。所以在进行算术计算时会产生误差。
如何解决
大概思路就是把浮点数转化成整数(乘以10的n次幂)去加减,然后在降级处理(除以10的n次幂)
例如: 0.1 + 0.2 == 0.3 //false
(0.1*10 + 0.2*10)/10 == 0.3 //true
本以为这样就能完美解决了,尴尬的事情出行了
18.4 * 100 = 1839.9999999998
所以最终决定把数值先toString之后通过split方法切割小数点,把小数点位数补成两位后,通过字符串拼接的方式拼接起来然后转成数值进行加减,最后在除以100即可。
代码如下:
//补全位数
function operation(num) {
//如果是整数的话默认补两位小数
if (num) {
num = num.slice(0,2);
return num.length === 1 ? num = num + '0' : num;
} else {
return '00'
}
}
//处理浮点数想加
var addNUm = function (num1,num2) {
var array1 = num1.toString().split("."),//转化成字符串并已小数点切割
array2 = num2.toString().split(".");
//如果是整数的话默认补两位小数
array1[1] = operation(array1[1]);
array2[1] = operation(array2[1]);
//把整数部分和小数部分拼接起来然后在转成数字类型
var num1 = Number(array1[0] + array1[1]),
num2 = Number(array2[0] + array2[1]);
//整数想加之后除以100
return (num1 + num2) / 100;
}
addNUm(0.1,0.2);