阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

JavaScript 中精度问题及解决思路汇总

80次阅读
没有评论

共计 1811 个字符,预计需要花费 5 分钟才能阅读完成。

导读 JavaScript 中的数字按照 IEEE 754 的标准,使用 64 位双精度浮点型来表示。其中符号位 S,指数位 E,尾数位 M 分别占了 1,11,52 位,并且在 ES5 规范 中指出了指数位 E 的取值范围是 [-1074, 971]。

JavaScript 中精度问题及解决思路汇总

精度问题汇总

想用有限的位来表示无穷的数字,显然是不可能的,因此会出现一些列精度问题:

  1. 浮点数精度问题,比如 0.1 + 0.2 !== 0.3
  2. 大数精度问题,比如 9999 9999 9999 9999 == 1000 0000 0000 0000 1
  3. toFixed 四舍五入结果不准确,比如 1.335.toFixed(2) == 1.33

浮点数精度和 toFixed 其实属于同一类问题,都是由于浮点数无法精确表示引起的,如下:

(1.335).toPrecision(20);    // "1.3349999999999999645"

而关于大数精度问题,我们可以先看下面这个代码片段:

// 能精确表示的整数范围上限,S 为 1 个 0,E 为 11 个 0,S 为 53 个 1
Math.pow(2, 53) - 1 === Number.MAX_SAFE_INTEGER    // true
// 能精确表示的整数范围下限,S 为 1 个 1,E 为 11 个 0,S 为 53 个 1
-(Math.pow(2, 53) - 1) === Number.MIN_SAFE_INTEGER    // true
// 能表示的最大数字,S 为 1 个 0,E 为 971,S 为 53 个 1
(Math.pow(2, 53) - 1) * Math.pow(2, 971) === Number.MAX_VALUE    // true
// 能表示的最接近于 0 的正数,S 为 1 个 0,E 为 -1074,S 为 0
Math.pow(2, -1074) === Number.MIN_VALUE // true

通过以上可以明白,[MIN_SAFE_INTEGER, MAX_SAFE_INTEGER] 的整数都可以精确表示,但是超出这个范围的整数就不一定能精确表示。这样就会产生所谓的大数精度丢失问题。

解决思路

首先考虑的是如何解决浮点数运算的精度问题,有 3 种思路:

  1. 考虑到每次浮点数运算的偏差非常小 (其实不然),可以对结果进行指定精度的四舍五入,比如可以 parseFloat(result.toFixed(12));
  2. 将浮点数转为整数运算,再对结果做除法。比如 0.1 + 0.2,可以转化为 (1*2)/3。
  3. 把浮点数转化为字符串,模拟实际运算的过程。

先来看第一种方案,在大多数情况下,它可以得到正确结果,但是对一些极端情况,toFixed 到 12 是不够的,比如:

210000 * 10000  * 1000 * 8.2    // 17219999999999.998
parseFloat(17219999999999.998.toFixed(12));    // 17219999999999.998,而正确结果为 17220000000000

上面的情况,如果想让结果正确,需要 toFixed(2),这显然是不可接受的。

再看第二种方案,比如 number-precision 这个库就是使用的这种方案,但是这也是有问题的,比如:

// 这两个浮点数,转化为整数之后,相乘的结果已经超过了 MAX_SAFE_INTEGER
123456.789 * 123456.789     // 转化为 (123456789 * 123456789)/1000000,结果是 15241578750.19052

所以,最终考虑使用第三种方案,目前已经有了很多较为成熟的库,比如 bignumber.js,decimal.js,以及 big.js 等。我们可以根据自己的需求来选择对应的工具。并且,这些库不仅解决了浮点数的运算精度问题,还支持了大数运算,并且修复了原生 toFixed 结果不准确的问题。

题外话

还有另外一个与 JavaScript 计算相关的问题,即 Math.round(x),它虽然不会产生精度问题,但是它有一点小陷阱容易忽略。下面是它的舍入的策略:

  1. 如果小数部分大于 0.5,则舍入到下一个绝对值更大的整数。
  2. 如果小数部分小于 0.5,则舍入到下一个绝对值更小的整数。
  3. 如果小数部分等于 0.5,则舍入到下一个正无穷方向上的整数。

所以,对 Math.round(-1.5),其结果为 -1,这可能不是我们想要的结果。

当然,上面提到的 big.js 等库,都提供了自己的 round 函数,并且可以指定舍入规则,以避免这个问题。

阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配

腾讯云新客低至 82 元 / 年,老客户 99 元 / 年

代金券:在阿里云专用满减优惠券

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-07-25发表,共计1811字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中