> 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/exceptions.md).

# 4. 五种异常

无效运算 > 上溢 > 除以零 > 下溢 > 不精确，前三个一旦出现很少被忽略

IEEE 754 定义了五种基本的浮点异常，分别是：无效运算、除以零、上溢（指数太大）、下溢（指数太小）和不精确。前三个一旦出现很少被忽略，后两个通常可以安全地忽略（尽管不总是）。

异常的优先顺序是：无效运算 > 上溢 > 除以零 > 下溢 > 不精确。能够在单个运算中同时发生的标准异常只有不精确的上溢和不精确的下溢这两种组合。

## 1. 无效运算

* 原因：对于将要执行的运算，某个操作数无效
* 示例：0/0, 0\*∞, ∞/∞, 负数的平方根, `NaN` 参与的任何运算, 无效转换
* 缺省结果： `NaN`

```javascript
0 / 0; // NaN
0 / -0; // NaN
-0 / 0; // NaN
-0 / -0; // NaN

0 * Infinity; // NaN
0 * -Infinity; // NaN
-0 * Infinity; // NaN
-0 * -Infinity; // NaN

Infinity / Infinity; // NaN
Infinity / -Infinity; // NaN
-Infinity / Infinity; // NaN
-Infinity / -Infinity; // NaN

Math.sqrt(-1); // NaN

NaN + 1; // NaN
parseInt("hello") + 1; //NaN

parseInt("hello"); // NaN
parseInt(NaN); // NaN
parseInt(Infinity); // NaN
```

## 2. 除以零

* 原因：针对有限操作数执行运算时，生成精确的无穷大结果
* 示例：非零x/0, log(0)
* 缺省结果：带正确符号的无穷大

```javascript
1 / 0; // Infinity
1 / -0; // -Infinity
-1 / 0; // -Infinity
-1 / -0; // Infinity

Math.log(0); // -Infinity
Math.log(-0); // -Infinity
```

## 3. 上溢（指数太大）

* 原因：正确舍入的结果的指数，超过了能表示的最大指数值，即指数 > emax。
* 双精度示例：$$2^{1024}, e^{709.8},$$ `MAX_VALUE*2`
* 缺省结果：取决于舍入模式和中间结果的符号。四种舍入方向：
  1. 向最接近的可表示的值舍入：当有两个最接近的可表示的值时，首选“偶数”值
  2. 向 0 舍入（截断）
  3. 向正无穷大舍入（向上）
  4. 向负无穷大舍入（向下）

| 舍入模式 | 正    | 负    |
| ---- | ---- | ---- |
| 最近   | +∞   | -∞   |
| 零    | +max | -max |
| 向上   | +∞   | -max |
| 向下   | +max | -∞   |

```javascript
// 舍入模式是“最近”
Math.pow(2, 1024); // Infinity
Math.exp(709.8); // Infinity
Number.MAX_VALUE * 2; // Infinity
Number.MAX_VALUE * -2; // -Infinity
```

## 4. 下溢（指数太小）

* 原因：精确结果或正确舍入的结果的绝对值，比能表示的最小正“正规数”小，即 |结果| < 最小正“正规数”。
* 双精度示例：$$2^{-1074}, 2^{-1075}$$
* 缺省结果：返回次正规数或 0

下溢，也称浮点下溢/算术下溢。在[《1. 双精度浮点数》](/cs/floating-point/binary64.md)中我们有提到，IEEE 754 binary64（双精度浮点数）实际存储的有效数是经过规范化处理的。正规数，就是规范化处理后的有效位表示，其有效数的默认前导位是 1。

次正规数是介于最小正规数和零之间的数，它通过调整指数（将指数位全置为 0）来去除有效数的前导 1，以此来表示比最小的正规数更接近 0 的数字。这样就能填补浮点运算中 0 附近的下溢间隙了，进而避免“即使两个数值不相等，减法 a - b 会发生下溢进而产生 0”的情况（达到下溢时，会丢弃所有有效数字，然后就突然变 0 了）。所以，次正规数也被称为逐渐下溢，因为它允许（值非常小的）计算结果慢慢地失去精度。

* 正规数：有效数的默认前导位是 1
* 次正规数：有效数的默认前导位是 0

### **4.1 最小正规数**

最小的正“正规数”应该是：正数即符号位为 0，11 位指数是“全 0+1”，52 位有效数是“全 0”。也就是说其 binary64 的内存存储为 `0x0010000000000000`，如下：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FEAl9BRUXXTkd2OXwzyJI%2F%E6%B5%AE%E7%82%B9%E6%95%B0-%E5%BC%82%E5%B8%B81.png?alt=media\&token=648f20e0-ee2b-4079-8d55-6c19c8de0ad5)

真实值就是：$$(-1)^0 \* 1.00...00\_{(2)} \* 2^{1-1023} = 1 \* 2^{-1022} ≈ 2.2250738585072014e-308$$

```javascript
2**-1022; // 2.2250738585072014e-308
```

### **4.2 最大次正规数**

最大“次正规数”应该是：正数即符号位为 0，11 位指数是“全 0”，52 位有效数是“全 1”。也就是说其 binary64 的内存存储为 `0x000FFFFFFFFFFFFF`，如下：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2FvKtt8cB35YmnF9z9HHnR%2F%E6%B5%AE%E7%82%B9%E6%95%B0-%E5%BC%82%E5%B8%B82.png?alt=media\&token=e5eba767-c3e8-4966-803b-3d376ecd10b8)

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

* \= $$11...111\_{(2)} \* 2^{-1022-52}$$
* \= $$(2^{52} - 1) \* 2^{-1022-52}$$
* \= $$2^{-1022} - 2^{-1074}$$
* ≈ 2.225073858507201e-308

```javascript
Number(2**-1022 - 2**-1074); // 2.225073858507201e-308
Number(2**-1022 - 2**-1074).toPrecision(55); // '2.225073858507200889024586876085859887650423112240959465e-308'
```

### **4.3 最小次正规数**

最小“次正规数”应该是：正数即符号位为 0，11 位指数是“全 0”，52 位有效数是“全 0+1”。也就是说其 binary64 的存储存储为 `0x0000000000000001`，如下：

![](https://605825044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVUa7MztTVfoHQxXozBQf%2Fuploads%2F0CcMXc94Rr4LISKLAQ5K%2F%E6%B5%AE%E7%82%B9%E6%95%B0-%E5%BC%82%E5%B8%B83.png?alt=media\&token=f97dcd08-ce19-4a61-8142-cfa2f1bd9d03)

真实值就是：$$(-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'
```

### **4.4 缺省结果**

下溢的缺省结果：返回次正规数或 0。示例如下：

```javascript
// 用最小正规数除以2，可以构造出次正规数
2**-1022 / 2; // 1.1125369292536007e-308 < 最大次正规数 2.225073858507201e-308

// 用最小次正规数除以2，可以构造出超出浮点数能表示范围的数，此时会返回 0
2**-1074 / 2;  // 0
```

## 5. 不精确

* 原因：有效运算的舍入结果和无限精确结果不相等，即精度被裁剪了。实际上，大多数浮点运算都会导致“不精确”异常，详见[《2. 精度损失》](/cs/floating-point/write.md)。
* 示例：1/3, log(1.1)
* 缺省结果：返回该运算的结果（舍入、上溢或下溢）

```javascript
/// 舍入
1/3; // 0.3333333333333333, 实际值是无限循环小数（有理数）
Math.log(1.1); // 0.09531017980432493

/// 上溢
2**1024; // Infinity，指数超出最大值了 [-1022, +1023]

/// 下溢
2**-1022 / 2; // 用最小正规数除以2，可以构造出次正规数
2**-1074 / 2; // 用最小次正规数除以2，可以构造出超出浮点数能表示范围的数，此时会返回 0
```

## 6. 主要参考

* 异常：<https://docs.oracle.com/cd/E57201_01/html/E57330/z4000ac230278.html>
* 下溢：<https://docs.oracle.com/cd/E57201_01/html/E57330/z4000ac020351.html>
* 小工具：<https://www.binaryconvert.com/result_double.html>
