JavaScript 0.1加0.2不等于0.3的解决方案

440 阅读3分钟

在这里我们分享WEB前端的相关技术文章、学习资源、热点资讯、面试难点等内容。期待你的建议和指正。 期待和你在前端的世界中一起学习,获得更多成长!

公众号

续上篇 为何 0.1 加 0.2 不等于 0.3 ???  我们知道了为何0.1+0.2≠0.3,发现了问题,那么这次我们来解决问题。

分析问题

在解决问题之前,我们先来弄清楚问题的根源。

问题的根源,整数是因为数据太大;小数是因为小数在转换成二进制时出现了无效循环的情况,由于存储位数限制因此存在“舍去”,精度丢失就发生了。

详细请看上一篇文章 JavaScript的数字存储

解决方案

对于整数,前端出现问题的几率可能比较低,毕竟很少有业务需要需要用到超大整数,只要运算结果不超过 Math.pow(2, 53) 就不会丢失精度。

对于小数,前端出现问题的几率还是很多的,尤其在一些电商网站涉及到金额等数据。解决方式:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)。

例子

计算小数:0.1 + 0.02

计算方案:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)。

计算过程

第一步把小数扩大成整数

0.1  扩大10倍  变成 1

0.02 扩大100倍 变成 2

第二步计算

1 * 100 ÷10 + 2 = 12

第三步缩小倍数(除以最大倍数)

12 ÷ 100 = 0.12

计算过程我们大概已经理清了,现在我们使用代码实现整个过程

代码实现

分析以上三个步骤,我们的代码实现分为两步。

第一步

为通过两个数字获取两个整数值,两个倍数值,以及最大倍数值。

例如:

入参为0.1 和0.02  

则返回值应该是0.1的整数值1 ,倍数为10;0.02的整数值2,倍数值100。

代码实现如下

  /**
   * 判断num是否为一个整数
   * @param {number|string|null} value
   */
  isInteger = (value) => {
    if (isNaN(value)) return false;
    const num = +value;
​
    return Math.floor(num) === num;
  };  
​
  /**
   * 将一个浮点数转成整数,返回整数和倍数
   * @param {number|string|null} floatNum 如:3.14
   * @returns {object} 如:{times:100, num: 314}
   */
toInteger = (floatNum) => {
    const ret = { times: 1, num: 0 };
    if (this.isInteger(floatNum)) {
      ret.num = floatNum;
      return ret;
    }
    const strfi = `${floatNum}`;
    const dotPos = strfi.indexOf('.');
    const len = strfi.substr(dotPos + 1).length;
    const times = 10 ** len;
    ret.times = times;
    ret.num = +strfi.replace('.', '');
​
    return ret;
  };

第二步,使用整数值以及倍数计算结果

代码如下

    add = (prevValue, nextValue) => {
    let result = null;
    //初始化
    const objectNumberA = this.toInteger(prevValue);
    const objectNumberB = this.toInteger(nextValue);
    const numberA = objectNumberA.num;
    const numberB = objectNumberB.num;
    const timesA = objectNumberA.times;
    const timesB = objectNumberB.times;
    const timesMax = timesA > timesB ? timesA : timesB;
​
    //计算加法
    if (timesA === timesB) {
      // 两个小数位长度相同
      result = numberA + numberB;
    } else if (timesA > timesB) {
      // numberA 小数位长度 大于 numberB
      result = numberA + numberB * (timesA / timesB);
    } else {
      // numberA 小数位长度 小于 numberB
      result = numberA * (timesB / timesA) + numberB;
    }
​
    //缩小并返回值
    return result / timesMax;
  };

代码测试结果

这样我们就小数加法丢失精度的问题。

同理我们可以实现减法,乘法,除法。具体实现代码入下,建议在有加法的基础上试试自己实现。

减法

subtract = (prevValue, nextValue) => {
    let result = null;
    //初始化
    const objectNumberA = this.toInteger(prevValue);
    const objectNumberB = this.toInteger(nextValue);
    const numberA = objectNumberA.num;
    const numberB = objectNumberB.num;
    const timesA = objectNumberA.times;
    const timesB = objectNumberB.times;
    const timesMax = timesA > timesB ? timesA : timesB;
    
    //计算减法
    const { numberA, numberB, timesA, timesB, timesMax } = calcuNumber;
    if (timesA === timesB) {
      result = numberA - numberB;
    } else if (timesA > timesB) {
      result = numberA - numberB * (timesA / timesB);
    } else {
      result = numberA * (timesB / timesA) - numberB;
    }
    
    //缩小并输出
    return result / timesMax;
  };

乘法

multiply = (prevValue, nextValue) => {
   //初始化 
     const objectNumberA = this.toInteger(prevValue);
    const objectNumberB = this.toInteger(nextValue);
    const numberA = objectNumberA.num;
    const numberB = objectNumberB.num;
    const timesA = objectNumberA.times;
    const timesB = objectNumberB.times;
    const timesMax = timesA > timesB ? timesA : timesB;
    
    //计算乘法,缩小并输出
    return (numberA * numberB) / (timesA * timesB);
  };

除法

 divide = (prevValue, nextValue) => {
    //初始化 
     const objectNumberA = this.toInteger(prevValue);
    const objectNumberB = this.toInteger(nextValue);
    const numberA = objectNumberA.num;
    const numberB = objectNumberB.num;
    const timesA = objectNumberA.times;
    const timesB = objectNumberB.times;
    const timesMax = timesA > timesB ? timesA : timesB;
    
    //计算除法,缩小并输出
    return (numberA / numberB) * (timesB / timesA);
  };

我们发现初始化代码会有重复,我们可以抽出来做成共用方法,这一步交给你们了。

为你推荐

JavaScript 为何0.1+0.2≠0.3

最后听一首悦耳的歌放松放松,回忆学到的东西。

点击下面​播放音乐


助力宝贝回家

微信公众号回复 加群 一起学习。

全部代码


  /**
   * 判断num是否为一个整数
   * @param {number|string|null} value
   */
  isInteger = (value: any) => {
    if (isNaN(value)) return false;
    const num = +value;

    return Math.floor(num) === num;
  };

  /**
   * 将一个浮点数转成整数,返回整数和倍数
   * @param {number|string|null} floatNum 如: 3.14
   * @returns {object} 如:{times:100, num: 314}
   */
  toInteger = (floatNum: any) => {
    const ret = { times: 1, num: 0 };
    if (this.isInteger(floatNum)) {
      ret.num = floatNum;

      return ret;
    }
    const strfi = `${floatNum}`;
    const dotPos = strfi.indexOf('.');
    const len = strfi.substr(dotPos + 1).length;
    const times = 10 ** len;
    ret.times = times;
    ret.num = +strfi.replace('.', '');

    return ret;
  };

  /**
   * 初始化算术计算数值
   * @param {number|string|null} prevValue
   * @param {number|string|null} nextValue
   */
  initCalcuNumber = (prevValue: any, nextValue: any) => {
    const numA = Number(prevValue);
    const numB = Number(nextValue);
    if (isNaN(numA) || isNaN(numB)) {
      return null;
      // throw Error(`method of initCalcuNumber's param is illigal`);
    }
    const objectNumberA = this.toInteger(numA);
    const objectNumberB = this.toInteger(numB);
    const numberA = objectNumberA.num;
    const numberB = objectNumberB.num;
    const timesA = objectNumberA.times;
    const timesB = objectNumberB.times;
    const timesMax = timesA > timesB ? timesA : timesB;

    return { numberA, numberB, timesA, timesB, timesMax };
  };

  /**
   * 加
   * @param {number|string|null} prevValue
   * @param {number|string|null} nextValue
   */
  add = (prevValue: number, nextValue: number): number => {
    let result = null;
    const calcuNumber = this.initCalcuNumber(prevValue, nextValue);
    if (calcuNumber === null) {
      return 0;
    }
    const { numberA, numberB, timesA, timesB, timesMax } = calcuNumber;
    if (timesA === timesB) {
      // 两个小数位长度相同
      result = numberA + numberB;
    } else if (timesA > timesB) {
      // numberA 小数位长度 大于 numberB
      result = numberA + numberB * (timesA / timesB);
    } else {
      // numberA 小数位长度 小于 numberB
      result = numberA * (timesB / timesA) + numberB;
    }

    return result / timesMax;
  };

  /**
   * 减
   * @param {number|string|null} prevValue
   * @param {number|string|null} nextValue
   */
  subtract = (prevValue: number, nextValue: number): number => {
    let result = null;
    const calcuNumber = this.initCalcuNumber(prevValue, nextValue);
    if (calcuNumber === null) {
      return 0;
    }
    const { numberA, numberB, timesA, timesB, timesMax } = calcuNumber;
    if (timesA === timesB) {
      result = numberA - numberB;
    } else if (timesA > timesB) {
      result = numberA - numberB * (timesA / timesB);
    } else {
      result = numberA * (timesB / timesA) - numberB;
    }

    return result / timesMax;
  };

  /**
   * 乘
   * @param {number|string|null} prevValue
   * @param {number|string|null} nextValue
   */
  multiply = (prevValue: number, nextValue: number): number => {
    const calcuNumber = this.initCalcuNumber(prevValue, nextValue);
    if (calcuNumber === null) {
      return 0;
    }
    const { numberA, numberB, timesA, timesB } = calcuNumber;

    return (numberA * numberB) / (timesA * timesB);
  };

  /**
   * 除
   * @param {number|string|null} prevValue
   * @param {number|string|null} nextValue
   */
  divide = (prevValue: number, nextValue: number): number => {
    const calcuNumber = this.initCalcuNumber(prevValue, nextValue);
    if (calcuNumber === null) {
      return 0;
    }
    const { numberA, numberB, timesA, timesB } = calcuNumber;

    return (numberA / numberB) * (timesB / timesA);
  };