js四舍五入和计算精度问题

js四舍五入和计算精度问题在电商网站中经常有金额的计算,但是在js中加减乘除的计算并不准确,比如:0.1+0.2=0.3000000000000004。那么势必会造成线上的事故,毕竟关于钱的事都是大事。我们可以通过引入mathjs解决这个问题安装:引入和使用:常用方法:具体使用方法可以直接查看文档:https://mathjs.org/docs/index.html解决了计算精度问题之后,计算得出的金额往往是多位小数,在实际业务中,我们需要把多位小数保留小数点后两位,且进行四舍五入。在网上查阅了资料并进行了多次实践之后,

业务背景

在电商网站中经常有金额的计算,但是在js中加减乘除的计算并不准确,比如:0.1+0.2 = 0.3000000000000004。那么势必会造成线上的事故,毕竟关于钱的事都是大事。
我们可以通过引入 mathjs 解决这个问题

计算精度问题(mathjs)

安装:npm install mathjs

引入和使用:

 import * as math from 'mathjs';
 let num = math.number(math.chain(math.bignumber(0.1))
   .add(math.chain(math.bignumber(0.2))
   .done());
 //  0.3

常用方法:

方法名 方法
add()
subtract()
multiply()
divide()
转变为数字类型 number()
转变为bigNumber bignumber()
链式调用 chain()

具体使用方法可以直接查看文档:https://mathjs.org/docs/index.html

四舍五入问题

解决了计算精度问题之后,计算得出的金额往往是多位小数,在实际业务中,我们需要把多位小数保留小数点后两位,且进行四舍五入

在网上查阅了资料并进行了多次实践之后,总结出了正确的四舍五入方法。首先看一下网上经常使用的几种无效方案

无效方案一:toFixed(2)

这应该是网上最多的解决方案了,实际上toFixed也会有精度问题,比如:
1.025.toFixed(2)
我们想要的结果是 1.03 ,但实际上计算出了1.02 的结果。
经过查找资料,了解了toFixed的运行逻辑:
在这里插入图片描述
主要看图中的这句话,大体意思就是如果toFixed的入参小于10的21次方,那么就取一个整数n,让n*10^f – x 的精确值尽可能的趋近于0,如果存在两个这样的n,取较大的n。
在这里插入图片描述
显然,当n=102的时候,n*10^f – x 更趋向于0,所以计算的结果是1.02,而不是1.03

无效方案二:小数位截取计算

直接看代码:

function round(number, presision) {
  const [int, decimals] = String(number).split('.');
  let precisionDecimals = +decimals.slice(0, presision);
  
  if (decimals[presision] > 4) precisionDecimals++;
  
  return parseFloat(int + '.' + precisionDecimals)
}

可以看出以上方式是通过截取小数位的方式进行计算,最后进行拼接。
有两个问题:1.没有考虑小数第一位0的问题,比如1.025的小数位是025,计算的时候会当成25计算 。2.没有考虑进位问题,比如1.999向前进位为2.000
总之,这个方法咔掉。

无效方案三:Math.round(n*100)/100

此方案有两个问题:

  1. 进行负数运算的时候,不是按照四舍五入的规则进行的。
    比如:Math.round(-11.5),结果为 -11
    解决:只需要转为正数,最后在变为负数即可
  2. n*100的计算问题
    比如:Math.round(1.025*100)/100,期望是1.03,但实际结果为1.02
    是因为1.025*100的计算结果并不是我们想到102.5,而是102.49999999999999
    那么只要通过上面的精准计算,就可以使用Math.round()了

最终方案

通过上面的分析,知道Math.round()是可以解决问题的,只需要保证参数是正数,且计算正确。
那么引入mathjs进行计算,并判断数值正负即可

    round(number) {
      let bigNumber = math.number(
        math.chain(math.bignumber(number))
          .multiply(math.bignumber(100))
          .done()
      );
      if(number<0){
        return -(Math.round(-bigNumber) / 100);
      }else{
        return Math.round(bigNumber) / 100;
      }
    }

后记

这个问题并不难,相反很简单,只需要自己测试查找资料半小时就能解决。但恰恰是这个简单的问题,网上的解决方案却错误百出,连最基本的测试都没有进行过。写这篇博客除了记录下解决问题的过程,也是想吐槽一下现在的网络环境。
最后,如果大家有更好的方案,或者文中有错误的地方,欢迎大家帮我指出来,一起进步。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/37050.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注