解决:浮点数(小数)数学运算精度有限&保留固定位数,金额数字如何千分位逗号分隔

一、数学运算

问题:0.1 + 0.2 = 0.30000000000000004

在JavaScript中,整数和浮点数都属于Number类型,它们都统一采用64位浮点数进行存储。因浮点数的精度有限,会出现精度丢失,舍入误差问题。

const operationObj = {
   /**
    * 处理传入的参数,不管传入的是数组还是以逗号分隔的参数都处理为数组
    * @param args
    * @returns {*}
    */
   getParam(args) {
      return Array.prototype.concat.apply([], args); // 若args为一维或二维数组[[2]], 结果都是一纬数组[2],但是三维数组,结果是二维数组...
   },

   /**
    * 获取每个数的乘数因子,根据小数位数计算
    * 1.首先判断是否有小数点,如果没有,则返回1;
    * 2.有小数点时,将小数位数的长度作为Math.pow()函数的参数进行计算
    * 例如2的乘数因子为1,2.01的乘数因子为100
    * @param x
    * @returns {number}
    */
   multiplier(x) {
      let parts = x.toString().split('.');
      return parts.length < 2 ? 1 : Math.pow(10, parts[1].length);
   },

   /**
    * 获取多个数据中最大的乘数因子
    * 例如1.3的乘数因子为10,2.13的乘数因子为100
    * 则1.3和2.13的最大乘数因子为100
    * @returns {*}
    */
   correctionFactor(...args) {
       // let args = Array.prototype.slice.call(arguments);
       let argArr = this.getParam(args);
       return argArr.reduce((accum, next) => {
           let num = this.multiplier(next);
           return Math.max(accum, num);
       }, 1);
   },

   /**
    * 加法运算
    * @param args
    * @returns {number}
    */
   add(...args) {
       let calArr = this.getParam(args);
       // 获取参与运算值的最大乘数因子
       let corrFactor = this.correctionFactor(calArr);
       let sum = calArr.reduce((accum, curr) => {
           // 将浮点数乘以最大乘数因子,转换为整数参与运算
           return accum + Math.round(curr * corrFactor);
       }, 0);
       // 除以最大乘数因子
       return sum / corrFactor;
   },

   /**
    * 减法运算
    * @param args
    * @returns {number}
    */
   subtract(...args) {
       let calArr = this.getParam(args);
       let corrFactor = this.correctionFactor(calArr);
       let diff = calArr.reduce((accum, curr, curIndex) => {
          // reduce()函数在未传入初始值时,curIndex从1开始,第一位参与运算的值需要
          // 乘以最大乘数因子
          if (curIndex === 1) {
              return Math.round(accum * corrFactor) - Math.round(curr * corrFactor);
          }
          // accum作为上一次运算的结果,就无须再乘以最大因子
          return Math.round(accum) - Math.round(curr * corrFactor);
       });
     // 除以最大乘数因子
       return diff / corrFactor;
   },

   /**
    * 乘法运算
    * @param args
    * @returns {*}
    */
   multiply(...args) {
      let calArr = this.getParam(args);
      let corrFactor = this.correctionFactor(calArr);
      calArr = calArr.map((item) => {
          // 乘以最大乘数因子
          return item * corrFactor;
      });
      let multi = calArr.reduce((accum, curr) => {
          return Math.round(accum) * Math.round(curr);
      }, 1);
      // 除以最大乘数因子
      return multi / Math.pow(corrFactor, calArr.length);
   },

   /**
    * 除法运算
    * @param args
    * @returns {*}
    */
   divide(...args) {
       let calArr = this.getParam(args);
       let quotient = calArr.reduce((accum, curr) => {
           let corrFactor = this.correctionFactor(accum, curr);
           // 同时转换为整数参与运算
           return Math.round(accum * corrFactor) / Math.round(curr * corrFactor);
       });
       return quotient;
   }
};

二、保留小数位为指定位数

在JavaScript中,toFixed函数有时也会导致一些四舍五入问题。这是因为JavaScript中的数字使用IEEE 754浮点数表示,存在精度有限的问题。

如:1.525.toFixed(2) 为1.52,而不是 1.53。

/**
 * 小数:四舍五入到指定位数
 * @param number { Number } 小数
 * @param decimalPlaces { Number } 需要保留的小数位数
 * @param isRemoveTrailingZeros { Boolean } 是否去除尾部的0, // 如:3.00,为3
 * @returns {number}
 */
export function roundToFixed(number, decimalPlaces = 2, isRemoveTrailingZeros = true) {
  const factor = 10 ** decimalPlaces; // **是幂运算符,左边是底数,右边是指数,即 2**3 = 8
  const result =  Math.round(number * factor) / factor;
  if (isRemoveTrailingZeros) {
    return Number(result)
  } else { 
    return Number(result).toFixed(decimalPlaces); // 若不够指定位数,自动末尾补0
  }
}

其他扩展:

1. 金额数字,自动千分位逗号分隔展示

参考网址:Intl.NumberFormat数字格式化应用 - 知乎

// 货币的相关处理
const IntlInstance = new Intl.NumberFormat("zh-CN", {
  style: "currency",
  currency: "CNY", // 人名币
  currencySign: "accounting", // 设置记账方式,会自动补全2位小数点
});

console.log(IntlInstance.format(0.1+0.2)); // ¥0.30
console.log(IntlInstance.format(0.1+0.2)); // ¥1.53
console.log(IntlInstance.format(9999999888.0)); // ¥9,999,999,888.00

猜你喜欢

转载自blog.csdn.net/qq_38969618/article/details/132098031