比较常见的计算问题: 0.1 + 0.2 ~= 0.3
天真的我,以为(a100+b100)/100 就解决问题了
参考计算方式
function sum(num1,num2){
num1= num1==null?0:num1
num2= num2==null?0:num2
var str1 = num1.toString(),str2=num2.toString(),result,str1Length,str2Length2
try{
str1Length = str1.split('.')[1].length
}catch{
str1Length = 0
}
try{
str2Length = str2.split('.')[1].length
}catch{
str2Length = 0
}
var temp = Math.pow(10,Math.max(str1Length,str2Length))
return (num1*temp+num2*temp)/temp
}
比如解决sim(0.1, 0.2) 是没问题的, 确实= 0.3
但是如果是下面这两个值相加发现还是不对:
sum(289.46, 38.48) = 327.93999999999994
发现其实,在*100(小数点的位数)的过程,这个值也会有精度问题,乘完的结果因为并不会得到整数,所以相加过后,同样会丢失精度..
这里就发现简单的乘法求整处理可能无法彻底解决这个问题的,鉴于我这里的背景是金额计算,我索性重新写了一个sum方法,主要支持两位小数点以内的计算,运用tofixed和parseInt的方法来强转整数,然后再做加减运算。
修改后的计算方式
// 提升两位数精度的加法, 因为是金额相加,所以:参数只支持最多两位小数点
// 目前涉及金额不算非常大数据的情况,如果后期数据量大,可能会考虑引入第三方库如:decimal.js
// Math.pow(2, 53) 一旦超过此值,js的计算就会出现误差
function sumAmount(...args) {
let result = 0;
const toFixTransNumX100 = (n) => {
const strFloat = n.toFixed(2);
const intStr = strFloat.split(".")[0] + strFloat.split(".")[1];
return parseInt(intStr);
};
for (let i = 0; i < args.length; i++) {
// 如果args的小数点超过两位,报错
if (Number(args[i].toFixed(2)) != args[i]) {
throw new Error("sumAmount: args[i] should be less than 2 decimal");
}
result += toFixTransNumX100(args[i]);
}
if (result > Math.pow(2, 53)) {
throw new Error("sumAmount: result is too big");
}
return result / 100;
}
总结:第二个方法目前测试过虽然是可行的,主要是项目上不想引用太多第三方库。正经项目还是推荐用一些开源的库来解决这类计算问题,如
https://www.npmjs.com/package/decimal.js, decimal.js 的核心思想是使用字符串来表示数字,从而避免 JavaScript 浮点数的精度问题,并提供高精度的数学运算功能。它通过字符串操作来实现各种数学运算和函数,以确保计算结果的精度,感兴趣的可以研究其源码。
额外的思考,其实在存这些金额值的时候,数据库设计金额的时候,应该设计成 “分” 为单位,这样也可以避免这种问题。