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

wangfulin

问题描述

  1. 后端返回 { spaceObject: { objectId: '1049564069045993472' } }
  2. 前端模版,使用的是 atpl 模版

    <span id="test" data-id="<%= spaceObject.objectId %>"></span>
  3. 前端获取 objectId 的方式,const objectId = $('#test').data('id')
  4. 正常理解,我们获取到的 objectId 就是返回的 1049564069045993472,可是现实情况是这个 objectId1049564069045993500

问题拆分

一,为什么从 dom 中获取的字符串会变成数字

查看 zepto 代码可知,由于通过 $('#test').data('id') 获取到的字符串 '1049564069045993472' 经过 deserializeValue 方法之后就变成数字了。

关键代码如下:

data: function (name, value) {
      //[Opt:C]将原本在父级作用域的变量转移至局部变量
      var capitalRE = /([A-Z])/g,
        data = this.attr('data-' + name.replace(capitalRE, '-$1').toLowerCase(), value)
      return data !== null ? deserializeValue(data) : undefined
    },

// "true"  => true
  // "false" => false
  // "null"  => null
  // "42"    => 42
  // "42.5"  => 42.5
  // "08"    => "08"
  // JSON    => parse if valid
  // String  => self
function deserializeValue(value) {
    var num
    try {
      return value ?
        value == "true" ||
        ( value == "false" ? false :
          value == "null" ? null :
            !/^0/.test(value) && !isNaN(num = Number(value)) ? num :
              /^[\[\{]/.test(value) ? $.parseJSON(value) :
                value )
        : value
    } catch (e) {
      return value
    }
  }

二,为什么数字跟 dom 中获取的不一致

由于javascript的能够保持精度的最大值是 9007199254740991,所以由于上面那个数字大于这个最大安全数,所以会出现失去精度的问题。

引申

javascript 中精度丢失的几种情况

1. 简单的浮点数相加

0.1 + 0.2 !== 0.3 // true
0.1 + 0.2 === 0.3 // false
add-example.png )

2. 大整数丢失精度

99999999999999999 === 100000000000000000
big-example.png

3. toFxied 有些情况下不会四舍五入

(12.235).toFixed(2) // 12.23
toFixed-example.png

数字精度丢失问题原因分析

  1. 首先,javascript 中保持精度不丢失的数值是有个范围的,是在 Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER 之间. Number.MAX_SAFE_INTEGER => 9007199254740991 => 2的53次方-1
  2. ECMA Section 8.5 - Numbers Note that all the positive and negative integers whose magnitude is no greater than 253 are representable in the Number type (indeed, the integer 0 has two representations, +0 and −0).
  3. 计算机的二进制实现和位数限制有些数无法有限表示。就像一些无理数不能有限表示,如 圆周率 3.1415926...,1.3333... 等。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。如图

precision-example.png

* 1位用来表示符号位
* 11位用来表示指数
* 52位表示尾数
  1. 深入了解

解决方案

  1. 使用 big.js库
  2. 如果是小数加减可以通过先将所有小数转化为整数(乘倍数),然后完成运算,最后缩小回去(除倍数)。

    0.01 + 0.2 // 0.21000000000000002
    (0.01 * 100 + 0.2 * 100) / 100 // 0.21

solution-example.png

参考

  1. javascript 中不会失去精度的最大值
  2. JS数字精度丢失的一些典型问题
阅读 2.3k

记录技术的点滴
记录一些自己正在学习的知识
6.1k 声望
103 粉丝
0 条评论
你知道吗?

6.1k 声望
103 粉丝
宣传栏