问题描述
在做公司库存数量的逻辑时踩到的大坑,如果对于浮点数,直接在JavaScript中进行加减乘除四则运算会出现很大的误差。问题产生的原因在于,计算机中是将十进制的数据转化为二进制来进行计算的,而对于浮点型的数据,转化成二进制之后可能会变成一个无限循环的数字,而计算机中是不允许无限循环的存在的,那就要做截断,而截断后的数据运算之后再转成十进制就可能会产生极大的误差。
其实并不是只JS存在这种问题,Java、C++也都如此,只不过他们内部都有封装好的方法来解决这个问题。JS是一门弱语言,没有做类似的操作。
解决办法
看到网上也有用toFixed()方法来解决的,但是这种四舍五入的方式在高精度需求下也显得不那么严谨。
最常用的方法就是把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完毕再降级(除以10的n次幂),这是大部分编程语言处理精度差异的通用方法。
参考代码
(Ant Design for Vue语言情景下)
/**
* arg1与arg2相加
*/
accAdd(arg1, arg2) {
var r1, r2, m;
try {r1 = arg1.toString().split(".")[1].length} catch (e) {r1 = 0};
try {r2 = arg2.toString().split(".")[1].length} catch (e) {r2 = 0};
m = Math.pow(10, Math.max(r1, r2));
return (arg1 * m + arg2 * m) / m;
},
/**
* arg1与arg2相减
*/
accSubtr(arg1,arg2){
var r1,r2,m,n;
try{r1=arg1.toString().split(".")[1].length;}catch(e){r1=0;}
try{r2=arg2.toString().split(".")[1].length;}catch(e){r2=0;}
m=Math.pow(10,Math.max(r1,r2));
//动态控制精度长度
n=(r1>=r2)?r1:r2;
return ((arg1*m-arg2*m)/m).toFixed(n);
},
/**
* arg1与arg2相乘
*/
accMul(arg1, arg2) {
var m = 0, s1 = arg1.toString(), s2 = arg2.toString();
try {m += s1.split(".")[1].length} catch (e) {};
try {m += s2.split(".")[1].length} catch (e) {};
return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
},
/**
* arg1与arg2相除,并以四舍五入的方式保留小数点后2位
*/
divide(arg1, arg2) {
var d1, d2,
n1 = Number(arg1.toString().replace(".", "")),
n2 = Number(arg2.toString().replace(".", ""));
try {d1 = arg1.toString().split(".")[1].length;} catch (e) {d1 = 0;}
try {d2 = arg2.toString().split(".")[1].length;} catch (e) {d2 = 0;}
return this.toFixed((n1 / n2) * Math.pow(10, d2 - d1), 2);
},
JS中自带的toFixed函数会把多余的小数直接截掉,因此,这里进行重写,采用四舍五入的方式保留小数 。
/**
* arg以四舍五入的方式保留小数点后n位
*/
toFixed(arg, n) {
if (n == 0) {
return Math.round(arg)
}
try {
var d, carryD, i,
ds = arg.toString().split('.'),
len = ds[1].length,
b0 = '', k = 0
if (len > n) {
while (k < n && ds[1].substring(0, ++k) == '0') {
b0 += '0'
}
d = Number(ds[1].substring(0, n))
carryD = Math.round(Number('0.' + ds[1].substring(n, len)))
len = (d + carryD).toString().length
if (len > n) {
return Number(ds[0]) + 1
} else if (len == n) {
return Number(ds[0] + '.' + (d + carryD))
}
return Number(ds[0] + '.' + b0 + (d + carryD))
}
} catch (e) {
}
return arg
},