JS 浮点数精度问题,以及 toFixed 的 bug

1.JS 浮点数精度问题

由于 JS 存在精度问题,所以以下运算结果与预期不符。

注:以下代码若无特殊说明,则均为谷歌浏览器下的运行结果。

0.56 * 100 // 56.00000000000001
0.1 + 0.2 // 0.30000000000000004
0.3 - 0.1 // 0.19999999999999998

所以不要用浮点数做以下判断

if (0.1 + 0.2 === 0.3) {}

当我们对浮点数做运算后要保留固定位小数时,也要进行处理

0.5623 * 100 // 预期 56.23, 实际 56.230000000000004

// 为预防此类情况, 需再做个四舍五入
(0.5623 * 100).toFixed(2) // 56.23

2.toFixed 的 bug

但这里要注意,toFixed 方法有 bug。比如

(12.245).toFixed(2) // 预期 12.25, 实际 12.24 

所以需要重写 toFixed 方法,这里我们不选择覆盖原有 toFixed,另定义一个 toFixed2

/**
 * 重写 toFixed 方法
 * @param {Number} d 保留的小数位数
*/
Number.prototype.toFixed2 = function (d) {
  let s = this + '';
  if (!d) d = 0;
  if (s.indexOf('.') == -1) s += '.';
  s += new Array(d + 1).join('0');
  if (new RegExp('^(-|\\+)?(\\d+(\\.\\d{0,' + (d + 1) + '})?)\\d*$').test(s)) {
    let s = '0' + RegExp.$2,
      pm = RegExp.$1,
      a = RegExp.$3.length,
      b = true;
    if (a == d + 2) {
      a = s.match(/\d/g);
      if (parseInt(a[a.length - 1]) > 4) {
        for (let i = a.length - 2; i >= 0; i--) {
          a[i] = parseInt(a[i]) + 1;
          if (a[i] == 10) {
            a[i] = 0;
            b = i != 1;
          } else break;
        }
      }
      s = a.join('').replace(new RegExp('(\\d+)(\\d{' + d + '})\\d$'), '$1.$2');
    }
    if (b) s = s.substr(1);
    return (pm + s).replace(/\.$/, '');
  }
  return this + '';
};

代码参考于:Js toFixed()四舍五入BUG的解决方法

再测试一下

(12.245).toFixed2(2) // 12.25

3.toFixed 的更多思考

关于 toFixed 进行舍入的奇怪现象,有人说它采用的不是四舍五入,而是银行家舍入

银行家舍入

银行家舍入的介绍可见百度百科:银行家舍入

简单理解为:四舍六入五考虑,五后非空就进一,五后为空看奇偶,五前为偶应舍去,五前为奇要进一

按这个思路,进行以下测试

(12.205).toFixed(2) // 预期 '12.21' 实际 '12.21'
(12.215).toFixed(2) // 预期 '12.22' 实际 '12.21'
(12.225).toFixed(2) // 预期 '12.22' 实际 '12.22'
(12.235).toFixed(2) // 预期 '12.24' 实际 '12.23'
(12.245).toFixed(2) // 预期 '12.24' 实际 '12.24'
(12.255).toFixed(2) // 预期 '12.26' 实际 '12.26'
(12.265).toFixed(2) // 预期 '12.26' 实际 '12.27'
(12.275).toFixed(2) // 预期 '12.28' 实际 '12.28'
(12.285).toFixed(2) // 预期 '12.28' 实际 '12.29'
(12.295).toFixed(2) // 预期 '12.30' 实际 '12.29'

可以发现,toFixed 并不遵循银行家舍入。而且在不同浏览器下运行的结果并不一致,比如在 IE 下是符合四舍五入规则的。

这里我们在谷歌下运行下面的代码,能发现一个有趣的现象

(2.245).toFixed(2) // 2.25
(12.245).toFixed(2) // 12.24
(112.245).toFixed(2) // 112.25
(1112.245).toFixed(2) // 1112.24
(11112.245).toFixed(2) // 11112.25
(111112.245).toFixed(2) // 111112.24

4.常用保留小数点舍入方法

  • Math.round(),四舍五入取整
  • Math.floor(),向下取整
  • Math.ceil(),向上取整
// 保留两位小数的写法
Math.round(12.245 * 100) / 100 // 12.25
Math.floor(12.245 * 100) / 100 // 12.24
Math.ceil(12.245 * 100) / 100 // 12.25

你可能感兴趣的:(javascript,javascript)