---
description: 以 JavaScript 语言里的 Number 数据类型为例
---

# 5. 四类常量

在 JavaScript 里，Number 对象有四类静态属性，分别是：

1. 最大/最小正数
   * 能表示的最大正数：`Number.MAX_VALUE`
   * 能表示的最小正数（最接近 0 的正数）：`Number.MIN_VALUE`
2. 最大/最小安全整数
   * 最大安全整数 $$+(2^{53} - 1)$$，`Number.MAX_SAFE_INTEGER`
   * 最小安全整数 $$-(2^{53} - 1)$$，`Number.MIN_SAFE_INTEGER`
3. 最小间隔：能表示的数值之间的最小间隔，`Number.EPSILON`
4. 特殊值
   * 正负无穷大（上溢时返回）
     * `Number.NEGATIVE_INFINITY`
     * `Number.POSITIVE_INFINITY`
   * 非数值（Not a Number），`Number.NaN`
   * ±0

它们的值分别是：

```javascript
// 最大正数和最小正数
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324

// 最大安全整数和最小安全整数
Number.MAX_SAFE_INTEGER; //  9007199254740991 = 2**53-1
Number.MIN_SAFE_INTEGER; // -9007199254740991

// 最小间隔
Number.EPSILON; // 2.220446049250313e-16 = 2**-52

// 特殊值：正负无穷大
Number.POSITIVE_INFINITY; // Infinity
Number.NEGATIVE_INFINITY; // -Infinity
// 特殊值：非数值
Number.NaN; // NaN
```

结合 [Number 在内存中的存储方式](../binary64#3.-nei-cun-cun-chu)，如何理解这些常量的含义和值呢？

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FxoqztlTigYjI5OqzUeEh%2Fbinary64.png?alt=media\&token=728b368b-2a5f-44af-866a-d0d92041c4bc)

* 符号位，1 位，0 正 1 负
* 指数，11 位，全 0 和全 1 是为特殊数值保留的
* 有效数，52 个显式存储位 + 1 个默认前导位，共 53 位

## 1. 最大正数和最小正数

### 1.1 最大正数

最大正数 `MAX_VALUE` 应该是：正数即符号位为 0，11 位指数是“全 1-1”，52 位有效数是“全 1”。如下：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FBx5ibHwk6vfMNQ4LpU1h%2Fmax_value.png?alt=media\&token=bc41c195-3ac2-4b71-8eb4-c0a8a765b8fc)

对应的 binary64 内存存储就是 `0x7FEFFFFFFFFFFFFF`。

真实值就是：$$(-1)^0 * 1.11...11_{(2)} * 2^{2046-1023}$$

* \= $$1.11...11_{(2)} * 2^{1023}$$
* \= $$111...11_{(2)} * 2^{1023-52}$$
* \= $$111...11_{(2)} * 2^{971}$$
* \= $$(2^{53} - 1) * 2^{971}$$
* \= 1.7976931348623157e+308
* ≈ 1.8e+308

```javascript
0b11111111110; // 2046
(2**53 - 1) * 2**971; // 1.7976931348623157e+308
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MAX_VALUE === (2**53 - 1) * 2**971; // true
```

### 1.2 最小正数

最小正数 `MIN_VALUE` 应该是：正数即符号位为 0，11 位指数是“全 0”，52 位有效数是“全 0+1”。此时即为[次正规数](../binary64#3.3.1-zhi-shu-quan-0)，如下：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FsvNRmCViml4C0ILxmlAD%2Fmin_value.png?alt=media\&token=19bf1217-aa96-4f78-9608-bbe08d8ecad7)

对应的 binary64 内存存储就是 `0x0000000000000001`。

真实值就是：$$(-1)^0 * 0.00...01_{(2)} * 2^{-1022}$$

* \= $$1 * 1_{(2)} * 2^{-1022-52}$$
* \= $$2^{-1074}$$
* \= 5e-324

```javascript
2**-1074; // 5e-324
Number(2**-1074).toPrecision(70); // '4.940656458412465441765687928682213723650598026143247644255856825006755e-324'
Number.MIN_VALUE; // 5e-324
Number.MIN_VALUE === 2**-1074; // true
```

## 2. 最大安全整数和最小安全整数

在介绍最大安全整数和最小安全整数之前，我们先来认识下什么是安全整数。

### 2.1  安全整数

安全整数是形容数学概念上的一个整数在 JavaScript 里的表示方式。如果说一个整数是安全的，就意味着它能在 JavaScript 中被唯一地表示。

> The idea of a safe integer is about how mathematical integers are represented in JavaScript.\
> In the range (−253, 253) (excluding the lower and upper bounds), JavaScript integers are safe: there is a one-to-one mapping between mathematical integers and their representations in JavaScript.

那么，如何理解“被唯一”地表示呢？来看个例子。

当 IEEE 754 binary64 的 53 个有效位全是 1 且都处于整数位置时，即实际指数值是 52，偏正指数值是 52+1023 = 1075 = $$10000110011_{(2)}$$，此时的内存表示如下：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FEUVOMsPx36wU1zvoYeky%2Fsafe.png?alt=media\&token=3da9d383-6e0f-4e49-9bf3-b9180ee55b07)

真实值就是：$$(-1)^0 * 1.11...11_{(2)} * 2^{52}$$

* \= $$1.11...11_{(2)} * 2^{52}$$
* \= $$111...11_{(2)}$$
* \= $$2^{53} - 1$$
* \= 9007199254740991

当它加 1 时，值会变成 $$2^{53}$$ = $$1000...00_{(2)}$$ = 1.00...00<mark style="color:red;">0</mark> \* $$2^{53}$$ = 1.00...00~~<mark style="color:red;">0</mark>~~ \* $$2^{53}$$。注意，末位的 ~~<mark style="color:red;">0</mark>~~ 之所以被删，是因为内存里的 IEEE 754 binary64 格式的有效位只有 53 个（包括默认的前导位 1），所以需要裁切精度。

当再加 1 时，值会变成 $$2^{53} + 1$$ = $$1000...01_{(2)}$$ = 1.00...00<mark style="color:red;">1</mark> \* $$2^{53}$$ ≈ 1.00...0<mark style="color:green;">1</mark>~~<mark style="color:red;">1</mark>~~ \* $$2^{53}$$ = 1.00...0<mark style="color:green;">1</mark> \* $$2^{53}$$。红色的 <mark style="color:red;">1</mark> 表示即将被裁掉的精度位，<mark style="color:green;">1</mark>~~<mark style="color:red;">1</mark>~~ 表示舍掉了 ~~<mark style="color:red;">1</mark>~~ 之后进位的 <mark style="color:green;">1</mark>（即四舍五入式）。

为了方便阅读，我们用表格的形式来描述。十进制那列是从 $$2^{53}-1$$ 即 9007199254740991 开始的，每行的值依次加 1。如下：

<table><thead><tr><th>十进制</th><th width="150" align="right">二进制</th><th width="182.49955429623654">二进制科学记数法</th><th>IEEE 754 binary64</th></tr></thead><tbody><tr><td>9007199254740991</td><td align="right"><span class="math">111...11</span></td><td>1.11...11 * <span class="math">2^{52}</span></td><td>1.11...11 * <span class="math">2^{52}</span></td></tr><tr><td>9007199254740992</td><td align="right"><span class="math">1000...00</span></td><td>1.000...0<mark style="color:red;">0</mark> * <span class="math">2^{53}</span></td><td>1.00...00<del><mark style="color:red;">0</mark></del> * <span class="math">2^{53}</span></td></tr><tr><td>9007199254740993</td><td align="right"><span class="math">1000...01</span></td><td>1.000...0<mark style="color:red;">1</mark> * <span class="math">2^{53}</span></td><td>1.00...0<mark style="color:green;">1</mark><del><mark style="color:red;">1</mark></del> * <span class="math">2^{53}</span></td></tr><tr><td>9007199254740994</td><td align="right"><span class="math">1000...10</span></td><td>1.000...1<mark style="color:red;">0</mark> * <span class="math">2^{53}</span></td><td>1.00...01<del><mark style="color:red;">0</mark></del> * <span class="math">2^{53}</span></td></tr><tr><td>9007199254740995</td><td align="right"><span class="math">1000...11</span></td><td>1.000...1<mark style="color:red;">1</mark> * <span class="math">2^{53}</span></td><td>1.00...<mark style="color:green;">10</mark><del><mark style="color:red;">1</mark></del> * <span class="math">2^{53}</span></td></tr><tr><td>9007199254740996</td><td align="right"><span class="math">100...100</span></td><td>1.00...10<mark style="color:red;">0</mark> * <span class="math">2^{53}</span></td><td>1.00...10<del><mark style="color:red;">0</mark></del> * <span class="math">2^{53}</span></td></tr><tr><td>9007199254740997</td><td align="right"><span class="math">100...101</span></td><td>1.00...10<mark style="color:red;">1</mark> * <span class="math">2^{53}</span></td><td>1.00...1<mark style="color:green;">1</mark><del><mark style="color:red;">1</mark></del> * <span class="math">2^{53}</span></td></tr><tr><td>9007199254740998</td><td align="right"><span class="math">100...110</span></td><td>1.00...11<mark style="color:red;">0</mark> * <span class="math">2^{53}</span></td><td>1.00...11<del><mark style="color:red;">0</mark></del> * <span class="math">2^{53}</span></td></tr><tr><td>...</td><td align="right"></td><td></td><td></td></tr></tbody></table>

说明：“二进制科学记数法”列中的<mark style="color:red;">标红</mark>数字，是在存入内存时将会被裁掉的部分，因为小数点后只能存储 52 个有效数字。“IEEE 754 binary64”列中，~~<mark style="color:red;">标红</mark>~~的数字即前一列的内容，<mark style="color:green;">标绿</mark>的数字就是用了四舍五入（即 0 舍 1 入）的舍入方式裁剪精度后最终存储的有效数字。

我们可以很直观地看到，当二进制有效数的位数大于 53 位时，就得舍弃最后一位，这会导致值不同的两个数值会有相同的 IEEE 754 binary64 表示。这，就是没有“被唯一”地表示。

* 当指数是 53 时，是 2 个整数共用一个内存存储，因为会舍去末位的 53-52 = 1 位
* 当指数是 54 时，是 4 个整数共用一个内存储存，因为会舍去末位的 54-52 = 2 位
* 当指数是 55 时，是 8 个整数共用一个内存储存，因为会舍去末位的 55-52 = 3 位
* 当指数是 56 时，是 16 个整数共用一个内存储存，因为会舍去末位的 56-52 = 4 位
* ...

注意：如果是用向 0 舍入的方式（即直接截断），那么共用一个内存存储的整数范围会有所不同，不过不影响大局。

JavaScript 里的安全整数的范围是 \[$$-(2^{53}-1)$$, $$+(2^{53}-1)$$]，没有包含边界 $$±2^{53}$$。虽然 $$2^{53}$$ 在内存中是能被唯一表示的，但是考虑到如果是用直接截断的方式，它是会和 $$2^{53}+1$$ 重叠的。所以在 JavaScript 中，一旦精度有被截断，就会被视为是不安全的。如下：

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

所以，在 JavaScript 中，说一个整数是安全的，就意味着它能唯一地表示一个数学意义上的整数，且在存储时没有被舍弃精度。

### 2.2 最大安全整数

最大安全整数 `MAX_SAFE_INTEGER` 应该是：正数即符号位为 0，指数的实际值是 52（即内存值为 52+1023 = 1075 = 10000110011(2) ），52 位有效数是“全 1”。如下：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FAOdN5uDKg8KcNVAPY4Qj%2Fmax_safe.png?alt=media\&token=14f49209-0f37-4992-b867-878777ec7d6e)

对应的 binary64 内存存储就是 `0x433FFFFFFFFFFFFF`。

真实值就是：$$(-1)^0 * 1.11...11_{(2)} * 2^{52}$$

* \= $$1.11...11_{2}*2^{52}$$
* \= $$111...11_{(2)}$$
* \= $$2^{53}$$-1
* \= 9007199254740991

```javascript
0b10000110011; // 1075
2 ** 53 - 1; // 9007199254740991
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.MAX_SAFE_INTEGER === 2 ** 53 - 1; // true
```

### 2.3 最小安全整数

最小安全整数 `MIN_SAFE_INTEGER` 除了符号和最大安全整数 `MAX_SAFE_INTEGER` 不同之外，其余都一样，即：负数即符号位为 1，指数的实际值是 52（即内存值为 52+1023 = 1075 = $$10000110011_{(2)}$$），52 位有效数是“全 1”。如下：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FE4F850AX8GONKboMCk2F%2Fmin_safe.png?alt=media\&token=c0f208b1-1aa7-433b-9e8d-eae713d92cc9)

对应的 binary64 内存存储就是 `0xC33FFFFFFFFFFFFF`。

真实值就是：$$(-1)^1 * 1.11...11_{(2)} * 2^{52}$$

* \= $$-1.11...11_{2}*2^{52}$$
* \= $$-111...11_{(2)}$$
* \= $$- (2^{53} - 1)$$
* \= -9007199254740991

```javascript
0b10000110011; // 1075
2 ** 53 - 1; // 9007199254740991
Number.MIN_SAFE_INTEGER; // -9007199254740991
Number.MIN_SAFE_INTEGER === -(2 ** 53 - 1); // true
```

## 3. 最小间隔

### 3.1 不均匀的断层

在[《精度损失》](write)里曾提到浮点数到实数的映射，其中间有覆盖不到的小断层，如下图：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FyMpia0CjibMh55FRN1DZ%2Freal-number.png?alt=media\&token=acb24f73-0271-4ace-a836-40617fe660c8)

能表示的两个数字之间的“间隔”，就是图中的那些个“小断层”。那么，这些断层是均匀的吗？如果均匀，值是多少？如果不均匀，最小值和最大值分别是多少？

要回答这个问题，我们就得看看二进制到十进制的转换了。在[《为什么 toPrecision(17) 能让内存中的数值原形毕露？》](read)里我们是将整数部分和小数部分分开来讨论的，相关结论是：

* 整数部分，当有 n 个二进制有效位时，可以表示 $$2^n$$ 个十进制数，且它们是个等差数列，差值是 1
* 小数部分，当有 n 个二进制有效位时，可以表示 $$2^n$$ 个十进制数，且它们是个等差数列，差值是 $$2^{-n}$$，数列范围是 \[$$2^{-n}$$, 1)

在真实的 IEEE 754 binary64 存储中，二进制的那 53 个有效位是可以同时有整数部分和小数部分的，如果再考虑指数，这会让每个有效位上的数字权重（即 2 的几次方）变成流动的。所以显然，两个能表示的数之间的“间隔”是不均匀的。

那间隔的最小值和最大值分别是多少？

### 3.2 最小间隔和最大间隔

假设现在有 4 个二进制有效位，其中包括 1 个默认前导位和 3 个显式存储位。当默认前导位是 0 时是次正规数，当默认前导位是 1 时是正规数。我们看看在这种情况下，二进制到十进制是一个什么样的对应关系。如下：

<table><thead><tr><th width="150">格式</th><th width="177">二进制</th><th width="205.77716643741405">十进制</th><th>间隔值</th></tr></thead><tbody><tr><td>0.xxx</td><td>0.000<br>0.001<br>0.010<br>0.011<br>0.100<br>0.101<br>0.110<br>0.111</td><td>0<br>0.125<br>0.25<br>0.375<br>0.5<br>0.625<br>0.75<br>0.875</td><td>0.125 = <span class="math">2^{-3}</span></td></tr><tr><td>1.xxx</td><td>1.000<br>1.001<br>1.010<br>1.011<br>1.100<br>1.101<br>1.110<br>1.111</td><td>1<br>1.125<br>1.25<br>1.375<br>1.5<br>1.625<br>1.75<br>1.875</td><td>0.125 = <span class="math">2^{-3}</span></td></tr><tr><td>1x.xx</td><td>10.00<br>10.01<br>10.10<br>10.11<br>11.00<br>11.01<br>11.10<br>11.11</td><td>2<br>2.25<br>2.5<br>2.75<br>3<br>3.25<br>3.5<br>3.75</td><td>0.25 = <span class="math">2^{-2}</span></td></tr><tr><td>1xx.x</td><td>100.0<br>100.1<br>101.0<br>101.1<br>110.0<br>110.1<br>111.0<br>111.1</td><td>4<br>4.5<br>5<br>5.5<br>6<br>6.5<br>7<br>7.5</td><td>0.5 = <span class="math">2^{-1}</span></td></tr><tr><td>1xxx</td><td>1000<br>1001<br>1010<br>1011<br>1100<br>1101<br>1110<br>1111</td><td>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15</td><td>1 = <span class="math">2^0</span></td></tr></tbody></table>

同理，当我们有 53 个二进制有效位时（1 个默认前导位 + 52 个显式位），从 0 \~ 1 \~ $$2^1$$ \~ $$2^2$$ \~ $$2^3$$ \~ $$2^4$$ \~ ... \~ ($$2^{53}$$ - 1) 之间，每段都是连续的等差数列，只是差值不同而已。其中，最小的差值是 $$2^{-52}$$，最大的差值是 $$2^0$$ = 1。

之所以只考虑了次正规数和 0 ≤ 指数实际值 ≤ 52 的情况，是因为当指数过大或者过小时，能表示的数值就不是线性连续的了。感兴趣的小伙伴们，可以自行推导下。

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2F1lOlEUZzOUORgPDWZAgo%2Fx.jpeg?alt=media\&token=c99ef3e1-8d9f-45c1-bd03-6e17865c0ee0)

在数轴上，二进制和十进制的逻辑类似。

综上，最小间隔 `EPSILON` 就是能表示的等差数列的最小差值，即 $$2^{-52}$$ = 2.220446049250313e-16

```javascript
2**-52; // 2.220446049250313e-16
Number.EPSILON; // 2.220446049250313e-16
Number.EPSILON === 2**-52; // true
```

## 4. 特殊值

包括次正规数，还有三类特殊值，它们都是依据指数的特殊编码来区分的。详情可查看[《双精度浮点数 / 内存存储 / 指数位编码](../binary64#3.3-zhi-shu-wei-bian-ma)》，这里就不展开说了。

### 4.1 正负无穷大

这两个就是指数位“全 1”的特殊情况（之一）：指数位是“全 1”，有效数位是“全 0”。

正无穷大的 binary64 的内存存储为 `0x7FF0000000000000`，如下图：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FHHC241lYwI5tghvMThgH%2F%2Binfinity.png?alt=media\&token=a48927d5-f6dd-4043-8a10-5cd57426b855)

负无穷大的  binary64 的内存存储是 `0xFFF0000000000000`，如下图：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FFNJHbGkP9FRuCw3NqfcG%2F-infinity.png?alt=media\&token=df8823c9-6ebb-41cd-9841-ed4760ab6de8)

```javascript
Number.POSITIVE_INFINITY === Infinity; // true
Number.NEGATIVE_INFINITY === -Infinity; // true
```

### 4.2 非数值

`NaN` ，Not a Number，是指数位“全 1”的特殊情况（之一）：指数位是“全 1”，只要有效数位不是“全0”的都是 `NaN`（忽略符号位）。如下图：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FFc8H8PCz9B0KUJFcZnnd%2Fnan.jpeg?alt=media\&token=43733e42-8949-4350-80be-0666f61cd029)

从 `0x7FF0000000000001` \~ `0x7FFFFFFFFFFFFFFF` 都是 `NaN`。

### 4.3 ±0

0 有两种表示方式：-0 和 +0（0 是 +0 的别名）。在实际开发中，这几乎没啥影响。但需要注意一点，就是当 0 是被除数的时候，会有不同：

```javascript
33 / 0; // Infinity
33 / -0; // -Infinity
-0 === 0; // 虽然是 true
```

+0 的 IEEE 754 binary64 内存存储为：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FyIdBKxTw9S2R4oq0eq7o%2F%2B0.png?alt=media\&token=06130b30-9097-45bc-aac9-27fb9befbd5f)

-0 的 IEEE 754 binary64 内存存储为：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FyUAg7n13QFmwNwQYxg8h%2F-0.png?alt=media\&token=56c65834-c9b1-43de-8b38-b6f6ec1cacf8)

## 5. 总结

这部分以 JavaScript 语言里的数值类型 Number 为例，介绍了双精度浮点数里的四类常量，对它们知其然也知其所以然会对编程大有裨益。

1. 能表示的最大正数和最小正数
2. 能表示的最大安全整数和最小安全整数
3. 能表示的数值之间的间隔
   * 浮点数→实数：有间隔，且间隔不均匀
   * 在线性增长中，是 n 段等差数列。最小差值/间隔是 `Number.EPSILON`，最大差值/间隔是 1
   * 在指数增长中，安全整数范围内的数列是连续的，之外的是不连续的（有重叠，没被唯一表示）
4. 特殊值：`NaN`, ±`Infinity`, ±`0`

## 6. 主要参考

* [https://2ality.com/2013/10/safe-integers.html](https://2ality.com/2013/10/safe-integers.html)
