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

业务背景

在电商网站中经常有金额的计算,但是在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()
转变为bigNumberbignumber()
链式调用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;
      }
    }

后记

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

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值