• 解决JS浮点数运算结果不精确的方案


    一、背景,起因

    最近在做项目的时候,涉及到产品价格的计算,经常会出现JS浮点数精度问题,这个问题,对于财务管理系统的开发者来说,是个非常严重的问题(涉及到钱相关的问题都是严重的问题)

    二、常见例子

       // 加法
       0.1 + 0.2 = 0.30000000000000004
       0.1 + 0.7 = 0.7999999999999999
       0.2 + 0.4 = 0.6000000000000001
    
       // 减法
       0.3 - 0.2 = 0.09999999999999998
       1.5 - 1.2 = 0.30000000000000004
    
       // 乘法
       0.8 * 3 = 2.4000000000000004
       19.9 * 100 = 1989.9999999999998
    
       // 除法
       0.3 / 0.1 = 2.9999999999999996
       0.69 / 10 = 0.06899999999999999
    
       // 比较
       0.1 + 0.2 === 0.3 // false
       (0.3 - 0.2) === (0.2 - 0.1) // false
    

    三、原因

    JavaScript 内部只有一种数字类型Number,也就是说,JavaScript 语言的底层根本没有整数,所有数字都是以IEEE-754标准格式64位浮点数形式储存,1与1.0是相同的。因为有些小数以二进制表示位数是无穷的。JavaScript会把超出53位之后的二进制舍弃,所以涉及小数的比较和运算要特别小心。

    四、 解决办法

    【1】引用类库

          Math.js
          decimal.js
          big.js
    

    【2】思路一:在知道小数位个数的前提下,可以考虑通过将浮点数放大倍数到整型(最后再除以相应倍数),再进行运算操作,这样就能得到正确的结果了

       0.8 * 3 ——> ( 0.8 * 100 * 3) / 100         //2.4
    

    五、自定义一个转换和处理函数

    【1】加法函数

     /**
      ** 加法函数,用来得到精确的加法结果
      ** 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
      ** 调用:calculateAdd(arg1,arg2)
      ** 返回值:arg1加上arg2的精确结果
      **/
     function calculateAdd(arg1, arg2) {
       let r1, r2, m, c;
       try {
         r1 = arg1.toString().split(".")[1].length;
       } catch (e) {
         r1 = 0;
       }
       try {
         r2 = arg2.toString().split(".")[1].length;
       } catch (e) {
         r2 = 0;
       }
       c = Math.abs(r1 - r2);
       m = Math.pow(10, Math.max(r1, r2));
       if (c > 0) {
         let cm = Math.pow(10, c);
         if (r1 > r2) {
           arg1 = Number(arg1.toString().replace(".", ""));
           arg2 = Number(arg2.toString().replace(".", "")) * cm;
         } else {
           arg1 = Number(arg1.toString().replace(".", "")) * cm;
           arg2 = Number(arg2.toString().replace(".", ""));
         }
       } else {
         arg1 = Number(arg1.toString().replace(".", ""));
         arg2 = Number(arg2.toString().replace(".", ""));
       }
       return (arg1 + arg2) / m;
     }
    
     //给Number类型增加一个add方法,调用起来更加方便。
     Number.prototype.add = function (arg) {
       return accAdd(arg, this);
     };
    

    【2】减法函数

        /**
         ** 减法函数,用来得到精确的减法结果
         ** 说明:javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显。这个函数返回较为精确的减法结果。
         ** 调用:calculateSub(arg1,arg2)
         ** 返回值:arg1加上arg2的精确结果
         **/
        function calculateSub(arg1, arg2) {
          let r1, r2, m, n;
          try {
            r1 = arg1.toString().split(".")[1].length;
          } catch (e) {
            r1 = 0;
          }
          try {
            r2 = arg2.toString().split(".")[1].length;
          } catch (e) {
            r2 = 0;
          }
          m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度
          n = (r1 >= r2) ? r1 : r2;
          return ((arg1 * m - arg2 * m) / m).toFixed(n);
        }
    
        // 给Number类型增加一个sub方法,调用起来更加方便。
        Number.prototype.sub = function (arg) {
          return calculateSub(arg, this);
        };
    

    【3】乘法函数

        /**
         ** 乘法函数,用来得到精确的乘法结果
         ** 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。
         ** 调用:calculateMul(arg1,arg2)
         ** 返回值:arg1乘以 arg2的精确结果
         **/
        function calculateMul(arg1, arg2) {
          let m = 0,
            s1 = arg1.toString(),
            s2 = arg2.toString();
          try {
            m += s1.split(".")[1].length;
          } catch (e) {}
          try {
            m += s2.split(".")[1].length;
          } catch (e) {}
          return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
        }
    
        // 给Number类型增加一个mul方法,调用起来更加方便。
        Number.prototype.mul = function (arg) {
          return calculateMul(arg, this);
        };
    

    【4】除法函数

        /** 
         ** 除法函数,用来得到精确的除法结果
         ** 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
         ** 调用:calculateDiv(arg1,arg2)
         ** 返回值:arg1除以arg2的精确结果
         **/
        function calculateDiv(arg1, arg2) {
          let t1 = 0,
            t2 = 0,
            r1, r2;
          try {
            t1 = arg1.toString().split(".")[1].length;
          } catch (e) {}
          try {
            t2 = arg2.toString().split(".")[1].length;
          } catch (e) {}
          with(Math) {
            r1 = Number(arg1.toString().replace(".", ""));
            r2 = Number(arg2.toString().replace(".", ""));
            return (r1 / r2) * pow(10, t2 - t1);
          }
        }
    
        //给Number类型增加一个div方法,调用起来更加方便。
        Number.prototype.div = function (arg) {
          return calculateDiv(this, arg);
        };
    
  • 相关阅读:
    Maven——配置阿里云的镜像仓库
    Eclipse——配置代码提示
    设计模式——一步步实现《单例模式》
    JAVA实现二叉平衡树
    简单设计模式——状态模式 (召唤师峡谷版)
    简单设计模式——观察者模式
    JavaScript获取鼠拖动选中的内容(InMemories笔记)
    图解数据结构——二叉查找树/二叉排序树
    SpringMVC详细笔记(一) —— 几步快速搭建一个SpringMVC程序
    【Java虚拟机10】ClassLoader.getSystemClassLoader()流程简析
  • 原文地址:https://www.cnblogs.com/CandyDChen/p/16300638.html
Copyright © 2020-2023  润新知