资讯专栏INFORMATION COLUMN

JavaScript 精度丢失问题

iOS122 / 1550人阅读

摘要:排除直接使用的数太大或太小超出范围,出现这种问题的情况基本是浮点数的小数部分在转成二进制时丢失了精度,所以我们可以将小数部分也转换成整数后再计算。

// 1. 两数相加
// 0.1 + 0.2 = 0.30000000000000004
// 0.7 + 0.1 = 0.7999999999999999
// 0.2 + 0.4 = 0.6000000000000001
// 2.22 + 0.1 = 2.3200000000000003

// 2. 两数相减
// 1.5 - 1.2 = 0.30000000000000004
// 0.3 - 0.2 = 0.09999999999999998

// 3. 两数相乘
// 19.9 * 100 = 1989.9999999999998
// 19.9 * 10 * 10 = 1990
// 1306377.64 * 100 = 130637763.99999999
// 1306377.64 * 10 * 10 = 130637763.99999999
// 0.7 * 180 = 125.99999999999999

// 4. 不一样的数却相等
// 1000000000000000128 === 1000000000000000129

计算机的底层实现就无法完全精确表示一个无限循环的数,而且能够存储的位数也是有限制的,所以在计算过程中只能舍去多余的部分,得到一个尽可能接近真实值的数字表示,于是造成了这种计算误差。

比如在 JavaScript 中计算0.1 + 0.2时,十进制的0.1和0.2都会被转换成二进制,但二进制并不能完全精确表示转换结果,因为结果是无限循环的。

// 百度进制转换工具
0.1 -> 0.0001100110011001...
0.2 -> 0.0011001100110011...

In JavaScript, Number is a numeric data type in the double-precision 64-bit floating point format (IEEE 754). In other programming languages different numeric types can exist, for examples: Integers, Floats, Doubles, or Bignums.

根据 MDN这段关于Number的描述 可以得知,JavaScript 里的数字是采用 IEEE 754 标准的 64 位双精度浮点数。该规范定义了浮点数的格式,最大最小范围,以及超过范围的舍入方式等规范。所以只要不超过这个范围,就不会存在舍去,也就不会存在精度问题了。比如:

// Number.MAX_SAFE_INTEGER 是 JavaScript 里能表示的最大的数了,超出了这个范围就不能保证计算的准确性了
var num = Number.MAX_SAFE_INTEGER;
num + 1 === num +2 // = true

实际工作中我们也用不到这么大的数或者是很小的数,也应该尽量把这种对精度要求高的计算交给后端去计算,因为后端有成熟的库来解决这个计算问题。前端虽然也有类似的库,但是前端引入一个这样的库代价太大了。

mathjs

decimal.js

排除直接使用的数太大或太小超出范围,出现这种问题的情况基本是浮点数的小数部分在转成二进制时丢失了精度,所以我们可以将小数部分也转换成整数后再计算。网上很多帖子贴出的解决方案就是这种:

var num1 = 0.1
var num2 = 0.2
(num1 * 10 + num2 * 10) / 10 // = 0.3

但是这样转换整数的方式也是一种浮点数计算,在转换的过程中就可能存在精度问题,比如:

1306377.64 * 10 // = 13063776.399999999
1306377.64 * 100 // = 130637763.99999999
var num1 = 2.22;
var num2 = 0.1;
(num1 * 10 + num2 * 10) / 10 // = 2.3200000000000003

所以不要直接通过计算将小数转换成整数,我们可以通过字符串操作,移动小数点的位置来转换成整数,最后再同样通过字符串操作转换回小数:

/**
 * 通过字符串操作将一个数放大或缩小指定倍数
 * @num 被转换的数
 * @m   放大或缩小的倍数,为正表示小数点向右移动,表示放大;为负反之
 */
function numScale(num, m) {
  // 拆分整数、小数部分
  var parts = num.toString().split(".");
  // 原始值的整数位数
  const integerLen = parts[0].length;
  // 原始值的小数位数
  const decimalLen = parts[1] ? parts[1].length : 0;
  
  // 放大,当放大的倍数比原来的小数位大时,需要在数字后面补零
  if (m > 0) {
    // 补多少个零:m - 原始值的小数位数
    let zeros = m - decimalLen;
    while (zeros > 0) {
      zeros -= 1;
      parts.push(0);
    }
  // 缩小,当缩小的倍数比原来的整数位大时,需要在数字前面补零
  } else {
    // 补多少个零:m - 原始值的整数位数
    let zeros = Math.abs(m) - integerLen;
    while (zeros > 0) {
      zeros -= 1;
      parts.unshift(0);
    }
  }

  // 小数点位置,也是整数的位数: 
  //    放大:原始值的整数位数 + 放大的倍数
  //    缩小:原始值的整数位数 - 缩小的倍数
  var index = integerLen + m;
  // 将每一位都拆到数组里,方便插入小数点
  parts = parts.join("").split("");
  // 当为缩小时,因为可能会补零,所以使用原始值的整数位数
  // 计算出的小数点位置可能为负,这个负数应该正好是补零的
  // 个数,所以小数点位置应该为 0
  parts.splice(index > 0 ? index : 0, 0, ".");

  return parseFloat(parts.join(""));
}
/**
 * 获取小数位数
 */
function getExponent(num) {
  return Math.floor(num) === num ?
    0 : num.toString().split(".")[1].length;
}

/**
 * 两数相加
 */
function accAdd(num1, num2) {
  const multiple = Math.max(getExponent(num1), getExponent(num2));
  return numScale(numScale(num1, multiple) + numScale(num2, multiple), multiple * -1);
}

测试用例:

describe("accAdd", function() {
  it("(0.1, 0.2) = 0.3", function() {
    assert.strictEqual(0.3, _.accAdd(0.1, 0.2))
  })
  it("(2.22, 0.1) = 2.32", function() {
    assert.strictEqual(2.32, _.accAdd(2.22, 0.1))
  })
  it("(11, 11) = 22", function() {
    assert.strictEqual(22, _.accAdd(11, 11))
  })
})

代码地址:https://github.com/xiaoyann/b...

https://developer.mozilla.org...

JavaScript 中小数和大整数的精度丢失

JavaScript 中的数字

计算机是怎么存储数字的

为什么0.1无法被二进制小数精确表示?

为什么0.1+0.2=0.30000000000000004而1.1+2.2=3.3000000000000003?

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91186.html

相关文章

  • 从一个 bug 看 javascript精度丢失问题

    摘要:就像一些无理数不能有限表示,如圆周率,等。遵循规范,采用双精度存储,占用。参考中不会失去精度的最大值数字精度丢失的一些典型问题 问题描述 后端返回 { spaceObject: { objectId: 1049564069045993472 } } 前端模版,使用的是 atpl 模版 前端获取 objectId 的方式,const objectId = $(#test).da...

    NusterCache 评论0 收藏0
  • 探寻 JavaScript 精度问题以及解决方案

    摘要:推导为何等于在中所有数值都以标准的双精度浮点数进行存储的。先来了解下标准下的双精度浮点数。精度位总共是,因为用科学计数法表示,所以首位固定的就没有占用空间。验证完成的最大安全数是如何来的根据双精度浮点数的构成,精度位数是。 阅读完本文可以了解到 0.1 + 0.2 为什么等于 0.30000000000000004 以及 JavaScript 中最大安全数是如何来的。 十进制小数转为二...

    YanceyOfficial 评论0 收藏0
  • [ JS 基础 ] JS 浮点数四则运算精度丢失问题 (3)

    摘要:基于这个问题运动基础问题,我想应该也有一部分人没有认真对待过中浮点数的四则运算出现的问题。解决方案引自解决方案为了解决浮点数运算不准确的问题,在运算前我们把参加运算的数先升级的的次方到整数,等运算完后再降级的的次方。 基于这个问题:javascript运动基础问题 ,我想应该也有一部分人没有认真对待过js中浮点数的四则运算出现的问题。 1.问题描述 示例代码: var x ...

    hoohack 评论0 收藏0
  • 「干货」细说 Javascript 中的浮点数精度丢失问题(内附好课推荐)

    摘要:前言最近,朋友问了我这样一个问题在中的运算结果,为什么是这样的虽然我告诉他说,这是由于浮点数精度问题导致的。由于可以用阶码移动小数点,因此称为浮点数。它的实现遵循标准,使用位精度来表示浮点数。 showImg(https://segmentfault.com/img/remote/1460000018981071); 前言 最近,朋友 L 问了我这样一个问题:在 chrome 中的运算...

    senntyou 评论0 收藏0
  • 数据精度问题自查手册

    摘要:前言在数据敏感的业务场景中,常常会碰到数据精度问题,尤其在金额显示占比统计等地方,该问题尤为显著。计算机原理真香数值的精度问题,其实是非常基础的计算机原理知识。前言 在数据敏感的业务场景中,常常会碰到数据精度问题,尤其在金额显示、占比统计等地方,该问题尤为显著。由于数据的每一位有效数字都包含真实的业务语义,一点点偏差甚至可能影响业务决策,这让问题的严重性上升了几个阶梯。 那,什么是精度丢失...

    liangzai_cool 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<