Skip to content

每日一报

js的最大安全数字为什么是 2531

javascript 中的所有数字都是用 64 位的 IEEE 754 双精度浮点数表示的。

IEEE 754 双精度浮点数

双精度浮点数由三个部分组成:

(1)sign×(1.fraction)×2exponent1023

其中:

  • sign(符号位)1 位,用于表示数值的正负。0 表示正数,1 表示负数。
  • exponent(指数部分)11 位,用于表示数值的数量级。它决定了浮点数的规模,通过对 11 位的二进制数进行偏移表示(即对指数加上一个偏移量,称为 bias)。
  • fraction(尾数部分)52 位,有效位数,也称为尾数,表示数值的精度。

指数部分的具体作用

指数部分是一个 11 位的无符号整数,表示的范围是 0(0) 到 2047(2111)。但指数实际上表示的是浮点数的二进制指数部分。因此,为了表示正负指数,采用偏移量(bias)的方式来表示。对于双精度浮点数,bias1023

样例说明

实际指数值 = 存储的指数值 - 偏移量(1023

  • 如果指数部分存储的是 0(即二进制的 00000000000),则实际指数是 0 - 1023 = -1023,此时通常用于表示特殊值,如次正规数或零。
  • 如果指数部分存储的是 1023(即二进制的 01111111111),则实际指数是 1023 - 1023 = 0,表示的是 20,即数字的值不需要放大或缩小。
  • 如果指数部分存储的是 1024,则实际指数是 1024 - 1023 = 1,表示的是 21,即数字的值需要乘以 2
  • 如果指数部分存储的是 1022,则实际指数是 1022 - 1023 = -1,表示的是 21,即数字的值要缩小两倍。
  • 如果指数部分存储的是 2047(即二进制的 11111111111),则实际指数是 2047 - 1023 = 1024,此时通常通常用于表示特殊值,如无穷大NaN

11 位无符号整数可以表示的范围是 02047,指数位 2exponent1023 可以表示的范围是 2102321024,除去边界的特殊值(当指数位存储 0 和指数位存储 2047 时,即有效区间 [1, 2046]),则可以表示的范围是 2102221023

可以看出双精度浮点数在 JavaScript 中可以表示非常大的数值范围,但是在超过安全整数范围时,可能会出现精度丢失的原因。

那么问题来了,最大的安全整数为什么为 Math.pow(2,53) - 1 呢?

由公式

(1)sign×(1.fraction)×2exponent1023

可以得知 fraction(尾数部分) 最大为 52 位数,再加上前面 1(由于 IEEE 754 标准在尾数部分隐含了一个 1(规范化数的前导 1),因此尾数实际上具有 53 位的精度。)。指数位(2exponent1023)由上可知最大有效值为 21023(意味着右移 1023 位),那么理论上双精度浮点数可以表示的最大值为 (1)0×(1.(52))×21023

这里需要注意一点,双精度浮点数整数的安全精度是由尾数位(1.fraction)决定的:

bash
1.0000000000000000000000000000000000000000000000000000

通过指数位右移 52 位:

bash
10000000000000000000000000000000000000000000000000000

可知最大的安全位数为 53 位。

那么最大可以表示的精确整数范围是从 253+1(Number.MIN_SAFE_INTEGER) 到 2531(Number.MAX_SAFE_INTEGER)。

那么最大可以表示的精确整数范围是 90071992547409919007199254740991

js
Number.isSafeInteger(2 ** 53 - 1); // true
Number.isSafeInteger(2 ** 53); // false
Number.isSafeInteger(-(2 ** 53) + 1); // true
Number.isSafeInteger(-(2 ** 53)); // false

当整数超过 2531 时,由于只有 53 位的精度,相邻整数间的差值可能无法在浮点表示中区分开。例如:

js
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2); // true

与位运算的区别

javascript 中的位运算是一个特殊的操作集合,与普通算术运算有本质区别。在执行位运算时,javascript 会将操作数转换为 32 位有符号整数(而非 64 位浮点数),因此位运算的安全数值范围为 [-231+1, 2311] 中的整数。

这是与 javascript 常规运算的第一个关键区别:

js
// 常规算术运算使用64位浮点数
console.log(9007199254740991 + 1); // 9007199254740992

// 位运算会先转换为32位整数
console.log(9007199254740991 | 0); // -1 (截断到32位)

位运算时的转换遵循以下规则:

  1. 将操作数转换为整数,忽略小数部分。
  2. 将整数截断为 32 位。
  3. 执行位运算。
  4. 结果仍保持为 32 位整数。
js
// 小数部分被截断
console.log(5.7 & 3.2); // 1 (5 & 3 = 1)

// 大于32位的整数被截断
console.log((2 ** 32 + 1) << 1); // 2 (而非 2 ** 33 + 2)

根据 ECMAScript 规范,位运算符会对操作数应用 ToInt32 抽象操作。这意味着:

  1. 非数值会被转换为数值(类似 Number())。
  2. 数值会被转换为 32 位有符号整数。
  3. NaNInfinity 会被转换为 0
  4. 小数部分被直接丢弃(向 0 取整)。
js
// 非数值的转换
console.log('5' & 3); // 1 (字符串"5"转换为数字5)
console.log(true << 2); // 4 (true转换为1,左移2位)

// NaN和Infinity的处理
console.log(NaN >>> 1); // 0
console.log(Infinity & 1); // 0
特性一般运算位运算
数值表示64位双精度浮点数32位有符号整数
精度范围±(2^53-1)±(2^31-1)
小数支持支持不支持(自动截断)
NaN/Infinity处理支持转换为0

位运算的性能特性

位运算在现代 JavaScript 引擎中并不总是比算术运算更快。V8 等引擎的优化已使算术运算非常高效。但位运算在以下情况依然有优势:

js
// 取整操作
const x = 25.9;
// 位运算方式
console.log(x | 0); // 25 (Math.floor(x))

// 判断奇偶
console.log(7 & 1); // 1 (奇数)
console.log(8 & 1); // 0 (偶数)

// 乘除法替代
console.log(8 << 1); // 16 (8 * 2)
console.log(8 >> 1); // 4 (8 / 2)

贡献者

页面历史

Discuss

根据 CC BY-SA 4.0 许可证发布。 (9037680)