先记住这个答案
JavaScript的Number类型基于IEEE 754双精度浮点数格式,其数值的有效二进制位数为53位,因此安全整数范围是[-9007199254740991, 9007199254740991]。超出该范围,并非每个整数都能精确表示,比如Number.MAX_SAFE_INTEGER+1和+2结果为同一值。若需处理更大或需要精确的整数,应使用BigInt,其通过n后缀或BigInt()构造,支持任意精度。可用Number.isSafeInteger()检查数值是否在安全范围内。
- 安全整数上限是9007199254740991。
- 超出后整数比较可能相等。
- BigInt提供任意精度整数。
双精度结构决定安全范围
JavaScript的Number实现遵循IEEE 754双精度标准,由1位符号、11位指数和52位尾数组成。但规格化数值的尾数最高位固定为1,所以有效精度实际为53位。这意味着当数值增长到2^53时,尾数仅能区分步长为2的整数。
因此,从-2^53+1到2^53-1的每个整数都能被精确表示,超出该区间后,连续整数开始出现空隙。例如,Number.MAX_SAFE_INTEGER值为9007199254740991,它加1得到2^53,加2得到2^53+1,但后者因无法用尾数表达而被舍入为2^53,于是两个加法结果相等。
console.log(Number.MAX_SAFE_INTEGER);
const a = Number.MAX_SAFE_INTEGER + 1;
const b = Number.MAX_SAFE_INTEGER + 2;
console.log(a);
console.log(b);
console.log(a === b);
console.log(Number.isSafeInteger(a));查看输出与解释
9007199254740991
9007199254740992
9007199254740992
true
falsea与b均被舍入为2^53,因此相等;且a超出安全范围,isSafeInteger返回false。
处理超过安全范围的ID
假设某电商平台订单号采用雪花算法,生成64位长整型主键。当前部分订单号已超过9007199254740991。前端使用JSON解析响应时,若技术栈将{"id": 9007199254740992}交给JavaScript,运行时会把该字面量直接存为Number,但实际值已与相邻ID重合。这可能导致用户点开订单详情时请求到错误记录。
解决方案是让后端将订单号序列化为字符串,前端接受字符串后,如需进行数值比较或运算,可使用BigInt类型。例如const id = BigInt("9007199254740992"),它在运算时不会丢精度。若不希望改后端,也可在前端用JSON.parse时使用reviver函数将超长ID转为字符串或BigInt。
安全范围判断与可操作边界
虽然Number.MAX_SAFE_INTEGER以外的数值仍是合法且可比较的浮点数,但它们已不具备整数的连续性。多次加减或乘法可能跨越不可表示点,例如9007199254740992 + 2若直接计算会因步长2而得到9007199254740994,但+1则原地踏步。这类问题只在值域超过安全范围时出现。
处理该类场景的可靠方式是主动调用Number.isSafeInteger()进行防御性检查。若返回false且业务要求精确整数,应改用BigInt进行运算,或使用字符串传递数据。注意BigInt与Number不能隐式混用,加法会抛出TypeError,必须通过Number()或BigInt()显式转换,而这种转换本身也可能有精度损失。
容易答错的地方
- 超过MAX_SAFE_INTEGER就完全无法表示
- 事实上双精度仍能表示更大的数,如
Number.MAX_VALUE约1.8e308,只是整数步长增大,每个整数不再唯一。Number.MAX_SAFE_INTEGER只是能保证精确整数运算的最大值。 - 混淆MAX_VALUE与MAX_SAFE_INTEGER
Number.MAX_VALUE是最大有限浮点数,约1.7976931348623157e308;而Number.MAX_SAFE_INTEGER是2^53-1,二者相差悬殊。用Number.MAX_VALUE作为安全边界会导致严重精度错误。
面试官还会怎么问?
为什么安全整数是2^53-1而不是2^53?
2^53本身可精确表示,因为尾数适宜,但2^53+1无法表示,所以从0到2^53-1的连续整数才都能唯一表达,边界取2^53-1。
如何快速判断一个数是否为安全整数?
使用Number.isSafeInteger(num),它要求Number.isInteger(num)且Math.abs(num) <= Number.MAX_SAFE_INTEGER。该方法会直接返回布尔值。
BigInt与Number混合运算会怎样?
BigInt与Number是不同原始类型,不能直接混合运算,如1n + 1会抛出TypeError。但关系比较(如1n < 2)是允许的,不过仍建议显式转换以避免混淆。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。