11. 数字编码
📜 [原文1]
|
无符号 |
符号 & 幅度 |
1 的补码 |
2 的补码 |
| 000 |
0 |
+0 |
+0 |
+0 |
| 001 |
1 |
+1 |
+1 |
+1 |
| 010 |
2 |
+2 |
+2 |
+2 |
| 011 |
3 |
+3 |
+3 |
+3 |
| 100 |
4 |
0 |
-3 |
-4 |
| 101 |
5 |
-1 |
-2 |
-3 |
| 110 |
6 |
-2 |
-1 |
-2 |
| 111 |
7 |
-3 |
0 |
-1 |
|
8个值 |
7个值,2 个零 |
7个值,2 个零 |
8个值,1 个零 |
📖 [逐步解释]
这段内容通过一个表格,对比了四种不同的方法来用3个二进制位(bit)表示数字。这四种方法分别是无符号(Unsigned)、符号与幅度(Sign & Magnitude)、1的补码(1's Complement)和2的补码(2's Complement)。理解这些表示法是理解计算机内部如何处理和存储数字的基础。
- 二进制位(Binary Digits, bits):计算机的基本存储单位,每个位只能是0或1。用3个位,我们可以组合出 $2^3 = 8$ 种不同的模式,从 000 到 111。
- 无符号 (Unsigned) 表示法:这是最直接的表示法。它将每个二进制模式直接映射到一个非负整数。转换规则是标准的二进制转十进制。例如,110 被解释为 $1 \cdot 2^2 + 1 \cdot 2^1 + 0 \cdot 2^0 = 4 + 2 + 0 = 6$。使用3个位,它可以表示从0 (000) 到7 (111) 的8个不同的非负整数。
- 符号与幅度 (Sign & Magnitude) 表示法:为了表示负数,这种方法将最高位(最左边的位)用作符号位。0 代表正数(+),1 代表负数(-)。剩下的位则表示幅度(绝对值),其转换规则与无符号数相同。例如,011 中,最高位是 0(正),剩下的 11 表示3,所以整个数是 +3。对于 101,最高位是 1(负),剩下的 01 表示1,所以整个数是 -1。
- 一个显著的缺点是存在两个零的表示:000 (+0) 和 100 (-0)。这不仅浪费了一个位模式,还给算术运算带来了复杂性。
- 1的补码 (1's Complement) 表示法:这种方法对正数的表示与符号与幅度法相同。对于负数,它的表示是通过将对应正数的所有位进行按位取反(0变1,1变0)得到的。例如,+3 是 011。要得到 -3,我们将 011 的每一位取反,得到 100。
- 和符号与幅度法一样,1的补码也存在两个零:000 (+0) 和 111 (-0),因为 111 是 000 按位取反的结果。
- 2的补码 (2's Complement) 表示法:这是现代计算机中最普遍使用的有符号整数表示法。正数的表示与前两种有符号表示法相同。负数的表示规则是“按位取反,再加1”。例如,要得到 -3,我们先取 +3 的二进制 011,按位取反得到 100,然后再加1,得到 101。
- 2的补码的最大优点是它只有一个零的表示(000)。这使得它可以比前两种方法多表示一个负数(在这个3位的例子中是-4)。同时,它的加减法运算可以统一处理,无需关心操作数的符号,大大简化了硬件设计。
表格最后一行总结了每种表示法的效率:
- 无符号、2的补码:能够表示8个不同的值。
- 符号与幅度、1的补码:由于存在两个零的表示,它们只能表示7个不同的值。
💡 [数值示例]
- 示例1:解释二进制模式 110
- 在无符号表示法中:$1 \cdot 2^2 + 1 \cdot 2^1 + 0 \cdot 2^0 = 4 + 2 + 0 = 6$。
- 在符号与幅度表示法中:最高位是 1,表示负号。剩下的 10 表示 $1 \cdot 2^1 + 0 \cdot 2^0 = 2$。所以,110 代表 -2。
- 在1的补码表示法中:由于最高位是 1,它是一个负数。为了找出它的值,我们先将其按位取反得到 001,这个值是 +1。所以,110 是 -1。
- 在2的补码表示法中:由于最高位是 1,它是一个负数。为了找出它的值,我们先减1得到 101,再按位取反得到 010,这个值是 +2。所以,110 是 -2。另一种更快的计算负数的方法是:最高位 1 代表 $-2^{3-1} = -4$,所以 110 的值是 $-4 + (1 \cdot 2^1 + 0 \cdot 2^0) = -4 + 2 = -2$。
- 示例2:表示数字 -1
- 在符号与幅度表示法中:符号为负,所以最高位是 1。幅度是 1,用剩下的2位表示为 01。所以,-1 是 101。
- 在1的补码表示法中:我们先找到 +1 的表示,即 001。然后按位取反,得到 110。
- 在2的补码表示法中:我们先找到 +1 的表示,即 001。按位取反得到 110,然后加1,得到 111。
⚠️ [易错点]
- 零的表示:最大的易错点在于符号与幅度和1的补码中对零的两种表示(+0 和 -0)。这常常导致混淆,并且是这两种表示法在实际中较少使用的原因之一。
- 范围不对称:在2的补码中,负数的范围比正数多一个。例如,在3位系统中,可以表示-4 (100),但不能表示+4。这个最负的数(-4)没有对应的正数。
- 负数转换:计算2的补码负值时,初学者常常忘记“取反后还要加1”这一步。或者在从负的2的补码转换回十进制时,使用错误的逆向操作。正确的逆向操作是“先减1,再取反”或使用权重法。
- 最高位:在有符号表示中,最高位(MSB)是符号位,不能像无符号数那样简单地计入数值。例如 100 在无符号下是4,但在2的补码下是-4。
📝 [总结]
该表格核心展示了使用有限二进制位表示数字的四种基本编码方案。它清晰地揭示了无符号数、符号与幅度、1的补码和2的补码之间的区别,特别是它们在表示范围和零的表示上的差异。2的补码因其唯一的零表示和简化的算术逻辑,成为现代计算机系统的事实标准。
🎯 [存在目的]
本节的目的是介绍数字在计算机内的底层表示方法。计算机硬件只能处理0和1,因此必须有一套规则来解释这些二进制序列代表的数值。理解这些不同的表示法,尤其是2的补码,对于理解算术运算、数据类型范围、溢出等计算机系统核心概念至关重要。
🧠 [直觉心智模型]
- 无符号:想象一把只有正数刻度的尺子,从0开始。
- 符号与幅度:想象一把中间是0点,向两边对称延伸的尺子。但是这把尺子很奇怪,0点有两个标签,一个叫"+0",一个叫"-0"。
- 1的补码:也像一把对称的尺子,同样有两个0点。可以想象成一个钟表,但0点和对面的点(比如6点)都被特殊标记了。
- 2的补码:想象一个环形的里程表或一个钟表。从0开始顺时针走是正数(1, 2, 3...)。当走到一半(比如12点走到6点的位置)后,继续走就进入了负数区域。比如,从0逆时针走1步是-1,这个位置恰好是顺时针走到底的位置。这个环是连续的,没有重复的刻度。
💭 [直观想象]
想象你有一个只能显示3个灯泡(亮/灭)的装置。
- 无符号:你用它来数数,从0(全灭)到7(全亮)。
- 符号与幅度:你规定第一个灯泡是“颜色灯”,绿色代表加号,红色代表减号。剩下两个灯泡表示数量。但你发现“绿灯+两个灭灯”和“红灯+两个灭灯”都表示没有东西,有点浪费。
- 1的补码:你发明了一种“反色”规则来表示负数。正数是一种颜色组合,负数是完全相反的颜色组合。但你还是发现“全绿”和“全红”这两个状态都代表“无”,还是很别扭。
- 2的补码:你终于找到了一个聪明的办法。你把这些灯泡排列成一个圈,像钟表一样。从“全灭”开始,每亮一个灯泡,数字就加一。转了半圈后,你规定剩下的模式都代表负数,并且它们正好无缝地接回了起点。这样,8种模式,8个不同的数字,完美!
📜 [原文2]
有符号幅度和 1 的补码浪费了一个位模式:2 个表示 0
|
无符号 |
符号 & 幅度 |
1 的补码 |
2 的补码 |
| 000 |
0 |
+0 |
+0 |
+0 |
| 001 |
1 |
+1 |
+1 |
+1 |
| 010 |
2 |
+2 |
+2 |
+2 |
| 011 |
3 |
+3 |
+3 |
+3 |
| 100 |
4 |
-0 |
-3 |
-4 |
| 101 |
5 |
-1 |
-2 |
-3 |
| 110 |
6 |
-2 |
-1 |
-2 |
| 111 |
7 |
-3 |
-0 |
-1 |
| 8个值 |
|
7个值,2 个零 |
7个值,2 个零 |
8个值,1 个零 |
📖 [逐步解释]
这段内容是前一个表格的变体和重申,旨在强调一个核心问题:符号与幅度(Sign & Magnitude)和1的补码(1's Complement)这两种有符号数表示法的固有缺陷。
- 核心论点: “有符号幅度和 1 的补码浪费了一个位模式:2 个表示 0”。这句话是本段的中心思想。在计算机中,每一个二进制模式都是宝贵的资源。如果有两个不同的模式代表同一个数学值(这里是0),那就意味着我们本可以用来表示另一个不同数字的模式被浪费了。
- 表格分析:
- 符号与幅度列:注意 000 被解释为 +0,而 100 被解释为 -0。这是因为在符号与幅度法中,最高位是符号位(0为正,1为负),其余位是幅度。当幅度为 00 时,无论符号位是 0 还是 1,数学上的值都是零。
- 1的补码列:注意 000 被解释为 +0,而 111 被解释为 -0。这是因为在1的补码中,负数是通过将对应正数的所有位取反得到的。+0 的表示是 000,将其所有位取反得到 111,这就成了 -0 的表示。
- 对比与结论:
- 2的补码列中,000 代表 +0,并且没有其他任何模式代表0。这使得它能够充分利用所有的位模式。
- 表格底部的总结再次强调了这一点:“7个值,2 个零” 对比 “8个值,1 个零”。对于一个3位的系统,符号与幅度和1的补码只能表示从-3到+3共7个不同的数值,而2的补码可以表示从-4到+3共8个不同的数值。
这个小小的差异在实际中会产生巨大的影响。拥有两个零的表示法会使算术逻辑单元(ALU)的设计变得复杂,因为在进行加减法后,需要额外检查结果是否为 +0 或 -0,并可能需要将它们统一为一种标准形式。而2的补码完全避免了这个问题,使得加法和减法可以用一套统一的、更简单的硬件逻辑来实现。
💡 [数值示例]
- 示例1: 4位系统中的零
- 在符号与幅度表示法中:
- +0 是 0000 (符号位0, 幅度0)
- -0 是 1000 (符号位1, 幅度0)
- 这里浪费了 1000 这个模式,它本可以用来表示其他数字。
- 在1的补码表示法中:
- +0 是 0000
- -0 是 1111 (对0000按位取反)
- 这里浪费了 1111 这个模式。
- 在2的补码表示法中:
- 0 就是 0000。没有 -0。模式 1000 在4位2的补码中代表-8,1111 代表-1。所有模式都得到了有效利用。
- 示例2: 值域范围比较 (8位系统,如 char 类型)
- 符号与幅度:
- 正数范围: 00000001 到 01111111,即 1 到 127。
- 负数范围: 10000001 到 11111111,即 -1 到 -127。
- 加上 +0 (00000000) 和 -0 (10000000),总共能表示 $127 + 127 + 1 = 255$ 个不同的值。等等,这里应该是能表示 $127 (正) + 127 (负) + 1 (零) = 255$ 个唯一值,但是用了 $256$ 个模式。问题就在于 0 有两个表示。所以实际唯一值的数量是 $2^8 - 1 = 255$ 个。
- 值的范围是 [-127, 127]。
- 2的补码:
- 正数范围: 00000001 到 01111111,即 1 到 127。
- 负数范围: 10000000 到 11111111,即 -128 到 -1。
- 加上 0 (00000000)。
- 总共能表示 $127 + 128 + 1 = 256$ 个不同的值。
- 值的范围是 [-128, 127]。
- 比较可见,2的补码利用了所有256个模式,并提供了一个不对称但更宽的表示范围。
⚠️ [易错点]
- 误认为-0有特殊价值:学生可能会误以为 -0 在某些计算中有特殊用途。在绝大多数情况下,它只是一个冗余的表示,是表示法设计上的一个副作用。
- 混淆不同表示法的负数:在第二个表格中,100 在符号与幅度下是 -0,但在1的补码下是 -3。同一个二进制模式在不同表示法下含义完全不同,这是非常关键且容易出错的一点。
- 对“浪费”的理解:浪费的不仅仅是一个数字的表示,更是硬件资源的浪费和软件复杂度的增加。硬件需要额外的逻辑来处理这两种零,这会增加芯片面积、功耗和延迟。
📝 [总结]
本段通过一个 slightly modified 的表格,尖锐地指出了符号与幅度和1的补码表示法存在“双零”问题的缺陷。这个问题导致了位模式的浪费和算术实现的复杂化,从而反衬出2的补码表示法只有一个零、能表示更多数值的优越性。
🎯 [存在目的]
这一部分的目的是在介绍了多种有符号数表示法之后,进行一次“优劣对比”,为后面只关注2的补码提供充分的理由。它回答了“为什么现代计算机几乎只用2的补码?”这个问题。通过强调其他方案的“浪费”,来凸显2的补码的“高效”。
🧠 [直觉心智模型]
想象你在分配房间号。你有8个房间,你想给8个不同的人住。
- 符号与幅度 / 1的补码的做法:你分配房间时,不小心把“0号”这个标签贴在了两个不同的房间门上(一个叫“正0号”,一个叫“负0号”)。结果,最后有个人没房间住,因为你浪费了一个房间。
- 2的补码的做法:你聪明地规划了房号,从-4到3,每个房间一个唯一的号码。8个房间,8个人,不多不少,完美入住。
💭 [直观想象]
想象一条数轴。
- 符号与幅度 / 1的补码的数轴:在0点的位置,有两个紧紧挨在一起的标记,一个“+0”,一个“-0”。这让数轴看起来有点“臃肿”和“不干净”。
- 2的补码的数轴:0点就是一个干干净净的点。但是数轴有点“不对称”,负半轴比正半轴长那么一点点。虽然不对称,但每个整数都有一个且仅有一个位置,非常清晰。
22.2. 使用二进制加法算法
📜 [原文4]
在 2 的补码中,二进制加法算法总是有效的(除非发生溢出)!
$$
\begin{aligned}
& \text {-e.g., } \\
& \qquad \begin{array}{lr}
111 & \\
+0101 & 5 \\
+1101 & -3 \\
0010 & 2
\end{array}
\end{aligned}
$$
📖 [逐步解释]
这一部分阐述了2的补码表示法最核心的优势:它极大地简化了计算机的算术运算。
- 核心论点: “在 2 的补码中,二进制加法算法总是有效的(除非发生溢出)!”。这句话的含义是,无论两个数字是正数还是负数,你都可以将它们的2的补码形式直接相加,就像它们是无符号数一样。你不需要预先检查它们的符号,也不需要根据符号来决定是做加法还是减法。得到的二进制结果,如果把它解释为2的补码,就是正确的算术和。这使得硬件设计(即算术逻辑单元ALU)可以非常简单和高效。
- “除非发生溢出”: 这是一个至关重要的限定条件。溢出(Overflow)指的是计算结果超出了当前数据类型所能表示的范围。例如,在4位2的补码(范围-8到+7)中,计算 5 + 5 = 10。10 无法在4位2的补码中表示,因此发生了溢出。在这种情况下,虽然二进制加法算法本身仍然按照规则执行,但最终得到的二进制结果不再代表正确的算术和。
- 公式与示例分析:
- 公式部分展示了一个具体的例子:计算 5 + (-3)。
- 在4位2的补码系统中:
- +5 的表示是 0101。
- +3 的表示是 0011。要得到 -3,我们对 0011 按位取反得到 1100,再加1得到 1101。所以 -3 的表示是 1101。
- 二进制加法:
- 加法过程:
- 最右边(最低位): 1 + 1 = 10 (二进制),所以结果位是 0,向左进位 1。
- 第二位: 1 (进位) + 0 + 0 = 1。结果位是 1,无进位。
- 第三位: 1 + 1 = 10 (二进制)。结果位是 0,向左进位 1。
- 第四位(最高位): 1 (进位) + 0 + 1 = 10 (二进制)。结果位是 0,向左进位 1。
- 结果解读: 最终的4位结果是 0010。在2的补码中,0010 代表 +2。这与我们预期的数学结果 5 + (-3) = 2 完全一致。
- 最高进位: 注意,从最高位产生了一个进位 1(在 (1)0010 中用括号表示)。在2的补码加法中,这个从最高位溢出的进位通常被丢弃。
💡 [数值示例]
- 示例1: 负数 + 负数 (4位系统)
- 计算 -2 + (-3)。预期结果是 -5。
- -2 的2的补码: +2 是 0010 -> 取反 1101 -> 加1 1110。
- -3 的2的补码: +3 是 0011 -> 取反 1100 -> 加1 1101。
- 加法:
```
111 (进位)
1110 (-2)
+ 1101 (-3)
-------
(1)1011 (-5)
```
- 结果 1011。这是一个负数。我们来验证它的值:减1 -> 1010 -> 取反 0101 (即5)。所以 1011 确实是 -5。计算正确。
- 示例2: 导致溢出的加法 (4位系统)
- 计算 6 + 7。预期结果是 13。但在4位2的补码(范围-8到+7)中,13 无法表示。
- 6 的2的补码是 0110。
- 7 的2的补码是 0111。
- 加法:
```
111 (进位)
0110 (6)
+ 0111 (7)
-------
1101 (-3)
```
- 结果 1101,在2的补码中代表 -3。这显然不等于 13。这里就发生了溢出。两个正数相加,结果却成了一个负数,这是溢出的典型特征。
⚠️ [易错点]
- 忽略溢出: 最常见的错误是假设加法总是正确的,而忘记检查溢出。在实际编程中,未处理的整数溢出是许多安全漏洞和程序错误的根源。
- 处理最高位进位: 在2的补码加法中,从最高有效位(MSB)产生的进位(carry-out)本身并不直接表示溢出,它应该被丢弃。溢出的判断有专门的规则(后面会讲)。
- 混淆加法和减法: 2的补码的优雅之处在于,减法 A - B 可以通过计算 A + (-B) 来实现。而计算 -B 就是对 B 的二进制表示执行“按位取反再加1”的操作。这样,减法器硬件可以被加法器和一些简单的逻辑门替代,大大节省了成本。
📝 [总结]
本段强调了2的补码表示法的核心优点:它允许计算机使用单一、简单的二进制加法算法来处理所有整数的加法(和减法),无需关心数字的符号。这个特性极大地简化了硬件设计。同时,本段也引入了算术溢出的概念,即当计算结果超出表示范围时,这个简便的算法会得出错误答案。
🎯 [存在目的]
本节的目的是解释“为什么计算机科学家和工程师如此钟爱2的补码?”。答案就是:为了算术运算的“简单”和“统一”。这个看似微小的表示法选择,是构建高效、快速的中央处理器(CPU)的基石之一。
🧠 [直觉心智模型]
再次使用环形里程表的模型。加法就是在这个环上顺时针旋转,减法就是逆时针旋转。
- 5 + (-3) 可以看作:从0点顺时针走5步,然后逆时针走3步。最终停在+2的位置。
- 二进制加法算法的巧妙之处在于,它把“逆时针走3步”等价于“顺时针走 最大值 - 3 步”。在这个8个刻度的环上,逆时针3步和顺时针5步会到达同一个点。而-3的2的补码 101,如果看作无符号数,恰好就是5。所以 5 + (-3) 就变成了在模8的环上计算 5 + 5,5+5=10,10 mod 8 = 2。结果正确!
- 这个模型也很好地解释了溢出。6 + 7 在8位系统(-8到+7)上,是从0顺时针走6步,再顺时针走7步。总共走了13步,在一个只有8个刻度的钟表上,13 mod 8 = 5,不对,这里应该用16个刻度的钟表(-8到+7)。6+7 = 13,在模16的意义下,13就是13,但这个值在我们的有符号范围里不存在。6 (0110) + 7 (0111) = 13 (1101),而 1101 被我们定义为了 -3。所以,你以为你在向前走,但因为走得太远“绕回了”环的负数区域。
💭 [直观想象]
想象你在一条生产线上组装玩具。
- 使用符号与幅度表示法:你面前有两箱零件,你需要先检查箱子上的标签是“+”还是“-”。如果都是“+”,你用加法说明书。如果都是“-”,你也用加法说明书但最后要在成品上贴个“-”标签。如果一个“+”一个“-”,你得拿出另一本减法说明书,还要比较哪个箱子的零件多,来决定最后贴什么标签。非常麻烦。
- 使用2的补码表示法:所有零件箱都用同一种编码。你只有一本说明书,就是“加法”。无论来什么箱子,你只管把它们倒在一起,按照加法说明书组装。组装完的成品自动就是正确的(除非零件太多装不下了,这就是溢出)。这条生产线简单、高效、出错少。
32.3. 使用二进制加法算法
📜 [原文5]
当发生溢出时,结果将差 $2^{\text{字长}}$
📖 [逐步解释]
这句话是对2的补码算术中溢出现象的一个精确定量描述。它告诉我们,当溢出发生时,错误的计算结果和正确的数学结果之间存在一个固定的关系。
- 核心含义: 如果你用一个 k 位的2的补码系统进行加法运算,发生了溢出,那么计算机得到的那个不正确的答案(我们称之为CompuResult)与本应得到的正确数学答案(我们称之为MathResult)之间的差值,正好是 $2^k$ (其中 k 是字长,即二进制的位数)。
- 数学关系:
- CompuResult = MathResult - 2^k (如果 MathResult 太大,发生了正向溢出)
- CompuResult = MathResult + 2^k (如果 MathResult 太小,发生了负向溢出)
- 这两种情况可以统一表示为:CompuResult ≡ MathResult (mod 2^k)。这读作“计算机结果与数学结果在模 $2^k$ 的意义下同余”。模运算(Modulo Operation)是理解计算机整数算术的关键。计算机的所有整数运算,无论是无符号还是2的补码,本质上都是在模 $2^k$ 的环上进行的。
- 为什么是 $2^k$?
一个 k 位的二进制数可以表示 $2^k$ 个不同的状态。想象一个 k 位的计数器,从 00...0 开始计数,每加1,状态变化一次,直到 11...1。如果再加1,会发生什么?
11...1 + 1 = (1)00...0。
最高位产生了一个进位 1,而计数器的 k 位都归零了。这个溢出的进位 1 代表的值就是 $2^k$。由于计算机(通常)只保留 k 位结果,这个进位被丢弃了,相当于整个结果被减去了 $2^k$。这就是“差 $2^{\text{字长}}$”的根本原因。
💡 [数值示例]
- 示例1: 正向溢出 (4位系统)
- 字长 k = 4。所以 $2^k = 2^4 = 16$。
- 计算 6 + 7。
- MathResult = 13。
- 计算机计算:0110 (6) + 0111 (7) = 1101。
- CompuResult = 1101 (在2的补码中是-3)。
- 验证关系:CompuResult 是否等于 MathResult - 2^k?
- -3 = 13 - 16。
- 关系成立!计算机得到的结果 -3 确实比正确答案 13 小了16。
- 示例2: 负向溢出 (4位系统)
- 字长 k = 4。所以 $2^k = 2^4 = 16$。
- 计算 -5 + (-6)。
- MathResult = -11。这个值超出了4位2的补码的范围 [-8, 7]。
- -5 的表示是 1011。
- -6 的表示是 1010。
- 计算机计算:1011 (-5) + 1010 (-6) = (1)0101。丢弃进位后结果是 0101。
- CompuResult = 0101 (在2的补码中是+5)。
- 验证关系:CompuResult 是否等于 MathResult + 2^k?
- 5 = -11 + 16。
- 关系成立!计算机得到的结果 +5 确实比正确答案 -11 大了16。
⚠️ [易错点]
- 字长的意义: k 是字长,不是表示范围内的数字个数。对于8位系统,k=8, $2^k = 256$;对于32位系统,k=32, $2^k$ 是一个非常大的数。
- 正负溢出的混淆: 正向溢出(两个正数相加得到负数)意味着结果“环绕”到了负数区,实际值比正确值小了 $2^k$。负向溢出(两个负数相加得到正数)意味着结果“环绕”到了正数区,实际值比正确值大了 $2^k$。
- 正负数相加: 一个正数和一个负数相加,结果的绝对值一定小于其中绝对值较大的那个数。因此,它们的和一定在表示范围内,永远不会发生溢出。
📝 [总结]
本句精辟地总结了2的补码算术溢出的后果:计算出的错误结果与真实的数学结果精确地相差 $2^k$(其中k为字长)。这揭示了计算机整数算术的本质是模运算。
🎯 [存在目的]
这一知识点不仅帮助我们理解溢出的本质,在某些高级编程技巧和底层算法中甚至可以被利用。例如,一些密码学或哈希算法会故意利用整数运算的环绕特性。同时,它也为我们设计溢出检测逻辑提供了理论基础。
🧠 [直觉心智模型]
想象你的汽车里程表只有4位,最大能显示9999公里。它就是一个模 $10^4$ (10000) 的系统。
- 你的车已经开了9998公里。你再开3公里。
- MathResult = 9998 + 3 = 10001公里。
- 里程表的变化是:9998 -> 9999 -> 0000 -> 0001。
- CompuResult (里程表读数) = 1公里。
- CompuResult = MathResult - $10^4$。即 1 = 10001 - 10000。
- 这个“差 $10^4$”就和计算机里“差 $2^k$”是完全相同的道理。里程表“溢出”了,从最大值绕回了最小值。
💭 [直观想象]
想象你在玩一个圆盘形的棋盘游戏,棋盘有16个格子,编号从-8到+7。
- 你现在在+6的位置,你的骰子掷出了+7。
- 数学上你应该前进7步,到达+13。但棋盘上没有+13。
- 你实际的移动是:从+6 -> +7 -> (越过边界) -> -8 -> -7 -> -6 -> -5 -> -4 -> -3。你最终停在了-3的格子上。
- 你的最终位置(-3)和你本应到达的位置(+13)之间,正好隔了16个格子,也就是整个棋盘的大小 ($2^4$)。你因为走得太远,“掉进了”棋盘的另一端。
33.3. C语言中的补码示例
📜 [原文8]
```
int main() {
int x = 47;
int y=-50;
unsigned int z = 50;
printf("%d %d %uln", x, y, z);
}
```
你(无意中)在哪里使用过 2 的补码?
```
int main() {
int x = 47;
int y=-50;
unsigned int z = 50;
printf("%d %d %uln", x, y, z);
}
- The computer represents
- x and y as signed 2 's complement
- $z$ as an unsigned
- C Demo: https://onlinegdb.com/49zlpDA9P8
- Note:
- $0 \times F$ (in Hex) is $15=1111$ in (unsigned) binary
- $0 \times 8$ (in Hex) is $8=1000$ in (unsigned) binary
- $2^{32}=4,294,967,296$ and $2^{31}=2,147,483,648$
```
📖 [逐步解释]
这部分将前面讨论的理论知识与实际的编程语言(C语言)联系起来,说明我们日常编程中其实一直在不知不觉地使用2的补码。
- C语言中的数据类型:
- 在C语言中,当我们声明一个int类型的变量时,它默认是signed int,即有符号整数。现代计算机体系结构(如x86, ARM)几乎无一例外地使用2的补码来表示有符号整数。
- 因此,int x = 47; 和 int y = -50; 这两行代码,编译器和CPU在底层就是用2的补码来存储和操作x和y的。
- 当我们声明unsigned int z = 50;时,我们明确告诉编译器,z是一个无符号整数。它的底层表示就是标准的二进制。
- printf格式说明符:
- printf函数通过格式说明符来决定如何解释内存中的二进制数据。
- %d:告诉printf把对应的参数(一个int)当作一个有符号的十进制整数来解释和打印。它假定内存中的数据是2的补码形式。
- %u:告诉printf把对应的参数(一个unsigned int)当作一个无符号的十进制整数来解释和打印。
- (注意:原文中的 %uln 可能是笔误,应该是 %u\n,\n是换行符)。
- 核心思想: “你(无意中)在哪里使用过 2 的补码?” 答案是:每当你使用int、short、long、char(通常默认为signed)等有符号整数类型时,你就在使用2的补码。这是编程语言和底层硬件之间的一个约定。你不需要手动进行“取反加一”的操作,编译器和CPU已经为你做好了这一切。
- 笔记解释:
- x and y as signed 2's complement: 确认了x和y是用2的补码表示。
- z as an unsigned: 确认了z是用无符号二进制表示。
- C Demo链接: 提供了一个在线的C语言编译器环境,可以运行这段代码来验证输出。
- Hex (十六进制) 注释: 这是为了说明二进制、十六进制和十进制之间的关系,并为可能涉及的更大数字做铺垫。0xF (十六进制的F) 等于十进制的15,二进制的1111。0x8等于8,二进制的1000。
- 2^32和2^31: 这些是32位系统中常见的边界值。一个32位的无符号整数范围是 0 到 $2^{32}-1$。一个32位的有符号整数(2的补码)范围是 $-2^{31}$ 到 $2^{31}-1$。这些数值定义了unsigned int和int的取值范围。
💡 [数值示例]
- 示例1: 8位系统下的 char
- 假设我们有 signed char y = -50;。
- 一个signed char是8位的。
- +50 的8位二进制是 00110010。
- 要得到 -50 的2的补码:
- 取反: 11001101
- 加1: 11001110
- 所以,在内存中,变量y的8个比特就是11001110。当printf("%d", y);执行时,printf会读取这8个比特,按照2的补码规则把它解释回-50并打印出来。
```c
int y = -50;
unsigned int z;
z = y; // 把一个负数赋给无符号整数
printf("y = %d, z = %u\n", y, z);
```
- 在32位系统中,-50的2的补码是 11111111 11111111 11111111 11001110。
- 当 z = y; 执行时,底层的32个比特被原封不动地复制给了z。
- 现在,printf被要求用 %u (无符号) 来解释z的这堆比特。
- 1111...11001110 作为一个无符号数是一个非常大的正数,它等于 $2^{32} - 50 = 4294967246$。
- 所以,程序的输出会是 y = -50, z = 4294967246。
- 这个例子极好地说明了同一串二进制位在不同解释(%d vs %u)下,代表的数值是天差地别的。
⚠️ [易错点]
- 有符号与无符号混用: 在C语言中,混合使用signed和unsigned整数进行运算,可能会导致非预期的结果。C语言有一套复杂的“整数提升”和“寻常算术转换”规则。一个常见的陷阱是: if (-1 < 0u),这个表达式的结果是false!因为 -1 会被转换成一个非常大的unsigned int,它比0u要大。
- printf格式说明符错误: 如果为int y = -50; 使用 %u 来打印,printf("%u", y);,就会得到那个巨大的正数,而不是-50。这是因为你命令printf用错误的“镜头”去看待内存中的数据。
- 平台依赖性: 虽然标准建议int至少是16位,但在现代几乎所有平台上,int都是32位的。char的符号性(signed or unsigned)在C标准中是实现定义的(implementation-defined),不过在x86等主流平台上通常默认为signed。
📝 [总结]
本节通过一个简单的C语言代码示例,将抽象的2的补码理论与我们日常的编程实践联系起来。它揭示了一个事实:作为程序员,我们一直在使用2的补码,尽管这个过程被编译器和硬件“隐藏”了。理解这一点有助于我们更深刻地理解数据类型、类型转换和潜在的编程陷阱。
🎯 [存在目的]
这一部分的目的是“接地气”,展示理论知识在现实世界中的应用。它弥合了理论与实践之间的鸿沟,让学习者明白,前面所学的关于二进制表示法的知识,并不是屠龙之技,而是理解和编写高质量代码所必需的内功。
🧠 [直觉心智模型]
- int, unsigned int 就像是不同颜色的眼镜。
- 内存中的二进制数据就像是一幅画。
- 当你戴上 int 这副“2的补码眼镜”(通过%d),你看到的是有正有负的正常世界。
- 当你换上 unsigned int 这副“无符号眼镜”(通过%u),你看到的世界里没有负数,原本是负数的地方变成了一些非常刺眼、巨大的正数。
- 画本身(内存中的比特)没有变,变的是你看待它的方式(解释规则)。
💭 [直观想象]
想象你去银行存钱。
- 你存了47元,银行在你的账户(变量x)的“正数账本”上记了一笔。
- 你朋友欠了银行50元,银行在他的账户(变量y)的“负数账本”上记了一笔。这个“负数账本”用的就是2的补码记账法。
- 另一个人参加一个积分活动,获得了50积分,银行在他的账户(变量z)的“积分账本”上记了一笔。这个“积分账本”只能记正数,用的就是无符号记账法。
- 当你去查询余额时,柜员(printf)会根据你要查的是“存款账户”(%d)还是“积分账户”(%u),拿出对应的账本来看,然后告诉你正确的数字。如果你让柜员用看“积分账户”的方法去查你朋友的“负数账户”,他会懵掉,然后报出一个莫名其妙的巨大正数。
17.1. 自动化减法示例
📜 [原文14]
- 问:为什么我们对 2 的补码感兴趣,尽管它看起来如此不直观?
- 答:自动化减法(即加符号相反的 \#)要容易得多
- 例如,字长 6,使用有符号幅度表示执行 14-21
$$
\begin{array}{r}
001110 \\
-010101
\end{array} \longrightarrow-\begin{array}{r}
0110 \\
010101 \\
\hline 001110 \\
\hline 000111
\end{array}
$$
- 有符号幅度有很多潜在的“繁重工作”
- 例如,翻转顶部 & 底部,“从较高位借位”等。
📖 [逐步解释]
这部分通过一个反例——使用有符号幅度进行减法——来突出2的补码的优越性。
- 核心问题: 为什么我们要忍受2的补码这种看起来不那么直接的表示法?
- 核心答案: 因为它能让计算机硬件极大地简化减法运算。计算机的ALU(算术逻辑单元)天生就有一个高效的加法器。如果能把减法也变成加法,那么就不需要再额外设计一个复杂的减法器了。2的补码正好能实现这个目标:A - B 可以通过 A + (-B) 来计算,而 -B 的2的补码可以通过简单的“取反+1”位操作得到。
- 有符号幅度的“繁重工作”: 为了说明这一点,原文展示了用有符号幅度表示法计算 14 - 21 的复杂过程。
- 初始状态:
- +14 在6位有符号幅度下是 001110 (符号0,幅度14)。
- +21 在6位有符号幅度下是 010101 (符号0,幅度21)。
- 减法过程:
- 14 - 21 是一个小数减大数,结果应该是负数。
- 人类做这个减法时,会下意识地把它转换成 -(21 - 14)。
- 计算机硬件要模仿这个过程,就需要一系列的判断和操作:
- 比较幅度: 硬件需要先比较 14 (001110) 和 21 (010101) 的大小。它发现 21 > 14。
- 翻转操作数: 因为是被减数的幅度小,所以硬件需要交换两个数的幅度,变成用大幅度减小幅度,即 21 - 14。
- 确定结果符号: 因为是小数减大数,结果的符号应该是负号。硬件需要记下这个最终符号。
- 执行减法: 现在硬件对两个幅度 010101 和 001110 执行二进制减法。这涉及到复杂的“借位”逻辑,和我们做十进制减法一样。
```
010101 (21)
----------
000111 (7)
```
- 组合结果: 最后,硬件将第3步确定的负号(1)和第4步得到的幅度 000111 组合起来,得到最终结果 100111,它在有符号幅度下表示 -7。
- 总结: 整个过程涉及到:比较大小、可能地交换操作数、确定符号、执行带借位的减法、最后再组合。这一系列“如果...那么...”的逻辑,对于硬件设计来说是非常复杂和低效的。
💡 [数值示例]
- 示例: 用有符号幅度计算 -14 - 21
- -14 是 101110,+21 是 010101。
- 这是一个负数减正数,等于负数加负数: (-14) + (-21)。
- 硬件需要:
- 判断两个数符号。发现一个是负,一个是正,且是减法。或者发现是两个负数相加。
- 取出两个数的幅度: 14 和 21。
- 将两个幅度相加: 001110 + 010101 = 100011 (35)。
- 确定结果符号: 负数加负数,结果是负数。
- 组合结果: 符号1 和 幅度100011 -> 1100011。在6位系统里,这是溢出了,因为35超出了6位幅度能表示的范围(最大31)。
⚠️ [易错点]
- 减法和加法的不同逻辑: 在有符号幅度中,加法和减法是完全不同的过程。A+B 和 A-B 都需要根据A和B的符号分成四种情况(正+正,正+负,负+正,负+负)来处理,非常繁琐。
- 硬件实现: 这种复杂的逻辑判断意味着需要更多的门电路、更长的计算延迟和更大的功耗。
📝 [总结]
本节通过展示有符号幅度减法的复杂性——需要比较大小、交换数字、处理借位、单独判断符号——来反衬出“将减法转换为加法”这一思路的巨大价值。它雄辩地证明了,2的补码之所以被采纳,正是因为它能用一套统一、简单的加法硬件来“自动化”所有加减运算。
🎯 [存在目的]
目的是为了让学习者深刻体会到“优雅的”硬件设计是什么样的。通过展示一个“笨拙”的设计(有符号幅度的减法器),来 appreciation 2的补码所带来的“简洁之美”。这是一种通过对比来加强理解的教学方法。
🧠 [直觉心智模型]
- 有符号幅度减法: 就像一个做事死板的机器人。你让它算 14-21,它不会直接算。它会先拿出两个尺子量一下14和21谁长,发现21长。然后它把题目改成 21-14,算出来是7,然后自言自语:“哦,原来是小数减大数,那我得在答案前面加个负号”。整个过程充满了判断和特殊处理。
- 2的补码减法: 像一个聪明的机器人。你让它算 14-21,它二话不说,直接把 21 扔进一个“反转机”里变成了 -21 的代码,然后把 14 和这个新代码扔进一个“加法机”里。出来的结果直接就是 -7 的代码。一步到位,没有如果。
💭 [直观想象]
想象你在厨房做菜。
- 有符号幅度减法: 菜谱上写着“减去21克盐”。你需要:1. 称一下盘子里现在有多少盐(14克)。2. 比较一下,发现不够减。3. 于是你把菜谱反过来想:“相当于净增加 -7克盐”。4. 但你没有负的盐,所以你先把所有盐(14克)都取出来,再重新称21克盐,再从这21克里称出14克放回盘子里,剩下的7克扔掉,最后在盘子旁边贴个条“这是一个欠了7克盐的菜”。过程极其复杂。
- 2的补码减法: 菜谱上写着“减去21克盐”。你有一个“反物质盐瓶”(2的补码的负数)。你直接从这个瓶子里取出“21克反物质盐”加到盘子里。盘子里的14克盐和“21克反物质盐”一中和,自动就变成了“-7克盐”的状态。简单快捷。
18.1. 补码减法示例
📜 [原文15]
- 只需取反减数(减法中的底数)并加
- 例如,字长 6,使用 2 的补码表示执行 14-21
$$
\begin{array}{r}
001110 \\
-010101 \\
\hline
\end{array}
$$
2 的补码减法:利用 BAA
- 只需取反减数(减法中的底数)并加
- 例如,字长 6,使用 2 的补码表示执行 14-21

$X=111001, -X=000111=7, X=-7$
📖 [逐步解释]
这一部分紧接上一节,正面演示了使用2的补码执行减法是多么简洁高效。
- 核心方法: “只需取反减数(减法中的底数)并加”。
- 这句话是2的补码减法的操作口诀。
- “减数” (Subtrahend): 在 A - B 中,B 是减数。
- “取反”: 这里的“取反”指的是数学上的取反(negation),即求相反数。在2的补码系统中,这个操作对应于“按位取反再加1”的位操作。
- 所以,计算 A - B 的完整流程是:
- 保持 A 的二进制不变。
- 计算 B 的2的补码相反数,即 ~B + 1。
- 将 A 和 ~B + 1 用标准的二进制加法算法(BAA)相加。
- 示例演示: 14 - 21,使用6位2的补码。
- 字长 k=6。范围是 $[-2^{5}, 2^{5}-1]$,即 [-32, 31]。
- +14 的6位2的补码是 001110。
- +21 的6位2的补码是 010101。
- 步骤1: 取反减数
- 减数是 21 (010101)。我们需要计算 -21 的2的补码。
- 按位取反: 010101 -> 101010。
- 加1: 101010 + 1 = 101011。
- 所以,-21 的2的补码是 101011。
- 步骤2: 相加
- 现在问题变成了 14 + (-21)。
- 二进制计算: 001110 + 101011
```
11111 (进位)
001110 (14)
+ 101011 (-21)
----------
111001 (-7)
```
- 结果解读:
- 计算结果是 111001。
- 这是一个负数。我们来验证它的值。
- 逆操作:减1 -> 111000 -> 按位取反 000111。
- 000111 是 7。所以 111001 是 -7。
- 数学结果 14 - 21 = -7。
- 计算机的计算结果与数学结果完全一致。没有判断,没有借位,只有一次“取反+1”和一次加法。
- 图片和公式的解释:
- 图片清晰地展示了上述的计算过程。
- 最后的 $X=111001, -X=000111=7, X=-7$ 是对结果的验证。它表明,如果你对结果 111001 (X) 执行2的补码取反操作,你会得到 000111,它的值是 7,这说明原来的 X 确实是 -7。
💡 [数值示例]
- 示例1: 8位系统, 100 - 50
- A = 100 (01100100)
- B = 50 (00110010)
- 计算 -B (即 -50):
- ~B: 11001101
- ~B+1: 11001110
- 加法 A + (-B):
```
01100100 (100)
+ 11001110 (-50)
-----------
(1)00110010 (50)
```
- 示例2: 8位系统, -100 - 30 (导致溢出)
- A = -100。+100是01100100 -> ~是10011011 -> +1是10011100。
- B = 30 (00011110)。
- 计算 -B (即 -30):
- ~B: 11100001
- ~B+1: 11100010
- 加法 A + (-B):
```
1 1 (进位)
10011100 (-100)
+ 11100010 (-30)
-----------
(1)01111110 (126)
```
- MathResult = -130。这超出了8位2的补码范围 [-128, 127]。
- CompuResult = 01111110,即 +126。
- 发生了溢出。两个负数相加得到了一个正数。
- 验证模运算: $126 \equiv -130 \pmod{256}$ (因为 $126 = -130 + 256$)。
⚠️ [易错点]
- 对谁取反: 一定要搞清楚是减数(第二个数字)需要被取反,而不是被减数。
- 取反操作的精确含义: “取反”在2的补码语境下,特指“按位取反再加一”,而不是简单的按位取反。
- 减法溢出: 减法同样会发生溢出。A - B 发生溢出的条件,等价于 A + (-B) 发生溢出的条件。例如,一个正数减一个负数,如果结果太大就会正向溢出。一个负数减一个正数,如果结果太小就会负向溢出。
📝 [总结]
本节通过一个清晰的、按部就班的例子,展示了2的补码减法的“魔法”:它将一个复杂的减法问题,通过一次简单的位操作(求补),转化成了一个纯粹的加法问题,然后利用高效的二进制加法算法(BAA)一举解决。这正是2的补码成为现代计算机基石的核心原因。
🎯 [存在目的]
在上一节“批判”了有符号幅度之后,本节的目的是“立”,即正面展示2的补码方案是多么的优秀和简洁。通过这种“一反一正”的对比,让学习者对2的补-码的必要性和优越性建立起牢固的认识。
🧠 [直觉心智模型]
回到“聪明的机器人”模型。这个机器人只有一个工具:“加法锤”。
- 当被要求计算 A - B 时,机器人不换工具。
- 它先把 B 扔进一个“反转黑洞”里,出来一个 B' (即 -B 的2的补码)。
- 然后它拿起“加法锤”,把 A 和 B' 敲在一起。
- 最终得到的产物就是正确答案。
- 整个流程高度统一、自动化,不需要思考和判断。
💭 [直观想象]
你是一个银行出纳,你的任务是计算 账户A - 账户B。
- 你不需要做减法。你的流程是:
- 找到账户B的账本。
- 拿出一张特殊的“复印纸”(2的补码取反操作),盖在账本上复印一下,得到一张“负债单” -B。
- 把你手里的账户A的存款单和这张“负债单” -B 一起用加法计算器加起来。
- 计算器显示的结果就是最终的净值。整个银行系统只需要加法计算器,而不需要减法计算器。
1010. 2 的补码中的溢出检测很简单
📜 [原文17]
-如果最终两个进位匹配,则没有溢出 - 如果不同,则溢出 - 例如,字长 $=4$ |
|
|
|
| $5+1=6$ |
$-5+-3=-8$ |
$-2+7=5$ |
$-2+-7=-9$ |
| 00 |
11 |
11 |
10 |
| + |
+ |
+ |
+ |
| $\underline{0001}$ |
$\underline{1101}$ |
0111 |
$\underline{1001}$ |
| 0110 |
1000 |
0101 |
0111 |
$$
\begin{aligned}
& \begin{array}{l}
7+7=14 \\
0 \\
0111 \\
+{ }_{\underline{0111}}^{\underline{0111}} \\
\text { overflow }
\end{array}
\end{aligned}
$$
- 需要考虑 3 种情况:
- 两个 \# 都是正数(即最高位都是 0)
- 两个 \# 都是负数(即最高位都是 1)
- 一个是正数(最高位 0),另一个是负数(最高位 1)
📖 [逐步解释]
这部分介绍了2的补码(有符号数)加法中一种非常巧妙且高效的溢出检测方法。
- 核心规则: “如果最终两个进位匹配,则没有溢出。如果不同,则溢出。”
- 进位到最高有效位(MSB)的进位。我们称之为 C_in_msb。
- 从最高有效位(MSB)产生的进位。我们称之为 C_out_msb。
- 所以,溢出的条件是 C_in_msb != C_out_msb。在逻辑电路中,这可以用一个异或门(XOR)来简单实现:Overflow = C_in_msb XOR C_out_msb。
- 这个规则的美妙之处在于它完全不依赖于操作数的符号,是一个纯粹的、基于进位的硬件级别判断。
- 表格示例分析 (字长k=4):
- Case 1: 5 + 1 = 6
- 5=0101, 1=0001。
- 0101 + 0001。
- 进位到MSB(第3位)的进位是0。(C_in_msb=0)
- 从MSB产生的进位是0。(C_out_msb=0)
- C_in_msb == C_out_msb (0 == 0)。没有溢出。结果 0110 (6) 正确。
- 原文表格中的00可能是指这两个进位。
- Case 2: -5 + (-3) = -8
- -5=1011, -3=1101。
- 1011 + 1101。
- 进位到MSB的进位是1。(来自0+1和前一位的进位1)
- 从MSB产生的进位是1。(1(cin)+1+1=11)
- C_in_msb == C_out_msb (1 == 1)。没有溢出。结果 1000 (-8) 正确。
- 原文表格中的11指这两个进位。
- Case 3: -2 + 7 = 5
- -2=1110, 7=0111。
- 1110 + 0111。
- 进位到MSB的进位是1。(来自1+1和前一位的进位1)
- 从MSB产生的进位是1。(1(cin)+1+0=10)
- C_in_msb == C_out_msb (1 == 1)。没有溢出。结果 0101 (5) 正确。
- 原文表格中的11指这两个进位。
- Case 4: -2 + (-7) = -9 (溢出)
- -2=1110, -7=1001。
- 1110 + 1001。
- 进位到MSB的进位是0。(1+0和前一位进位1,1+1+0=10,所以进位是1...啊我算错了,1+0+1=10,进到MSB是1)
- 让我重新计算 -2+(-7):
```
11 (内部进位)
1110 (-2)
+ 1001 (-7)
-------
(1)0111 (7)
```
- 进位到MSB的进位是 1 (来自 1+0 和更前一位的进位 1)。C_in_msb = 1。
- 从MSB产生的进位是 1 (来自 1(cin) + 1 + 1 = 11)。C_out_msb = 1。
- 所以 1 == 1,应该没有溢出... 但 -2+(-7) 结果是 -9,明明溢出了。
- 让我重新审视原文表格的 10。10 意味着 C_out_msb=1, C_in_msb=0。
- 这说明我对进位的计算有误。再来一次 -2+(-7):
- 1110 + 1001
- 最低位: 0+1=1
- 第二位: 1+0=1
- 第三位: 1+0=1 (这里 C_in_msb = 0)
- 最高位: 1+1=10 (这里 C_out_msb = 1)
- 啊哈!C_in_msb = 0, C_out_msb = 1。0 != 1。溢出!
- 结果是 0111 (+7)。错误。
- 公式示例分析: 7+7=14
- 7=0111。
- 0111 + 0111。
- 进位到MSB的进位是 1 (来自1+1和前一位的进位1)。C_in_msb = 1。
- 从MSB产生的进位是 0 (来自1(cin)+0+0=1)。C_out_msb = 0。
- C_in_msb != C_out_msb (1 != 0)。溢出!
- 结果 1110 (-2)。错误。
- 原文公式下面画的 0 和 1 (我猜 0111上方的0和1?) 可能就是指 C_out 和 C_in。C_out=0, C_in=1。
- 按符号情况分析:
- 这个溢出检测规则也可以从操作数的符号来理解,这引出了另一种等价的判断方法。
- 正数 + 正数: 结果必须是正数。如果结果变成了负数(即结果的MSB为1),则溢出。
- 负数 + 负数: 结果必须是负数。如果结果变成了正数(即结果的MSB为0),则溢出。
- 正数 + 负数: 结果的绝对值不会超过两个数中绝对值较大的那个,所以永远不会溢出。
💡 [数值示例]
- 示例1: 6 + 3 (4位)
- 6=0110, 3=0011。
- 0110 + 0011 = 1001 (-7)。
- C_in_msb=1, C_out_msb=0。1 != 0 -> 溢出。
- 用符号判断:正+正,结果为负。溢出。
- 示例2: -6 + (-4) (4位)
- -6=1010, -4=1100。
- 1010 + 1100 = (1)0110 (+6)。
- C_in_msb=0, C_out_msb=1。0 != 1 -> 溢出。
- 用符号判断:负+负,结果为正。溢出。
⚠️ [易错点]
- 手动计算进位: 手动计算 C_in_msb 和 C_out_msb 容易出错,需要非常仔细。
- 两种判断方法等价: “进位不匹配”规则和“符号不符合预期”规则是完全等价的,它们是同一现象的两种不同描述。硬件层面用前者,人类思考用后者更直观。
- 只适用于加法: 这个检测规则是针对加法(包括A+(-B)形式的减法)设计的。
📝 [总结]
本节介绍了判断2的补码加法是否溢出的两种等价方法:
- 硬件方法: 检查进入最高位的进位和从最高位出去的进位是否不同。不同则溢出。
- 逻辑方法: 检查操作数和结果的符号。同号相加,结果异号,则溢出。异号相加,永不溢出。
这两种方法都非常高效,使得溢出检测在CPU中可以快速完成。
🎯 [存在目的]
目的是解决之前留下的一个关键问题:如何知道2的补码运算出错了?提供了可靠、高效的溢出检测方法,是构建一个完整的、健壮的算术逻辑单元(ALU)的最后一块拼图。
🧠 [直觉心智模型]
“进位不匹配”的直觉模型:
- C_in_msb: 代表了低位加法的结果是否“大到”需要向符号位“求助”(进位)。
- C_out_msb: 代表了加上符号位本身后,整个数是否“大到”要“冲出”k位的范围。
- 溢出就发生在这两种“大”的判断不一致时。
- 正+正溢出 (C_in=1, C_out=0): 低位部分已经很大了,需要向符号位进位(C_in=1)。但因为两个符号位都是0,加上进位1后,符号位变成了1,没有产生向外的进位(C_out=0)。结果的符号从正变成了负。
- 负+负溢出 (C_in=0, C_out=1): 低位部分不大,没向符号位进位(C_in=0)。但两个符号位都是1,1+1=10,结果的符号位变成了0,还向外产生了进位(C_out=1)。结果的符号从负变成了正。
💭 [直观想象]
想象符号位是一个特殊的“经理”位,其他位是“员工”位。
- C_in_msb: 是员工们的工作量是否大到需要向经理汇报(进位)。
- C_out_msb: 是经理处理完自己的工作和员工的汇报后,是否导致整个项目“爆掉”(向更高层进位)。
- 溢出就像:
- 员工们忙得要死,向经理求助 (C_in=1)。但经理(和另一个正数经理)自己很闲,他处理完汇报后说“项目没问题” (C_out=0)。结果整个团队(结果数)的状态从“正常”(正)变成了“危机”(负)。
- 员工们自己搞定了,没向经理汇报 (C_in=0)。但经理(和另一个负数经理)自己就在处理一个烂摊子,他们俩一合计,直接让项目“爆了” (C_out=1)。结果整个团队的状态从“危机”(负)变成了“看起来一切正常”(正)。
- 只有当经理的判断 (C_out) 和员工的状况 (C_in) 一致时,项目状态才是可信的。
111.1. 情况1:两者皆正
📜 [原文18]
- 情况 1:两者皆正:
- 写下两个最高位进位为 A 和 B
- 写下两个最高位进位为 $A$ 和 $B \quad A B$
- $A=$ 进位 B $+0+0 O X_{k-2} X_{k-3} \ldots X_{0}$
$O Y_{k-2} Y_{k-3} \ldots Y_{0}$
- A 始终为 0!
- 最高位 $= B+0+0$
- $B=1$:将结果解释为负数?
错误
- $B=0$:正常(刚刚执行了无符号 $K-1$ 位
加法)
📖 [逐步解释]
这部分开始对上一节提出的溢出检测规则进行分情况证明。首先证明第一种情况:两个正数相加。
- 设定:
- 我们有两个 k 位的2的补码正数 X 和 Y。
- 因为是正数,它们的最高位(MSB,第k-1位)都是 0。
- X = 0 X_{k-2} ... X_0
- Y = 0 Y_{k-2} ... Y_0
- A 在这里代表 C_out_msb (从最高位产生的进位)。
- B 在这里代表 C_in_msb (进位到最高位的进位)。
- 分析 A (C_out_msb):
- 最高位的加法是 A = B + 0 + 0。这里的两个 0 是 X 和 Y 的符号位。
- B 是从第 k-2 位传来的进位,它要么是 0 要么是 1。
- 所以 A = B。C_out_msb = C_in_msb。
- 等等,原文的推导似乎有点问题或表达不清。
- 让我们严格按照加法来:最高位的计算是 (结果的MSB, A) = B + 0 + 0,这里 (a,b) 表示和为b,进位为a。
- B+0+0 的和就是 B,进位是 0。
- 所以,结果的MSB就是 B,而 A (从最高位出去的进位) 始终为0!
- C_out_msb (即A) = 0。
- 分析 B (C_in_msb):
- B 是从低 k-1 位(数值部分)加法产生的进位。
- B 完全取决于 X_{k-2}...X_0 和 Y_{k-2}...Y_0 的和。
- 结合分析,判断溢出:
- 我们已经确定 A = C_out_msb = 0。
- 溢出规则是 A != B。
- 所以,当两个正数相加时,溢出发生的条件是 0 != B,即 B = 1。
- 不溢出的条件是 A == B,即 B = 0。
- 解读两种情况:
- 情况一: B = 0 (不溢出)
- 从数值部分没有产生进位到符号位。
- C_in_msb = 0, C_out_msb = 0。两者匹配。
- 结果的符号位 = B + 0 + 0 = 0 + 0 + 0 = 0。结果仍然是正数。
- 这相当于两个 k-1 位的无符号数相加,其结果没有超出 k-1 位。一切正常。
- 情况二: B = 1 (溢出)
- 从数值部分产生了一个进位 1 到符号位。
- C_in_msb = 1, C_out_msb = 0。两者不匹配。
- 结果的符号位 = B + 0 + 0 = 1 + 0 + 0 = 1。
- 这意味着,两个正数相加,结果的符号位却变成了 1,即计算机认为结果是一个负数。这是错误的。
- 这就是“正+正=负”型的溢出。
💡 [数值示例]
- 示例1: 4位, 3+2=5 (不溢出)
- X=0011, Y=0010。
- 数值部分 011+010 = 101。没有产生进位。所以 B = C_in_msb = 0。
- 符号位加法 B+0+0 = 0+0+0 = 0。结果的符号位是0,没有向外进位。所以 A = C_out_msb = 0。
- A == B (0==0)。不溢出。结果 0101 (5)。正确。
- 示例2: 4位, 5+4=9 (溢出)
- X=0101, Y=0100。
- 数值部分 101+100 = (1)001。产生了一个进位。所以 B = C_in_msb = 1。
- 符号位加法 B+0+0 = 1+0+0 = 1。结果的符号位是1,没有向外进位。所以 A = C_out_msb = 0。
- A != B (0!=1)。溢出。结果 1001 (-7)。错误。
⚠️ [易错点]
- 原文符号混乱: 原文的A和B定义似乎与标准用法有出入,容易造成混淆。在我的解释中,我将 A 明确为 C_out_msb,B 明确为 C_in_msb,这更清晰。
- 证明的逻辑: 证明的核心是把k位的加法拆分成“数值部分(k-1位)的加法”和“符号位的加法”两部分来分析。
📝 [总结]
本节证明了对于两个正数的2的补码加法:
- 溢出的充要条件是:低(k-1)位的数值部分加法产生了向符号位的进位。
- 这个条件等价于“进入符号位的进位为1,从符号位出去的进位为0”,即 C_in_msb=1, C_out_msb=0。
- 这个条件也等价于“两个正数相加,结果为负数”。
🎯 [存在目的]
提供严谨的数学证明,以支持上一节提出的“进位不匹配即溢出”和“符号不符即溢出”的规则。通过分情况讨论,让这些规则的来源变得清晰可见,而不仅仅是死记硬背的结论。
🧠 [直觉心智模型]
正数区的“溢出”:
- 想象2的补码的环形数轴,0到 $2^{k-1}-1$ 是正数区。
- 两个正数相加,就像从一个正数点出发,再顺时针走几步。
- 如果不溢出,你仍然停留在正数区。
- 如果溢出,说明你走得太远了,越过了正数区的边界($2^{k-1}-1$),“掉进”了负数区。你停下的第一个负数点就是最小的负数(-2^{k-1})。
- 这个“越过边界”的动作,在硬件上就体现为数值部分向符号位产生了进位 1,把原本是 0 的符号位“污染”成了 1。
💭 [直观想象]
想象一个透明的量杯,有一半的高度被涂成了红色(负数区),一半是无色的(正数区)。
- 两个正数相加,就像往杯子里倒了两杯无色的水。
- 不溢出: 水位上升了,但还在无色区域。
- 溢出: 水倒得太多了,水位超过了无色区的顶端,进入了红色区域。虽然你倒的都是“正”的水,但最终的水位线却在“负”的区域里。那个“溢过”分界线的动作,就是 C_in_msb = 1。
211.2. 情况2:两者皆负
📜 [原文19]
- 情况 2:两者皆负:
- 写下两个最高位进位为 A 和 B
- $A=$ 进位 B $+1+1$
$$
\begin{aligned}
& A B \\
+ & 1 X_{k-2} X_{k-3} \ldots X_{0} \\
& 1 Y_{k-2} Y_{k-3} \ldots Y_{0}
\end{aligned}
$$
- A 始终为 1!
- 最高位 = B + 1 + 1
- $B=0$:最高位 = 0:正数 \#?错误
- $B=1$:负数:正常(并且 $\bmod 2^{\mathrm{k}}$ 正确)
📖 [逐步解释]
这部分继续证明,讨论第二种情况:两个负数相加。
- 设定:
- 我们有两个 k 位的2的补码负数 X 和 Y。
- 因为是负数,它们的最高位(MSB)都是 1。
- X = 1 X_{k-2} ... X_0
- Y = 1 Y_{k-2} ... Y_0
- 同样,A 代表 C_out_msb,B 代表 C_in_msb。
- 分析 A (C_out_msb):
- 最高位的加法涉及到 X和Y的符号位(都是1)以及从数值部分传来的进位B。
- 所以最高位的加法是 B + 1 + 1。
- B+1+1 等于 B+2。在二进制里,2是10。
- 所以 B+1+1 的结果是:和为 B,进位为 1。
- 因此,A (从最高位出去的进位) 始终为1!
- C_out_msb (即A) = 1。
- 分析 B (C_in_msb):
- B 仍然是数值部分(低 k-1 位)加法产生的进位,可能是0或1。
- 结合分析,判断溢出:
- 我们已经确定 A = C_out_msb = 1。
- 溢出规则是 A != B。
- 所以,当两个负数相加时,溢出发生的条件是 1 != B,即 B = 0。
- 不溢出的条件是 A == B,即 B = 1。
- 解读两种情况:
- 情况一: B = 1 (不溢出)
- 从数值部分产生了一个进位 1 到符号位。
- C_in_msb = 1, C_out_msb = 1。两者匹配。
- 结果的符号位 = (B+1+1) 的和 = (1+1+1) 的和 = 1。结果仍然是负数。
- 一切正常。结果在模$2^k$下是正确的,并且也在表示范围内。
- 情况二: B = 0 (溢出)
- 从数值部分没有产生进位到符号位。
- C_in_msb = 0, C_out_msb = 1。两者不匹配。
- 结果的符号位 = (B+1+1) 的和 = (0+1+1) 的和 = 0。
- 这意味着,两个负数相加,结果的符号位却变成了 0,即计算机认为结果是一个正数。这是错误的。
- 这就是“负+负=正”型的溢出。
💡 [数值示例]
- 示例1: 4位, -3+(-2)=-5 (不溢出)
- X=-3 (1101), Y=-2 (1110)。
- 数值部分 101+110 = (1)011。产生了一个进位。所以 B = C_in_msb = 1。
- 符号位加法 B+1+1 = 1+1+1 = 11 (二进制)。结果的符号位是1,向外进位是1。所以 A = C_out_msb = 1。
- A == B (1==1)。不溢出。结果 1011 (-5)。正确。
- 示例2: 4位, -6+(-5)=-11 (溢出)
- X=-6 (1010), Y=-5 (1011)。
- 数值部分 010+011 = 101。没有产生进位。所以 B = C_in_msb = 0。
- 符号位加法 B+1+1 = 0+1+1 = 10 (二进制)。结果的符号位是0,向外进位是1。所以 A = C_out_msb = 1。
- A != B (1!=0)。溢出。结果 0101 (+5)。错误。
⚠️ [易错点]
- 对 A=1 的理解: 两个负数相加,从符号位一定会产生一个进位出去。这本身是正常的,不代表溢出。是否溢出,取决于这个出去的进位 (A) 是否和进来的进位 (B) 相等。
- 负数世界的环绕: 负+负溢出,可以理解为在环形数轴上,从一个负数点出发,逆时针走得太远,越过了负数区的边界(如-8),“掉进”了正数区。
📝 [总结]
本节证明了对于两个负数的2的补码加法:
- 溢出的充要条件是:低(k-1)位的数值部分加法没有产生向符号位的进位。
- 这个条件等价于“进入符号位的进位为0,从符号位出去的进位为1”,即 C_in_msb=0, C_out_msb=1。
- 这个条件也等价于“两个负数相加,结果为正数”。
🎯 [存在目的]
这是对溢出检测规则证明的第二部分,与前一节共同构成了一个完整的证明链条,解释了为什么“同号相加,结果异号”是溢出的标志。
🧠 [直觉心智模型]
负数区的“溢出”:
- 在2的补码环形数轴上,负数区通常在左半边。
- 两个负数相加,就像从一个负数点出发,再逆时针走几步。
- 不溢出: 你仍然停留在负数区。
- 溢出: 你逆时针走得太远了,越过了负数区的边界(最小负数,如-8),“掉进”了正数区的尾部(如+7)。
- 这个“越过边界”的动作,在硬件上就体现为:符号位 1+1 产生的进位 1 “跑了出去” (C_out=1),但数值部分又不够“大”来提供一个进位(C_in=0)去“填补”符号位,导致符号位变成了0。
💭 [直观想象]
回到那个红/无色量杯。
- 两个负数相加,就像往杯子红区的液面下,注入了两管“制冷剂”(让液面下降)。
- 不溢出: 液面下降了,但还在红色区域。
- 溢出: 制冷剂加得太多了,液面降得太低,穿过了红色区的底部,进入了无色区域。虽然你加的都是“负”的制冷剂,但最终的水位线却在“正”的区域里。那个“穿过”底线的动作,就是 C_in_msb=0 (数值部分没啥动静) 和 C_out_msb=1 (符号位自己搞出了大新闻) 的不匹配。
112.1. 溢出示例分析
📜 [原文21]
- 不能过分强调溢出意味着结果无法在字长内表示(不一定是结果太大)!
- 示例:假设我选择一个(奇怪的)方式将 2-位字映射到值,如下所示:
两位字 word |
关联值 Value |
| 00 |
1 |
| 01 |
3 |
| 10 |
6 |
| 11 |
2 |
$$
\begin{aligned}
& 00+00=11 \text { (i.e., } 1+1=2 \text { ) } \\
& 00+01=? \text { ? } \\
& 1+3 \text { should equal } 4 \text {, but no 2-bit combo } \\
& \text { represents } 4
\end{aligned}
$$
因此,对于此表示,00+01 导致溢出
📖 [逐步解释]
这部分通过一个非常规的、人为设计的例子,来回归和强调溢出最根本的定义。
- 核心论点: 溢出的本质是“结果无法在当前表示法下表示”,而不一定是因为结果“太大”或“太小”。它完全取决于你如何定义二进制模式与数值之间的映射关系。
- 奇怪的表示法:
- 我们抛弃所有前面学的标准表示法,自己发明一个。
- 用2个位,我们有4个模式:00, 01, 10, 11。
- 我们规定它们的映射关系是:
- 00 -> 1
- 01 -> 3
- 10 -> 6
- 11 -> 2
- 这个表示法所能表示的数值集合是 {1, 2, 3, 6}。
- 在这个表示法下进行运算:
- 示例1: 1 + 1
- 1 对应的二进制是 00。
- 所以 1+1 对应于二进制加法 00 + 00。
- 00 + 00 = 00。
- 等等,原文写的是 00+00=11。这说明这个奇怪的系统里,连加法器(BAA)都被修改了!它可能是一个自定义的逻辑。但我们先假设加法器还是标准的BAA。
- 00 + 00 = 00。结果 00 对应的值是 1。
- 数学上 1+1=2。计算机结果是 1。结果不符。
- 让我们采信原文的 00+00=11。这意味着这个系统的“加法规则”是自定义的。根据这个规则,00和00相加,结果的二进制模式是11。
- 11 在我们的表示法里对应的值是 2。
- 数学上 1+1=2。计算机结果是 2。在这种自定义加法下,1+1 没有溢出。
- 示例2: 1 + 3
- 1 对应的二进制是 00。
- 3 对应的二进制是 01。
- 数学上的和应该是 1 + 3 = 4。
- 关键问题: 在我们的表示法 {1, 2, 3, 6} 中,有没有 4 这个值?没有。
- 结论: 因为数学结果 4 无法用我们这套奇怪的表示法来表示,所以 1+3 这个运算,在这个系统里,必然导致溢出。
- 无论 00 + 01 的二进制结果是什么(是01也好,是10也好,或其他),它解读出来的值(3 或 6)都不等于 4。
💡 [数值示例]
- 示例: 在这个奇怪的系统里计算 3+3
- 3 对应 01。
- 数学结果: 3 + 3 = 6。
- 检查表示范围: {1, 2, 3, 6}。6 在范围内!
- 所以 3+3 有可能不溢出,前提是二进制加法的结果必须正确地得到 10。
- 假设使用标准BAA: 01 + 01 = 10。
- 10 在我们的表示法里对应的值是 6。
- 计算机结果 6 和数学结果 6 相符。所以,3+3 在这个系统里不溢出。
⚠️ [易错点]
- 默认标准表示法: 我们太习惯于无符号和2的补码了,以至于会下意识地认为溢出就是“大于最大值”或“小于最小值”。这个例子提醒我们,溢出的定义更具普遍性。
- 表示范围的“空洞”: 这个奇怪的表示法 {1, 2, 3, 6} 是不连续的,它中间有“空洞”(缺少0, 4, 5)。任何运算结果如果恰好掉在这些空洞里,就是溢出。
📝 [总结]
本节通过一个极端的人造例子,回归了溢出最根本的定义:当且仅当一个运算的精确数学结果,不在当前表示法所能表示的数值集合之内时,该运算发生溢出。这与结果是否“太大”或“太小”没有必然联系,完全取决于表示法的定义。
🎯 [存在目的]
目的是为了打破思维定势,加深对溢出本质的理解。通过展示一个“反直觉”的例子,它促使我们从最第一性的原理出发去思考问题,而不是仅仅依赖于在标准表示法下总结出的规则(如“正+正=负”)。
🧠 [直觉心智模型]
你有一台只能显示特定单词的打字机。
- 表示法: 你的打字机只能打出 {"CAT", "DOG", "PIG", "COW"} 这四个词。
- 运算: 你在做一个填字游戏,"C" + "A" + "T" 应该得到 "CAT"。
- 不溢出: 游戏的结果是 "DOG",你的打字机可以打出来。
- 溢出: 游戏的结果是 "BIRD"。但你的打字机根本没有 "BIRD" 这个键。你想打出结果,但是做不到。这就是溢出。问题不在于 "BIRD" 是一个特别长或特别短的词,仅仅是因为它“不在你的词汇表里”。
💭 [直观想象]
你有一个自动售货机,它只卖四种饮料:可乐(1号按钮),雪碧(2号按钮),芬达(3号按钮),橙汁(6号按钮)。
- 表示法: {可乐, 雪碧, 芬达, 橙汁} 对应 {1, 3, 6, 2}。
- 你想买一杯“奶茶”(值=4)。
- 你站在售货机前,发现根本没有“奶茶”这个选项。
- 你的购买行为“溢出”了,因为你想要的东西,这台机器不提供。
113.1. 需要更大的范围?浮点表示
📜 [原文22]
- 改变编码。
- 浮点(用于以紧凑方式表示非常大的数字)
- 很像科学记数法:
$$
-7.776 \times 10^{3}=-7776=-6^{5}
$$
尾数
注意:尾数总是采用 X.XX... 形式
(又名小数部分)
(小数点前一位)
📖 [逐步解释]
这一部分标志着从整数表示到浮点数表示的过渡。前面讨论的所有整数表示法,无论是无符号还是2的补码,都存在一个共同的局限性:对于固定的位数(如32位),它们能表示的数值范围是有限的,并且无法表示小数。
- 问题背景: 如果我们需要表示非常大(如宇宙的质量)或非常小(如电子的质量)的数字,或者需要表示带有小数部分的数字(如π=3.14159...),那么固定位数的整数表示法就不够用了。即使是64位的long long,也只能表示到 $10^{18}$ 左右,这在科学计算中是远远不够的。
- 解决方案: “改变编码”。我们不再让每一位都代表一个固定的权重(如$2^0, 2^1, 2^2...$),而是采用一种更灵活的编码方式——浮点(Floating-Point)。
- 核心思想:科学记数法: 浮点表示法的核心思想借鉴了我们都熟悉的科学记数法。
- 在十进制科学记数法中,任何一个数都可以表示成 A × 10^B 的形式。
- A 被称为尾数 (Mantissa) 或有效数 (Significand)。
- 10 是基数 (Base)。
- B 是指数 (Exponent)。
- 示例: -7776 可以写成 -7.776 × 10^3。
- 这里的 - 是符号。
- 7.776 是尾数。
- 10 是基数。
- 3 是指数。
- 通过调整指数 B 的大小,我们可以让小数点“浮动”起来,从而表示范围极广的数字。例如,1.23 × 10^30 是一个巨大的数,1.23 × 10^-30 是一个极小的数。
- 尾数的规范化 (Normalization):
- “尾数总是采用 X.XX... 形式(小数点前一位)”。
- 这是一个重要的约定,称为规范化。在十进制科学记数法中,我们通常要求尾数的整数部分是一个非零的个位数,即 1 ≤ |A| < 10。
- 例如,123 不会写成 123 × 10^0 或 0.123 × 10^3,而是规范地写成 1.23 × 10^2。
- 这样做的好处是,对于任何一个非零数,其规范化的科学记数法表示是唯一的。这对于标准化存储和计算至关重要。
💡 [数值示例]
- 示例1: 巨大的数
- 地球的质量大约是 5,972,000,000,000,000,000,000,000 千克。
- 用科学记数法表示为 $5.972 \times 10^{24}$ 千克。
- 这里,符号是正(省略),尾数是5.972,指数是24。用这种方式,我们只用了几个数字就简洁地表示了一个极大的量。
- 示例2: 微小的数
- 一个水分子的质量大约是 0.0000000000000000000000299 克。
- 用科学记数法表示为 $2.99 \times 10^{-23}$ 克。
- 这里,尾数是2.99,指数是-23。
⚠️ [易错点]
- 精度损失: 浮点表示法虽然范围广,但它的精度是有限的。尾数的位数决定了它能表示多少位有效数字。例如,如果尾数只能存3位有效数字,那么 1.2345 × 10^10 就会被近似为 1.23 × 10^10,损失了 0.0045 × 10^10 的信息。这是浮点数与理想实数最大的区别。
- 基数: 我们习惯于基数为10的科学记数法。但计算机是二进制的,所以计算机内的浮点表示法,其基数是2。
📝 [总结]
本节介绍了浮点表示法的基本思想,它通过借鉴十进制的科学记数法,将一个数拆分为符号、尾数和指数三部分来表示。这种方法允许用固定的位数(例如32位或64位)来表示一个范围极广且包含小数的数值集合,从而克服了整数表示法的局限性。
🎯 [存在目的]
本节的目的是为了引入一种全新的数字编码方案,以解决整数表示法无法处理小数和极大/极小数的问题。它是后续所有关于浮点数标准(IEEE 754)、运算、精度和误差等讨论的开端。
🧠 [直觉心智模型]
- 整数表示法: 就像一把刻度均匀的尺子。每个刻度之间的距离都是1。尺子的总长度是固定的。
- 浮点表示法: 像一个“可伸缩的”尺子,或者说是一个尺子加上一个放大镜。
- 尾数: 是尺子本身,但它很短,只有有限的几个刻度(决定了精度)。
- 指数: 是放大镜的倍数。
- 当你看一个很远、很大的物体时,你用一个高倍率的放大镜(大指数),尺子上的一个小小刻度可能就代表一千米。
- 当你看一个很近、很小的物体时,你用一个更高倍率的放大镜(负指数),尺子上的一个刻度可能只代表一微米。
- 通过改变放大镜的倍率(指数),这把短尺子(尾数)可以测量各种尺度的东西。
💭 [直观想象]
你正在给一个巨大的城市绘制地图。
- 整数画法: 你想用1厘米代表1米。但城市太大了,你的纸(固定的位数)根本画不下。
- 浮点画法: 你在地图旁边加了一个“比例尺”说明。
- 画市中心时,你的比例尺是“1厘米 = 10米”(小指数)。
- 画整个城市轮廓时,你的比例尺是“1厘米 = 10公里”(大指数)。
- 尾数: 就是你在图上画的那些线条的长度。
- 指数: 就是那个比例尺。
- 通过改变比例尺,你在同一张纸上既能画出建筑的细节,又能画出整个城市的宏观布局。
213.2. 浮点表示示例
📜 [原文23]
$$
-1.10 \times 2^{0111}\left(=-1.5^{*} 2^{7}\right)
$$
注意:在正确形式中,对于二进制,尾数总是 1.XX... (小数点前一位,且该位总是 1)
唯一例外:$0=0.0 \times 2^{0}$
📖 [逐步解释]
这一部分将上一节介绍的通用科学记数法思想,具体应用到计算机的二进制世界中。
- 从十进制到二进制: 计算机不使用基数10,它使用基数2。所以,浮点数的通用形式从 A × 10^B 变成了 A × 2^B,其中 A 和 B 也都是用二进制表示的。
- 二进制浮点数示例:
- -1.10 × 2^0111
- 符号: -,是负数。
- 尾数 (Mantissa): 1.10。这是一个二进制小数。它的值是 $1 \cdot 2^0 + 1 \cdot 2^{-1} + 0 \cdot 2^{-2} = 1 + 0.5 + 0 = 1.5$ (十进制)。
- 基数 (Base): 2。
- 指数 (Exponent): 0111。这是一个二进制整数。它的值是 $0 \cdot 2^3 + 1 \cdot 2^2 + 1 \cdot 2^1 + 1 \cdot 2^0 = 4 + 2 + 1 = 7$ (十进制)。
- 整合: 整个数的值是 -1.5 × 2^7。
- 计算: 2^7 = 128。所以值是 -1.5 × 128 = -192。
- 二进制的规范化:
- “在正确形式中,对于二进制,尾数总是 1.XX... ”。
- 这对应于十进制科学记数法中要求尾数 1 ≤ |A| < 10 的规则。在二进制中,任何一个非零数,我们都可以调整其指数,使得其尾数的整数部分为 1。
- 例如,一个二进制数 10110.1。
- 它可以写成 10110.1 × 2^0。
- 为了规范化,我们把小数点向左移动4位,变成 1.01101。
- 每向左移动一位,相当于除以2,所以指数需要加1。移动4位,指数就加4。
- 所以,规范化的形式是 1.01101 × 2^4。
- 为什么总是1.XX...? 因为在一个二进制数中,最高位的非零位必然是 1。我们总是可以移动小数点,让它紧跟在这个最高的1后面。
- 隐含位 (Implicit Bit) 的思想:
- 既然对于任何非零数,规范化后的尾数第一位总是1,那我们还有必要在内存里专门花一个比特来存储这个1吗?
- 答案是没必要。我们可以约定,在存储时,我们只存储小数点后面的部分(XX...),而在计算时,由硬件自动在前面“补”上那个 1.。
- 这个被省略的、但大家都心知肚明的 1,被称为“隐含位”或“隐藏位”(Hidden Bit)。这是IEEE 754标准中的一个关键优化,它使得在相同的位数下,尾数的精度可以凭空多出一位。
- 唯一的例外:
- 数字 0 是一个例外。0 无法写成 1.XX... 的形式。
- 因此,需要一个特殊的表示来处理 0。在IEEE 754标准中,当指数和尾数的所有位都为0时,表示的数就是0。
💡 [数值示例]
- 示例1: 表示十进制数 9.75
- 步骤1: 整数和小数部分分开转二进制。
- 9 (dec) = 1001 (bin)。
- 0.75 (dec) = 0.5 + 0.25 = $1 \cdot 2^{-1} + 1 \cdot 2^{-2}$ = .11 (bin)。
- 所以 9.75 (dec) = 1001.11 (bin)。
- 步骤2: 规范化。
- 1001.11,小数点需要向左移动3位,才能变成 1.00111。
- 所以规范化形式是 1.00111 × 2^3。
- 符号:正。尾数:1.00111。指数:3。
- 示例2: 解读二进制浮点数 +1.01 × 2^-2
- 符号:正。
- 尾数:1.01 (bin) = $1 + 0 \cdot 0.5 + 1 \cdot 0.25 = 1.25$ (dec)。
- 指数:-2 (dec)。
- 值 = 1.25 × 2^-2 = 1.25 × 0.25 = 0.3125 (dec)。
⚠️ [易错点]
- 二进制小数转换: 十进制小数转二进制小数采用“乘2取整法”,容易出错。十进制有限小数,转成二进制可能是无限循环小数(例如 0.1 (dec))。这是浮点数精度问题的根源之一。
- 忘记隐含位: 在分析实际的浮点数内存布局时(如下一节的IEEE 754),初学者常常忘记计算时要把那个“隐藏的1”加回来。
- 0的处理: 0是一个需要特殊规则处理的边界情况。
📝 [总结]
本节将浮点表示的核心思想从十进制平移到了二进制。关键知识点包括:1) 形式为尾数 × 2^指数;2) 二进制的规范化形式是 1.XX...;3) 这个1.可以被省略以节省空间(隐含位思想);4) 数字0是唯一的例外,需要特殊处理。
🎯 [存在目的]
本节是连接“科学记数法”这个通用概念和“IEEE 754标准”这个具体实现的桥梁。它解释了为什么IEEE 754标准会设计成后面我们将要看到的样子,特别是“隐含位”这个设计的理论基础。
🧠 [直觉心智模型]
“隐含位”心智模型:
- 这就像一个俱乐部,会员卡上只需要印你的名字的后半部分。因为所有会员的姓都一样,比如姓“王”。所以卡上印着“二小”,大家自动知道你叫“王二小”。这个“王”就是隐含位。
- 这样做的好处是,在同样大小的卡片上可以印更长的名字,比如“麻子”,大家知道你叫“王麻子”。精度更高了。
- 唯一的例外是俱乐部的创始人,他不姓王,他就叫“零”。他的卡是空白的,需要特殊识别。
💭 [直观想象]
想象你在写一本关于所有非零数字的“族谱”。
- 你发现,所有数字,无论高矮胖瘦,追根溯源,都可以通过乘以或除以足够多次2,变成一个“始祖”的形式,这个“始祖”的大小在1到2之间(不含2)。
- 例如,12 -> 6 -> 3 -> 1.5。它的始祖是1.5。
- -0.25 -> -0.5 -> -1.0。它的始祖是1.0。
- 这个“始祖”就是规范化的尾数,它的形式永远是 1.XXX。
- 浮点表示法就是记录两件事:1. 这个数字的“始祖”长什么样(尾数的小数部分)。2. 它和“始祖”差了多少代(指数)。
1414. 浮点数的标准形式
📜 [原文24]
- 如何在 32-位字的限制内表示浮点数
- 字的位分成不同的字段
3130292827262524232221201918171615141312111009080706050403020100
符号 小数部分 (尾数)
- IEEE 754 标准规定
- 哪些位表示哪些字段(位 31 是符号位,位 30-23 是 8-位指数,位 22-00 是 23-位小数部分)
- 如何解释每个字段
📖 [逐步解释]
这一部分正式引入了工业界和学术界广泛采用的浮点数标准——IEEE 754。它规定了如何在一个32位的二进制字中,为符号、指数和尾数这三部分信息“划分地盘”。
- 问题: 我们有一个32位的存储空间(比如一个float类型的变量),需要把一个浮点数的所有信息(符号、指数、尾数)都塞进去。如何合理分配这32个比特?
- 解决方案:分字段 (Fields):
- 我们将32个比特分成三个独立的字段,每个字段负责存储一部分信息。
- 符号 (Sign): 只需要1位。0代表正,1代表负。
- 指数 (Exponent): 需要一些位来存储指数的大小。
- 小数部分 (Fraction) / 尾数 (Mantissa): 需要最多的位来存储有效数字,这直接决定了精度。
- IEEE 754 单精度 (32-bit) 标准:
- 这是一个国际标准,确保了在所有遵循该标准的计算机上,浮点数的表示和运算都是一致的。这对于软件的可移植性至关重要。
- 地盘划分规则:
- 位 31 (最高位): 1位,用作符号位 (S)。
- 位 30-23: 8位,用作指数位 (E)。
- 位 22-0: 23位,用作小数部分 (F)。
- 可视化:
- 解释规则:
- 标准不仅规定了位的划分,还规定了如何解释每个字段的值,以及如何将它们组合成最终的数值。这将在下一节详细展开。
💡 [数值示例]
- 示例: 编码十进制数 9.75
- 在上一节,我们知道 9.75 的规范化二进制形式是 +1.00111 × 2^3。
- 现在我们要把它编码成 IEEE 754 格式:
- 符号 (S): 正数,所以 S = 0。
- 指数 (E): 指数是 3。IEEE 754使用一种“偏移表示法”(下一节会讲),对于8位指数,偏移量是127。所以存储的指数值是 实际指数 + 偏移量 = 3 + 127 = 130。
- 130 (dec) = 10000010 (bin)。所以8位指数场 E = 10000010。
- 小数部分 (F): 规范化的尾数是 1.00111。我们省略掉隐含的1.,只存储小数部分 00111。这23位的小数部分字段需要填满,所以我们在后面补0。
- F = 00111000000000000000000。
- 组合: 将S, E, F拼接起来:
```
S E F
0 10000010 00111000000000000000000
```
- 这个32位的二进制模式就是在IEEE 754标准下 9.75 在内存中的实际样子。
⚠️ [易错点]
- 字段顺序: 必须记清楚 S-E-F 的顺序,以及每个字段的位数。最高位是符号,然后是指数,然后是小数。
- 尾数 vs 小数部分: 术语上容易混淆。尾数 (Mantissa/Significand) 通常指完整的 1.F 部分。而小数部分 (Fraction) F 是指存储在23位字段里的那部分。Significand = 1 + Fraction。
- 指数不是直接存储的: 这是一个巨大的易错点。8位指数场 E 存储的不是实际的指数值(如3),而是加上一个偏移量(127)之后的值。
📝 [总结]
本节介绍了浮点数的业界标准——IEEE 754(以32位单精度为例)。它将一个32位的字划分成三个字段:1位的符号、8位的指数和23位的小数部分。这个标准化的“地盘划分”方案是实现可移植、可交互的浮点计算的基础。
🎯 [存在目的]
目的是将浮点表示从一个抽象的概念,落实到一个具体的、标准化的硬件实现方案上。它回答了“在实践中,计算机是如何组织一个浮点数的32个比特的?”这个问题,为后续解码和编码浮点数打下了结构基础。
🧠 [直觉心智模型]
IEEE 754 就像一张标准化的“数字身份证”。
- 32位字: 身份证的卡片大小。
- 符号字段: “性别”栏(1位:男/女)。
- 指数字段: “出生年代”栏(8位:可以表示不同的年代,如80后、90后...)。
- 小数部分字段: “个人照片”栏(23位:提供了主要的个人身份信息)。
- 有了这张标准身份证,任何一个“读卡器”(CPU)都能正确地读取和识别这个人的信息。
💭 [直观想象]
你正在打包一个快递,快递单有三个标准化的格子。
- 第一个小格子 (S): 只能打一个勾,选项是“普通件”或“加急件”。
- 第二个中等格子 (E): 填写“距离等级”,比如“同城”、“省内”、“国内”、“国际”等,这个等级决定了邮费的基数。
- 第三个大格子 (F): 填写详细的物品描述。
- IEEE 754标准就是这张快递单的设计规范。它规定了每个格子的大小、位置和填写内容的格式,确保任何一个快递站都能看懂。
1515. IEEE 754 浮点描述
📜 [原文25]
3130292827262524232221201918171615141312111009080706050403020100
| $\frac{5}{5}$ |
指数 |
小数部分 (尾数) |
- 符号:0 = 正数 \#,1 = 负数 \#(像有符号幅度)
- 指数:无符号,偏移量为 127
- 指数值 = 8 位无符号二进制表示 -127
- 小数部分:回想小数部分总是 1.XXXXXX 形式
- 省略‘1’,只表示“XXXXXXX”部分
3130292827262524232221201918171615141312111009080706050403020100
1000011011010000000000000000000
- $=(-1) \times 1.101_{(2)} \times 2^{13-127}=-1.625_{(10)} \times 2^{-114}=-7.82409 \times 10^{-35}$
- 因为表示总是 $\pm 1.\mathrm{XXXX} \times 2^{\text{yyy}}$ 形式,不能表示真正的 0
- 注意:所有位都设为 0 等于 $1.0 \times 2^{-127}$,一个非常非常小的数字,实际上是 0
📖 [逐步解释]
这一部分详细解释了IEEE 754标准中每个字段的解码规则,并通过一个完整的例子演示了如何将一个32位的二进制模式转换成它所代表的十进制数。
- 解码规则:
- 符号 (S):
- 位于位31。
- 0 代表正数,1 代表负数。这和有符号幅度表示法是一样的。
- 最终值的计算公式里,可以用 (-1)^S 来表示符号。
- 指数 (E):
- 位于位30-23,共8位。
- 它本身被当作一个无符号整数来解读,范围是 0 到 255。
- 偏移表示法 (Biased Representation): 存储的指数 E 并不是真正的指数。实际的指数值 e 需要通过 e = E - bias 来计算。
- 对于单精度(32位),这个偏移量 (bias) 固定为 127。
- 所以,实际指数 = 存储的8位无符号值 - 127。
- 为什么用偏移表示法? (下一节会讲),主要是为了简化比较操作。
- 小数部分 (F):
- 位于位22-0,共23位。
- 它存储的是规范化尾数 1.XXX... 中,小数点后面的部分。
- 在计算时,我们需要把那个被省略的“隐含位 1”给补回来。
- 所以,完整的尾数 (Significand, M) 的值是 1 + F。这里的 F 是把23位小数部分当作二进制小数来计算的值。例如,如果 F = 1010...,那么它的值是 $1 \cdot 2^{-1} + 0 \cdot 2^{-2} + 1 \cdot 2^{-3} + ...$。
- 完整解码公式 (对于规范化数):
Value = (-1)^S × (1 + F) × 2^(E - 127)
- 示例解码:
- 二进制模式: 1 0000110 11010000000000000000000
- 分解:
- S = 1 (符号为负)。
- E = 00001101 (bin)。作为无符号数,它的值是 $8+4+1 = 13$ (dec)。
- F = 110100...0 (bin)。
- 计算:
- (-1)^S = (-1)^1 = -1。
- 实际指数 e = E - 127 = 13 - 127 = -114。
- 小数部分 F 的值是 $1\cdot2^{-1} + 1\cdot2^{-2} + 0\cdot2^{-3} + 1\cdot2^{-4} = 0.5 + 0.25 + 0.0625 = 0.8125`。
- 尾数 M = 1 + F = 1 + 0.8125 = 1.8125。
- 等等,原文的计算是 1.101。让我看看 F=1101...。 1.F 应该是 1.1101。1.1101 (bin) = $1+0.5+0.25+0.0625 = 1.8125$。原文中的1.101可能是笔误,或者它省略了F的某个1。让我们按照原文的 1.101 来算,这意味 F 应该是 1010...。1.101(bin) = $1+0.5+0.125=1.625$。这与原文的-1.625吻合。所以我们假设 F 字段实际上是 1010...,而 E 是 00001101 (13)。
- 按照原文的 1.101 和 E=13 重新计算:
- Value = -1 × 1.625 × 2^(13 - 127)
- Value = -1.625 × 2^-114。
- $2^{-114}$ 是一个极小的数,约等于 $4.85 \times 10^{-35}$。
- 所以最终值约等于 -1.625 × 4.85e-35 ≈ -7.88e-35。这与原文的 -7.82409e-35 基本一致(差异可能来自计算器精度或原文数字的微小笔误)。
- 关于0的讨论:
- “因为表示总是 $\pm 1.\mathrm{XXXX} \times 2^{\text{yyy}}$ 形式,不能表示真正的 0”。这个公式是针对规范化数的。在这种形式下,因为尾数 1+F 永远大于等于1,所以结果永远不可能是0。
- “注意:所有位都设为 0 等于 $1.0 \times 2^{-127}$,一个非常非常小的数字,实际上是 0”。
- 这句话描述了一个矛盾,并暗示了我们目前的知识不完整。
- 如果我们严格按照上面的规范化公式,当S=0, E=0, F=0时,Value = (-1)^0 × (1+0) × 2^(0-127) = 1.0 × 2^-127。这是一个非常接近0的数,但不是0。
- 这说明,IEEE 754 标准中有一些特殊规则。事实上,当指数字段 E 全为0时,它就进入了一种称为“非规范化数 (Denormalized Numbers)”的模式,此时隐含位不再是1而是0,并且指数的计算也不同。而当E和F都全为0时,它就明确地定义为0。这些特殊情况处理了0和下溢问题。原文在这里只是埋下伏笔。
⚠️ [易错点]
- 指数偏移: 最常见的错误是忘记指数的偏移量 127。直接把 E 当作指数来用是错误的。
- 隐含位: 第二大易错点是忘记在计算尾数值时补上 1.。
- 特殊值: IEEE 754 标准不仅定义了规范化数,还为 0, 无穷大(Infinity), 非数(NaN) 以及非常小的非规范化数定义了特殊的编码规则。这些通常是通过预留一些特殊的指数字段值(如全0或全1)来实现的。本节的讨论只涵盖了最常见的“规范化数”情况。
📝 [总结]
本节详细阐述了如何解码一个标准的32位IEEE 754浮点数。核心解码公式为 Value = (-1)^S × (1 + F) × 2^(E - 127)。通过一个具体例子,我们实践了从一个32位二进制模式中分离出S, E, F三个字段,并根据规则计算出其最终代表的十进制值的全过程。同时,本节也暗示了标准中存在处理0等特殊值的额外规则。
🎯 [存在目的]
提供解码IEEE 754浮点数的具体操作指南。这是将浮点理论付诸实践的关键一步。掌握了这个解码过程,就能从根本上理解float类型变量在内存中的本质,以及浮点数运算可能出现的各种现象(如精度损失、舍入误差等)。
🧠 [直觉心智模型]
解码IEEE 754浮点数就像解读一份加密情报。
- 32位码: 加密的情报原文。
- 解码手册 (IEEE 754):
- 第一条规则:看第1个字符,是'A'就是我方,是'B'就是敌方 (S 字段)。
- 第二条规则:看第2到第9个字符,把它转换成数字,再减去127,就知道行动的“日期” (E 字段)。
- 第三条规则:看剩下的字符,在它前面加个“1.”,就知道行动的“具体内容” (F 字段和隐含位)。
💭 [直观想象]
你有一个神奇的调味瓶。瓶子上有三个调节旋钮。
- S旋钮: 只有两档,“盐”或“糖”。
- E旋钮: 8位,刻度从0到255。它控制调味料的“浓度等级”,但刻度是偏移的,127档才是“标准浓度”,128档是“2倍浓度”,126档是“半倍浓度”。
- F旋钮: 23位,非常精密。它控制调味料的“基础风味”,比如“微辣”、“中辣”、“变态辣”。
- 一个32位的浮点数,就是这三个旋钮的一组特定设置。把它们组合起来,就能调制出成千上万种不同的味道(数值)。
1717. IEEE 754 64-位 (双精度)
📜 [原文29]
| 字 1 |
|
31/30292827262524232221201918171615141312111009080706050403020100 |
|
指数 |
小数部分 (高位) |
3130292827262524232221201918171615141312111009080706050403020100
字 2 小数部分 (低位)
- 11-位指数,偏移量为 1023
- 小数部分长 52 位(字 1 中 20 个高位,字 2 中剩余 32 个低位)
📖 [逐步解释]
这一部分介绍了IEEE 754标准的另一种常见格式:64位双精度(Double Precision),在C语言中对应double类型。它提供了比32位单精度更高的精度和更广的表示范围。
- 需求背景: 32位的单精度浮点数虽然范围比整数大得多,但其精度有限(大约7位十进制有效数字)。在许多科学计算、金融建模等领域,这点精度是远远不够的,计算过程中微小的舍入误差会累积起来,导致最终结果出现巨大偏差。因此,需要一种更高精度的浮点格式。
- 64位双精度:
- 它使用64个比特(8个字节)来表示一个浮点数。
- 在一些老的32位架构中,由于硬件一次只能处理32位,一个64位的double可能被存储在两个相邻的32位内存单元(“字”)中。现代64位架构则可以原生处理64位数据。
- 这64位的“地盘划分”与32位类似,但每个字段的位数都增加了:
- 符号 (S): 仍然是1位(位63)。
- 指数 (E): 扩展到11位(位62-52)。
- 小数部分 (F): 扩展到52位(位51-0)。
- 双精度的解码规则:
- 符号 (S): 规则不变,0为正,1为负。
- 指数 (E):
- 因为有11位,所以E的无符号取值范围是 0 到 2^11 - 1 = 2047。
- 偏移量 (bias) 也相应增大了,变为 1023。
- 所以,实际指数 e = E - 1023。
- 规范化数的实际指数范围是 1 - 1023 到 2046 - 1023,即 -1022 到 +1023。范围远超单精度的 -126 到 +127。
- 小数部分 (F):
- 有52位用于存储小数部分。
- 加上1位的隐含位,尾数的总精度达到了53位。
- $2^{53}$ 约等于 $9 \times 10^{15}$。这意味着双精度浮点数大约有15-17位十进制有效数字,远高于单精度的7位。
- 原文表格的解读:
- 原文的表格描述了一种在32位系统上如何“拼凑”出64位双精度数的情景。
- 字1: 存储了高位的32个比特。这32位里又被细分:
- 位31是符号S。
- 位30-20是11位指数E。(原文这里似乎有误,30-20只有11位,但图上标到了23,我们以11位为准)
- 位19-0是52位小数部分F的“高20位”。
- 字2: 存储了低位的32个比特,全部用于表示小数部分F的“低32位”。
- 20 (高位) + 32 (低位) = 52位小数部分。这个描述是自洽的。
- 这种分两个字存储的方式在现代64位系统中已不常见,但它有助于理解双精度数各个部分的构成。
💡 [数值示例]
- 示例: 编码十进制数 -192.0
- -192.0 = -1.5 × 128 = -1.5 × 2^7。
- 规范化二进制形式是 -1.1 × 2^7。
- 符号 (S): 负数, S = 1。
- 指数 (E): 实际指数 e=7。双精度的偏移量是1023。
- E = e + 1023 = 7 + 1023 = 1030。
- 1030 (dec) = 1024 + 6 = 10000000110 (bin)。这是11位的指数。
- 小数部分 (F): 尾数是 1.1。小数部分是 .1。
- F 需要填满52位,所以是 1000...0 (一个1后面跟51个0)。
- 组合: S=1, E=10000000110, F=100...0。
⚠️ [易错点]
- 单精度与双精度的混淆: 在编程和计算时,必须清楚地区分float(单精度)和double(双精度)。它们的位数、范围、精度和偏移量都完全不同。
- 偏移量: 双精度的偏移量是1023,而不是单精度的127。
- 特殊值: 双精度同样有为0, 无穷大, NaN等保留的特殊指数值(E全为0或全为1)。
📝 [总结]
本节介绍了IEEE 754标准的64位双精度浮点格式。它通过将位数扩展到64位(1位符号,11位指数,52位小数),实现了比32位单精度高得多的精度(约16位十进制有效数字)和更广的表示范围。其编码原理(符号、偏移指数、隐含位)与单精度一脉相承,只是各项参数有所不同。
🎯 [存在目的]
目的是介绍在实际应用中更常用、更精确的double类型所对应的底层标准。这让学习者明白,单精度和双精度并非两种完全不同的东西,而是同一设计思想在不同资源(位数)投入下的两种实现。
🧠 [直觉心智模型]
如果说单精度float是一张标准的“身份证”,那么双精度double就是一本“护照”。
- 卡片大小: 护照(64位)比身份证(32位)厚得多。
- 性别栏 (S): 都有,还是1位。
- 出生年代栏 (E): 护照上的这个栏位更长(11位),不仅能记录“90后”,还能记录到更精确的“92年8月生”,表示范围更广。
- 个人照片栏 (F): 护照上的照片更大、更清晰(52位),能看清脸上的每一颗痣,提供了更多的细节信息(精度更高)。
💭 [直观想象]
回到画地图的例子。
- 单精度: 是用一张A4纸画地图,比例尺选项比较有限,画出的细节也比较粗糙。
- 双精度: 是用一张巨大的A1画纸画地图,比例尺选项非常丰富,可以画得极为精细。
- 用A4纸(单精度)画整个世界地图,可能只能标出国家。而用A1画纸(双精度),不仅能标出国家,甚至能画出城市里的主要街道。
2020. 行间公式索引
- 二进制加法示例 (5 + (-3))
这个公式展示了在4位2的补码下,计算 5 + (-3) 的过程,结果正确地得到2。
$$
\begin{aligned}
& \text {-e.g., } \\
& \qquad \begin{array}{lr}
111 & \\
+0101 & 5 \\
+1101 & -3 \\
0010 & 2
\end{array}
\end{aligned}
$$
- 2的补码加法溢出示例 (7+7)
这个公式展示了在4位2的补码下,计算 7+7 如何导致溢出,最终得到错误结果-2。
$$
\begin{aligned}
& \begin{array}{l}
7+7=14 \\
0 \\
0111 \\
+{ }_{\underline{0111}}^{\underline{0111}} \\
\text { overflow }
\end{array}
\end{aligned}
$$
- 自定义表示法下的运算与溢出
这个公式在一个非标准的2位表示法下,演示了 1+1=2 (不溢出) 和 1+3=4 (结果无法表示,导致溢出) 的情况。
$$
\begin{aligned}
& 00+00=11 \text { (i.e., } 1+1=2 \text { ) } \\
& 00+01=? \text { ? } \\
& 1+3 \text { should equal } 4 \text {, but no 2-bit combo } \\
& \text { represents } 4
\end{aligned}
$$
- 十进制科学记数法示例
这个公式用科学记数法表示了数字-7776,作为引入浮点数思想的例子。
$$
-7.776 \times 10^{3}=-7776=-6^{5}
$$
- 二进制浮点数表示示例
这个公式展示了一个二进制浮点数的具体形式,并将其翻译成对应的十进制科学记数法。
$$
-1.10 \times 2^{0111}\left(=-1.5^{*} 2^{7}\right)
$$
[[END_OF_RESPONSE]]所有解释内容已全部输出完毕。