likes
comments
collection
share

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法

作者站长头像
站长
· 阅读数 7

前言

很多时候,我们在程序中计算数字,得到的结果也许并不和我们想象得一样,在我们大多数人的认知里几乎都是四舍五入法,但是程序中所呈现的好像并不是我们想要的结果。 今天就谈谈程序中的那匪夷所思得银行家舍入法(也会涉及到数字精度问题)

什么是银行家舍入法

银行家舍入法,也称为四舍六入五留双或四舍六入五成双,是一种在计算机科学和金融领域广泛使用的舍入方法。

具体操作步骤如下:

  1. 如果被修约的数字小于5,则直接舍去;
  2. 如果被修约的数字大于5,则进行进位;
  3. 如果被修约的数字等于5,则需要查看5前面的数字。如果5前面的数字是奇数,则进位;如果5前面的数字是偶数,则舍去5,即修约后末尾数字都成为偶数。特别需要注意的是,如果5的后面还有不为0的任何数,则无论5的前面是奇数还是偶数,均应进位。

以上可以看出银行家舍入法得规则,当为5时,并不是所有得都会向前进一位,所以就可以知道5.225.toFixed(2)为什么不等于5.23了

举例

在浏览器的控制台中,我们可以试着打印一下

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法 这个时候我们可以看到,哎,好像是符合我们的所认知得四舍五入法了,但是紧接着

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法 这里看出,怎么又变成这样的了,这还是银行家舍入法呀,为了更严谨再试一下5前面为奇数时得结果

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法 这里结果又变了,反而是整数大于等于4得正常了,但是小于4得又有些失常了,反而整数为1得总是按照咱们预想的结果在进行,这种结果让我大脑一片混乱,所以这到底是什么原因,导致结果不像是银行家舍入法,也不像是四舍五入法

在我掉了一花西币的头发后,终于想通了,是程序中的精度问题,我们所写的数字并不是表面那么纯粹,再次打印一下看看

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法 现在可以清楚看出,我们所写的简单的数字后面并不见简单,之所以1.235和1.225使用toFiexd的时候都准确的四舍五入了,都是因为他的后面是多出来了0.0000000000几的数字,然而2.235就没有那么幸运了,所以2.235的0.005就被舍弃了!

解决方法

先说一种可行但不完全可行的解决方法,就是使用Math.round()。 首先这个方法确实是js中提供的真正含义上的四舍五入的方法。

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法 哎,这么一看,确实可行,既然简单的可以,那我们就试着进行复杂运算一下,再保留一下两位小数试试看

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法 呕吼,错了,按我们正常来算应该是9.77,但却得到了9.76。 要知道程序中存在着精度问题,再我们算来这个式子的结果应该是9.765,但是在程序看来

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法 可以说是无限趋近于9.765但还没有达到,然后就在Math.round这个方法中给舍弃掉了,这个方法似乎不完全可行

那么另外一招就是可行但有隐式风险的方式,就是在我们所算出来的结果后面添加0.0000000001,这样再让我们看一下结果

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法 这样可以看出,无论使用哪种方法,都能达到我们所需的结果了,即使使用toFixed有了银行家舍入法的规则,依旧可以按我们所想的一样进行四舍五入,因为当我们加了0.000000001后,即使最后一位等于5了,5后面还有数字,它就会向前进一位,那如果说加了这0.000000001正好等于5然后又触发了银行家舍入法的规则,那只能说算你倒霉,这就是我说为什么会有隐式风险,有风险但很小。

当然还有一个方法就是自己写一个方法来解决这个问题

//有的时候也许传的参数就是计算过后的,无线趋近于5的数,可以根据需求来判断是否传入第二个参数
Number.prototype.myToFixed = function (n, d) {
  //进来之后转为字符串 字符串不存在精度问题
  const str = this.toString();
  const dotIndex = str.indexOf(".");
  //如果没有小数点传进来的就是整数,直接使用toFixed传出去
  if (dotIndex === -1) {
    return this.toFixed(n);
  }
  //当为小数的时候
  const intStr = str.substring(0, dotIndex);
  const decStr = str.substring(dotIndex + 1, str.length).split("");
  //当大于5时,就进一
  if (decStr[n] >= 5) {
    decStr[n - 1] = Number(decStr[n - 1]) + 1;
    const dec = decStr.slice(0, n).join("");
    return `${intStr}.${dec}`;
  } else {
    //否则小于五时 先判断是否有第二个参数
    if (d) {
      //如果有就截取到第二个参数的位置
      const newDec = decStr.splice(n, n + d);
      let nineSum = 0;
      //遍历循环有多少个9
      for (let index = 0; index < newDec.length; index++) {
        if (index != 0 && newDec[index] == 9) {
          nineSum++;
        }
      }
      //判断四舍五入后面的位置 是否为四 并且是否除了4之后全是9 或者 9的位数大于第二个传的参数
      if (newDec[0] == 4 && (nineSum >= newDec.length - 2 || nineSum >= d)) {
        //条件成立 就按5进一
        decStr[n - 1] = Number(decStr[n - 1]) + 1;
        const dec = decStr.slice(0, n).join("");
        return `${intStr}.${dec}`;
      } else {
        //不成立则舍一
        const dec = decStr.slice(0, n).join("");
        return `${intStr}.${dec}`;
      }
    } else {
      //没有第二个参数,小于五直接舍一
      const dec = decStr.slice(0, n).join("");
      return `${intStr}.${dec}`;
    }
  }
};

我们再进行测试一下

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法

为什么5.225.toFixed(2)!=5.23,令人摸不着头脑的银行家舍入法 这样就是我们想要的结果了

总结

在程序中,银行家舍入法和数字的精度问题很多时候都会遇见,不论前端还是后端,然而处理这些数据也是比较头疼的事,我所讲的这些也许不能满足所有情况,但大多数情况都是可以处理的。

如果是相对于银行里这种对数字比较敏感的环境,这些参数的处理还需要更加谨慎的处理

写的如有问题,欢迎提出建议

转载自:https://juejin.cn/post/7280430881952759862
评论
请登录