• 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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    常用方法:

    方法名方法
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看出以上方式是通过截取小数位的方式进行计算,最后进行拼接。
    有两个问题: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;
          }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    后记

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

  • 相关阅读:
    关于登录的那些事(vue3+element plus)
    【Python自动化办公】批量将Excel表格数据导出为PDF文件
    有哪些可助力英文学术论文写作的在线网站、工具或软件?
    Python开发技术—文件和异常5
    计网第六章(应用层)(二)(域名系统DNS)
    halcon算子1、dev_open_window
    R语言使用lm函数构建线性回归模型、使用subset函数指定对于数据集的子集构建回归模型(使用floor函数和length函数选择数据前部分构建回归模型)
    中秋佳节,基于华为云AI制作属于自己的月亮!
    Java OA系统日程管理模块
    centos7安装cdh6.3.2-附带安装包
  • 原文地址:https://blog.csdn.net/yuyuking/article/details/125335773