> For the complete documentation index, see [llms.txt](https://anjia1.gitbook.io/cs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://anjia1.gitbook.io/cs/floating-point/constants.md).

# 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 在内存中的存储方式](https://anjia1.gitbook.io/cs/floating-point/pages/T6JF4G3bCFCiU67zY4z4#3.-nei-cun-cun-chu)，如何理解这些常量的含义和值呢？

![](/files/sPH9rJ0nQI3YKRfWEDrC)

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

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

### 1.1 最大正数

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

![](/files/sor8YCyo9fHzFypueRh5)

对应的 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”。此时即为[次正规数](https://anjia1.gitbook.io/cs/floating-point/pages/T6JF4G3bCFCiU67zY4z4#3.3.1-zhi-shu-quan-0)，如下：

![](/files/A8VyMIZWDB2mfsghDASp)

对应的 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)}$$，此时的内存表示如下：

![](/files/Ss8fCSsvWkb0YGmfNINF)

真实值就是：$$(-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”。如下：

![](/files/dLgf5BvcUXnqspgsNTDW)

对应的 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”。如下：

![](/files/keREZFjOI7x2TCLG3l84)

对应的 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 不均匀的断层

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

![](/files/HaS6ZPvwX9Pv0F1AWHgV)

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

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

* 整数部分，当有 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 的情况，是因为当指数过大或者过小时，能表示的数值就不是线性连续的了。感兴趣的小伙伴们，可以自行推导下。

![](/files/dg3bhCTMXWI6HpefweXY)

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

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

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

## 4. 特殊值

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

### 4.1 正负无穷大

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

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

![](/files/e3rNdmgyqG7mDAgUfkU4)

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

![](/files/cMtKoVcIMnXGvhpApUoC)

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

### 4.2 非数值

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

![](/files/2NkE6DQcWARJUgcLVpMn)

从 `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 内存存储为：

![](/files/BvLO0pWrDiPwV8PfixQH)

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

![](/files/a7PkHZEkxpomSVJi1hL2)

## 5. 总结

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

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

## 6. 主要参考

* <https://2ality.com/2013/10/safe-integers.html>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://anjia1.gitbook.io/cs/floating-point/constants.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
