每日一报
js的最大安全数字为什么是
javascript 中的所有数字都是用 64 位的 IEEE 754 双精度浮点数表示的。
IEEE 754 双精度浮点数
双精度浮点数由三个部分组成:
其中:
- sign(符号位):
1位,用于表示数值的正负。0表示正数,1表示负数。 - exponent(指数部分):
11位,用于表示数值的数量级。它决定了浮点数的规模,通过对11位的二进制数进行偏移表示(即对指数加上一个偏移量,称为bias)。 - fraction(尾数部分):
52位,有效位数,也称为尾数,表示数值的精度。
指数部分的具体作用
指数部分是一个 11 位的无符号整数,表示的范围是 0(bias)的方式来表示。对于双精度浮点数,bias 是 1023。
样例说明
实际指数值 = 存储的指数值 - 偏移量(1023)
- 如果指数部分存储的是
0(即二进制的00000000000),则实际指数是0 - 1023 = -1023,此时通常用于表示特殊值,如次正规数或零。 - 如果指数部分存储的是
1023(即二进制的01111111111),则实际指数是1023 - 1023 = 0,表示的是,即数字的值不需要放大或缩小。 - 如果指数部分存储的是
1024,则实际指数是1024 - 1023 = 1,表示的是,即数字的值需要乘以 2。 - 如果指数部分存储的是
1022,则实际指数是1022 - 1023 = -1,表示的是,即数字的值要缩小两倍。 - 如果指数部分存储的是
2047(即二进制的11111111111),则实际指数是2047 - 1023 = 1024,此时通常通常用于表示特殊值,如无穷大或NaN。
11 位无符号整数可以表示的范围是 0 到 2047,指数位 0 和指数位存储 2047 时,即有效区间 [1, 2046]),则可以表示的范围是
可以看出双精度浮点数在 JavaScript 中可以表示非常大的数值范围,但是在超过安全整数范围时,可能会出现精度丢失的原因。
那么问题来了,最大的安全整数为什么为 Math.pow(2,53) - 1 呢?
由公式
可以得知 fraction(尾数部分) 最大为 52 位数,再加上前面 1(由于 IEEE 754 标准在尾数部分隐含了一个 1(规范化数的前导 1),因此尾数实际上具有 53 位的精度。)。指数位(1023 位),那么理论上双精度浮点数可以表示的最大值为
这里需要注意一点,双精度浮点数整数的安全精度是由尾数位(
1.0000000000000000000000000000000000000000000000000000通过指数位右移 52 位:
10000000000000000000000000000000000000000000000000000可知最大的安全位数为 53 位。
那么最大可以表示的精确整数范围是从 Number.MIN_SAFE_INTEGER) 到 Number.MAX_SAFE_INTEGER)。
那么最大可以表示的精确整数范围是
Number.isSafeInteger(2 ** 53 - 1); // true
Number.isSafeInteger(2 ** 53); // false
Number.isSafeInteger(-(2 ** 53) + 1); // true
Number.isSafeInteger(-(2 ** 53)); // false当整数超过 53 位的精度,相邻整数间的差值可能无法在浮点表示中区分开。例如:
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 位浮点数),因此位运算的安全数值范围为 [-
这是与 javascript 常规运算的第一个关键区别:
// 常规算术运算使用64位浮点数
console.log(9007199254740991 + 1); // 9007199254740992
// 位运算会先转换为32位整数
console.log(9007199254740991 | 0); // -1 (截断到32位)位运算时的转换遵循以下规则:
- 将操作数转换为整数,忽略小数部分。
- 将整数截断为
32位。 - 执行位运算。
- 结果仍保持为
32位整数。
// 小数部分被截断
console.log(5.7 & 3.2); // 1 (5 & 3 = 1)
// 大于32位的整数被截断
console.log((2 ** 32 + 1) << 1); // 2 (而非 2 ** 33 + 2)根据 ECMAScript 规范,位运算符会对操作数应用 ToInt32 抽象操作。这意味着:
- 非数值会被转换为数值(类似
Number())。 - 数值会被转换为
32位有符号整数。 NaN和Infinity会被转换为0。- 小数部分被直接丢弃(向
0取整)。
// 非数值的转换
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 等引擎的优化已使算术运算非常高效。但位运算在以下情况依然有优势:
// 取整操作
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)