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

7次阅读

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

问题描述

后端返回 {spaceObject: { objectId: ‘1049564069045993472’} }

前端模版,使用的是 atpl 模版
<span id=”test” data-id=”<%= spaceObject.objectId %>”></span>

前端获取 objectId 的方式,const objectId = $(‘#test’).data(‘id’)

正常理解,我们获取到的 objectId 就是返回的 1049564069045993472,可是现实情况是这个 objectId 是 1049564069045993500

问题拆分
一,为什么从 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 )
2. 大整数丢失精度
99999999999999999 === 100000000000000000
3. toFxied 有些情况下不会四舍五入
(12.235).toFixed(2) // 12.23
数字精度丢失问题原因分析

首先,javascript 中保持精度不丢失的数值是有个范围的,是在 Number.MIN_SAFE_INTEGER 和 Number.MAX_SAFE_INTEGER 之间. Number.MAX_SAFE_INTEGER => 9007199254740991 => 2 的 53 次方 -1

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.1415926…,1.3333… 等。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。如图

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

使用 big.js 库

如果是小数加减可以通过先将所有小数转化为整数(乘倍数),然后完成运算,最后缩小回去(除倍数)。
0.01 + 0.2 // 0.21000000000000002
(0.01 * 100 + 0.2 * 100) / 100 // 0.21

参考

javascript 中不会失去精度的最大值
JS 数字精度丢失的一些典型问题

正文完
 0