慎用 Number.toFixed() 处理数字

发布于 2024-01-13 09:24:11 字数 4943 浏览 43 评论 0

最近在公司项目中碰到一个隐藏的 bug,调试许久才发现竟然是 toFixed 函数精度问题引起的,从而引发了我一系列的思考。我们都知道,计算机在二进制环境下,浮点数的计算精度会存在缺失问题,最经典的例子就是为什么 0.1+0.2 ​ 不等于 0.3?

image.png

遇到上述问题,我们自然而然会想到 toFixed 方法来四舍五入,可结果却差强人意!

toFixed() 的精度问题

我们来看一下 toFixed 在 chrome、火狐、IE 浏览器下的不同表现:

image.png

可以看到 toFixed 的四舍五入在 chrome、火狐上并不准确。
toFixed 在 chrome、火狐上也并不是网上流传甚广的用银行家舍入法来进行四舍五入的。

银行家舍入法的规则是“四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一”。

例如银行家舍入法在 (2.55).toFixed(1) = 2.5、(3.55).toFixed(1) = 3.5 上就不符合了。

翻阅 ecmascript 规范toFixed 的表述如下:

Number.prototype.toFixed

上面规范这段大概意思就是如果 toFixed 的入参小于 10 的 21 次方,那么就取一个整数 n,让 n*10^f - x 的精确值尽可能的趋近于 0,如果存在两个这样的 n,取较大的 n。这段话可能有点晦涩难懂,我们举个例子比如 1.335.toFixed(2)

image.png

上图例子中 1.335.toFixed(2)按照四舍六入五成双应该是 1.34,但是实际情况确实 1.33。这是因为 n=133 的时候让 n*10^f - x ​ 更趋近于 0,所以最后得到的结果是 1.33。

解决方法

1.重写 toFixed()

我们可以通过重写 toFixed 的方法,来实现四舍五入:

Number.prototype.toFixed = function (length) {
  var carry = 0 // 存放进位标志
  var num, multiple // num 为原浮点数放大 multiple 倍后的数,multiple 为 10 的 length 次方
  var str = this + '' // 将调用该方法的数字转为字符串
  var dot = str.indexOf('.') // 找到小数点的位置
  if (str.substr(dot + length + 1, 1) >= 5) carry = 1 // 找到要进行舍入的数的位置,手动判断是否大于等于 5,满足条件进位标志置为 1
  multiple = Math.pow(10, length) // 设置浮点数要扩大的倍数
  num = Math.floor(this * multiple) + carry // 去掉舍入位后的所有数,然后加上我们的手动进位数
  var result = num / multiple + '' // 将进位后的整数再缩小为原浮点数
  /*
   * 处理进位后无小数
   */
  dot = result.indexOf('.')
  if (dot < 0) {
    result += '.'
    dot = result.indexOf('.')
  }
  /*
   * 处理多次进位
   */
  var len = result.length - (dot + 1)
  if (len < length) {
    for (var i = 0; i < length - len; i++) {
      result += 0
    }
  }
  return result
}

该方法的大致思路是首先找到舍入位,判断该位置是否大于等于 5,条件成立手动进一位,然后通过参数大小将原浮点数放大 10 的参数指数倍,然后再将包括舍入位后的位数利用 Math.floor 全部去掉,根据我们之前的手动进位来确定是否进位。

2.high-precision-four-fundamental-rules

在 GitHub 上找到一个 高精度的基本四则运算 npm 包 ,用来弥补原生 JS 中 toFixed 方法计算精度缺失的不足,该作者用四舍五入算法重写了改方法,并封装成 npm 包!

// 安装
$ npm install high-precision-four-fundamental-rules --save
// 使用
import {add, subtract, multiply, divide} from 'high-precision-four-fundamental-rules';
add(1, 2, 4); // '3.0000'
subtract(1, 2, 3); // '-1.000';
multiply(1, 2, 2); // '2.00';
divide(1, 3, 7); // '0.3333333';

参考文章

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
22 人气
更多

推荐作者

13886483628

文章 0 评论 0

流年已逝

文章 0 评论 0

℡寂寞咖啡

文章 0 评论 0

笑看君怀她人

文章 0 评论 0

wkeithbarry

文章 0 评论 0

素手挽清风

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文