美文网首页
Javasript计算金额那点事儿

Javasript计算金额那点事儿

作者: JakeBless | 来源:发表于2023-11-05 14:43 被阅读0次

比较常见的计算问题: 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 浮点数的精度问题,并提供高精度的数学运算功能。它通过字符串操作来实现各种数学运算和函数,以确保计算结果的精度,感兴趣的可以研究其源码。

额外的思考,其实在存这些金额值的时候,数据库设计金额的时候,应该设计成 “分” 为单位,这样也可以避免这种问题。

相关文章

网友评论

      本文标题:Javasript计算金额那点事儿

      本文链接:https://www.haomeiwen.com/subject/kamzidtx.html