跳转至

计算机算术 — 固定位数下的加法、进位与溢出

这一章讲什么: 计算机怎么加、怎么减,以及——关键的区别——它做算术时位数是 固定死的,装不下的结果怎么办。你会见到全书的第一个硬件机制(进位标志 CF)、 全书最重要的编码(补码),和一个反直觉的结论:CPU 的加减法电路根本不关心 你加的是有符号数还是无符号数,区分是你(程序)的事。 读完这章,「整数溢出」 对你就不再是玄学,而是环上多走一格,外加两个各管各的标志位。

1. 顶层全景:一条从「纸笔算术」到「两个标志位」的链

  纸笔算术:结果写不下就加一位(位数可以任意多)
     │  计算机:位数在写程序时就定死了(数据类型决定)
     ▼
  无符号数:装不下 = 进位/借位 → CPU 用进位标志 CF 记录
     │  那负数怎么办?
     ▼
  原码(最高位当符号位):简单,但加法直接算错(+2 + −2 ≠ 0)
     │  必须找一种「普通加法就能算对」的负数表示
     ▼
  补码:负数 = 2ⁿ − x,一套加法电路通吃有符号和无符号
     │  有符号数也有自己的「装不下」
     ▼
  溢出标志 OF:专门记录有符号数的装不下
     │
     ▼
  结论:CF 和 OF 每次都同时被硬件设置;用哪个,由程序怎么解释结果决定

  图说:每一环都是被上一环的缺陷逼出来的;CF/OF 的分工是本章的落点。

本章开头就把要面对的「现实」点明:进行计算时,我们要面对的现实是位数有限—— 每个数据项占多少位,由它的数据类型在写程序时就决定好了;这个限制「甚至会使最基本的 数学运算复杂化」1。

2. 先复习纸笔算术:进位与借位是两个方向上的「溢出」

为了把「固定宽度」这件事看清楚,原书先把我们多年不用的纸笔十进制演算翻出来, 挑的例子是两位数:x = 67,y = 79。

加法从右往左,每一位做两件事:把两个数码(加上右边来的进位)相加;把「比 10 多的部分」 进给左边一位:

    1         ← 进位
      6   7   ← x
  +   7   9   ← y
  ─────────
      4   6   ← 和的低位部分

个位:7 + 9 = 16,写 6 进 1。十位:1(进位)+ 6 + 7 = 14,写 4 进 1。 最终进位 1 没地方写了——因为我们只分配了两位。 原书把这套过程写成算法, 核心就两行:sumi = (xi + yi + carryi) % 10(取余数当本位), carryi+1 = (xi + yi + carryi) / 10(整数除法得当进位)2。 它有效的全部根据是:位置记数法里,进位从左往右传递时永远是 0 或 1。

减法是镜像:某位不够减就向左边借。67 − 79 要借两次:先从十位借(6 变成 5, 个位 7 变 17),还不够,再从「两位数之外」借(标记一个 1,十位变 15)。 原书把「从外部借位」写成了完整算法,并且点破它的用途: 算法结束时的 borrow = 1,说明结果在 N 位里放不下3。

作者在这里插了一句大实话:「该算法并不像看起来那么复杂(但是我花了很长时间才弄明白!)」4 以及:「如果你理解上有困难,也不要担心。并不是非得弄明白这个算法才能阅读本书。」 但补了一句值得记的理由:把日常操作翻译成编程语言的逻辑语句,本身就是一项困难任务—— 这个练习是在练「算法化」——原书接着点明动手实践第 1 题的伏笔:「存储单个十进制数需要 多少位?发明一种在 32 位中存储 8 位十进制数的编码」——这道题指向的正是 BCD 一类的 十进制编码,以及一个会伴随全书的问题:用二进制加法直接加这种编码,结果还对吗?27

这两个十进制例子是本章主线例子的第 ①、② 步:它们建立两个角色—— 进位(加法向高位溢出)和借位(减法向高位伸手)——接下来计算机只是把底数从 10 换成 2。

3. 无符号二进制加减:同样的动作,更短的跑道

3.1 加法:4 位跑道上的两幕

把底数换成 2,进位规则不变,只是「满 2 进 1」5。先看装得下的:

  进位   0 0 0
         0 0 1 0   = 2₁₀
       + 0 1 0 0   = 4₁₀
       ─────────
         0 1 1 0   = 6₁₀     CF = 0,结果正确

再看装不下的——这就是主线例子的第 ③ 步,4 + 14,还是 4 位:

  进位   1 1 0 0
         0 1 0 0   = 4₁₀
       + 1 1 1 0   = 14₁₀
       ─────────
         0 0 1 0   = 2₁₀  ≠ 18₁₀    CF = 1,结果错误!

  图说:18 需要 5 位(10010₂),4 位跑道只留下了低 4 位 0010₂。
  丢掉的那一位「1」被 CF 接住,没有丢——进位标志里存着它。

原书的关键解读:「如果我们将进位标志纳入结果,就得到了一个 5 位值,即 10010₂ = 18₁₀, 这个结果是正确的。在编写软件时必须要考虑进位标志。」6

3.2 减法:借位也是 1 位标志

4 − 14(0100 − 1110):

  借位   1 1 0
         0 1 0 0   = 4₁₀
       − 1 1 1 0   = 14₁₀
       ─────────
         0 1 1 0   = 6₁₀ ≠ −10₁₀

  图说:无符号数的世界里没有「负数」,不够减只能向 4 位之外借;
  CF = 1 记录「借了」,结果 0110 无效。

CPU 把减法的借位也记在 CF 里——进位标志实际上是「结果是否装得下」的统一记账员: 加法溢出记 1,减法没借位记 0;「尽管名为进位标志,但它的目的是表明运算结果何时 不适合数据类型的位数」7。

3.3 CF 的机械脾气

两条规则,没有例外8:

  • 每次加减运算,CPU 总是把 CF 设置为 0 或 1——不会「保持上一次的值」;
  • 没有进位/借位时,CPU 会主动把 CF 清为 0,不管它原来存什么。

「只要结果符合位数,就是正确的;结果有误,都会通过将进位标志设置为 1 来记录此错误」8。 这两句话把 CF 的语义钉死了。记住:CF 是每次运算都被无条件覆盖的标志, 想在两次运算之后回头查它,中间的任何加减都会把它冲掉(第 13 章的条件跳转会用到这个脾气)。

4. 有符号数:原码的失败与补码的诞生

无位数扩展的减法已经暴露一个问题:4 − 14 在人看来是 −10,机器却只给出 6 和一个借位。 要表示负数,最直觉的编码是原码(sign-magnitude):最高位当符号位 (0 正 1 负),其余位照旧9。问题立刻暴露——主线例子的第 ④ 步:

    0 0 1 0   = +2₁₀
  + 1 0 1 0   = −2₁₀(原码)
  ─────────
    1 1 0 0   = −4₁₀(原码解释)≠ 0

  图说:符号位参与的普通加法,算出来是错的。

「当使用原码时,我们先前用于无符号数的简单加法并不适用于有符号数。」9 补救方案是给有符号数配一套专门的加法指令——某些架构确实这么干,顺便得到 一个彩蛋:「这种计算机既有 +0 又有 −0!」10 但大多数架构选了另一条路: 换一套让普通加法天然算对的编码。这就是补码。

5. 补码:磁带计数器教我们的负数

5.1 补数的直觉:环形世界里的「后退」

数学里,一个量的补数是让它「凑成整体」所需加上的量。「整体」多大,取决于基数和位数: 两位十进制数的世界里,整体是 100;37 的基数补数是 63,因为 37 + 63 = 100。 两个说法等价:x 的补数 ¬x 满足 x + ¬x = 基数ⁿ(n 位);或者说,相加得 0、 并在 n 位之外送出一个进位 111。

原书用了一台盒式磁带播放机把这个概念讲活,这是本章最好的比方——请把它当 一次性脚手架,用完即拆:

老式磁带机有个 4 位计数器,中途按复位归零后,往前进一格显示 0001(+1); 往后退一格显示 9999(−1)。计数器上没有负号,但「−1」有明确的表示: 它就是 9999,因为 9999 + 1 = 10000,千位上的那个 1 溢出丢掉了12。

用这台机器算 (+2) + (−2):

  做法一:从 0 前进 2 格(0002),再后退 2 格 → 0000 ✓
  做法二:从 0 后退 2 格(9998,即 −2 的编码),再「加 +2」= 前进 2 格:
         9998 + 2 = 10000 → 显示 0000,并冒出一个进位 1

  图说:9998 就是 0002 的十进制补数。有符号加法里,那个进位「不重要」——
  忽略它,答案就是对的。

把底数换成 2、跑道换成 4 位,就得到二进制补码——「它使用与磁带计数器相同的方法」13:

5.2 4 位补码全表:一半给正数,一半给负数

十六进制 二进制 无符号解读 有符号(补码)解读
0 0000 0 0
1 0001 1 +1
… … … …
7 0111 7 +7
8 1000 8 −8
9 1001 9 −7
a 1010 10 −6
… … … …
f 1111 15 −1

四条观察,每条都要消化14:

  1. 每个正数的高位都是 0,每个负数的高位都是 1——符号位之称由此而来 (虽然「改变符号远比翻转这一位复杂」,名字只是绰号);
  2. 负数比正数多一个(4 位里 −8 有位置,+8 没有)——因为 0 占了正数那半边的一个名额;
  3. n 位的有符号范围是 −2ⁿ⁻¹ ~ +2ⁿ⁻¹ − 1(4 位:−8 ~ +7;8 位:−128 ~ +127; 32 位:−2 147 483 648 ~ +2 147 483 647);
  4. 在补码定义下,任何数 x 的负数 −x 满足 x + (−x) = 2ⁿ——n 个 0 加一个 「溢出围墙」的进位 115。

第 2 条值得停一秒:补码不是对称的,负的那半边比正的多一个数。这个「不对称」 正是第 ① 章「最大值永远是 2ⁿ − 1」的镜像——0 的位置在补码里被算进了非负半边, 于是 −2ⁿ⁻¹ 落了单。

5.3 手算一个负数的补码:反码 + 1

定义给了 −x = 2ⁿ − x,直接算要借位,容易错。原书做了代数变形:

  −x = 2ⁿ − x = ((2ⁿ − 1) − x) + 1
                  └──┬──┘
           n 个 1 减 x:逐位翻转!这叫反码(one's complement)

而 (2ⁿ − 1) 写成二进制是 n 个 1;n 个 1 减去任何 n 位数,效果就是逐位翻转 (1 变 0、0 变 1)——这就是反码16。所以手算口诀出来了:

负数补码 = 把该数逐位取反(反码),再加 1。

走一遍主线例子的第 ⑤ 步:8 位下求 −123。

  x = 123:        0111 1011 (0x7b)
  逐位取反:       1000 0100 (0x84)   ← 反码,ff − 7b = 84,一位都不用借
  加 1:           1000 0101 (0x85)   ← −123 的补码

  验证:x + (−x) = 0x7b + 0x85 = 0x100 → 低 8 位 0x00,进位 1 ✓

原书的小提示:检查时注意被转换的值是奇是偶——「它在所有的数字基数中都是相同的」17。

反码这个名字的来历顺手记下:它是「减基数的补数」(基数ⁿ⁻¹ 那个整体)运算的结果; 「减基数的补数 + 1」才是「基数的补数」,即补码18。两个词经常混着说, 区分开:反码是中间产物(取反),补码是最终编码(取反加一)。

5.4 这套编码买到了什么

补码买到的是硬件的极大简化,原书用「注意」框明说:「使用补码表示法意味着 CPU 不需要额外的指令来执行有符号数的加法和减法,从而简化了硬件。」 CPU 眼里只有位模式;加减法电路对补码自动给出正确结果,因为 −x 的定义 (x + (−x) = 2ⁿ) 就是让「普通加法 + 自然丢弃进位」等于「模 2ⁿ 意义下的零」19。

这正是「编码」暗线的又一次显形:补码不是一个「表示负数的技巧」,而是把 「减法变加法」编入位模式本身——和第 2 章 BCD 一样,又是一次用编码换电路的设计。

6. 溢出标志 OF:有符号数自己的「装不下」

6.1 为什么 CF 不够了

补码加法照样会超范围:两个正数相加可能越过 +7(4 位)掉进负数区,两个负数相加 可能越过 −8 掉进正数区。这时 CF 说了不算——还记得磁带吗?有符号加法里进位 「并不重要」,本来就该被忽略12。所以 CPU 需要另一个标志,只对有符号解读负责: 溢出标志 OF(Overflow Flag)20。

6.2 OF 的机械定义:两个进位的异或

OF 的取法,初看「并不直观」:次末级进位(符号位那一位产生的进位)和最终进位 (最高位向外的进位)做异或(XOR)21。异或的意思是:两位不同得 1,相同得 0 (第 4 章会正式讲这个运算符,这里先用结论)。

主线例子的第 ⑥ 步,8 位下把 0x15 和 0x6f 相加:

  最终进位 → 0 | 1        ← 次末级进位
              0001 0101   ← x = 21
            + 0110 1111   ← y = 111
            ─────────
              1000 0100   ← 和 = 0x84

  次末级进位 = 1,最终进位 = 0
  OF = 1 XOR 0 = 1   → 溢出了

  图说:两个正数(0x15=+21, 0x6f=+111)的和是 +132,超出 8 位补码上限 +127;
  结果的符号位是 1(0x84 解读为 −124)——正数加出负数,OF 抓住了它。

6.3 为什么是异或:三种情况的穷举

原书把「异或为什么恰好对」拆成三种情况逐一验证,这是本章最硬核的一段推理。 核心图式只有一行:和的符号位 = 最终进位;而「本该的符号」由两个加数的符号位 之和(加次末级进位)决定;两者不一致,就是溢出。

相加两数 次末级进位 最终进位 OF 结果 判定
异号(正+负) 0 0 0 符号 1 总不溢出 ✓
异号 1 1 0 符号 0 总不溢出 ✓
正 + 正 0 0 0 符号 0 范围内 ✓
正 + 正 1 0 1 符号 1 正+正=负,溢出
负 + 负 0 1 1 符号 0 负+负=正,溢出
负 + 负 1 1 0 符号 1 范围内 ✓

异号相加永不溢出,原因从范围式一眼可见:一正一负的和被夹在两个加数之间, 绝对值只会变小22。只有同号相加才有风险,而同号时「进位翻进/翻不出符号位」 恰好就是越界的信号——异或一步就抓住了这个信号。

6.4 CF 与 OF 的分工表:本章的结论

把两条标志摆在一起,本章的全部内容就收成四行23:

程序把结果解读为 正确的条件 要查的标志 另一个标志
无符号整数 结果在 n 位范围内 CF = 0 OF 无关
有符号整数(补码) 结果在 n 位范围内 OF = 0 CF 无关

而硬件的态度是:两个标志每次运算都同时更新;「有符号数和无符号数之间的区别 完全是由程序决定的」24。

判断(我们的,不是书里的): 这是全书第一次出现「位模式无语义,语义在解释端」 的硬件级证据,而且它来得比很多人想象的要硬:不是哲学观点,是「同一套电路、 同一个结果、两个标志各自记账」的物理安排。很多高级语言(书里的原话:「许多高级语言 并不进行这种检查」)默认不查这两个标志,溢出就静默发生——今天 C/C++ 的有符号溢出 是未定义行为、Rust 选择直接 panic,都是这张分工表的不同政策(补充,不在书里, 来自通用知识)。 如果错,会错在: 个别架构可能用不同的溢出检测机制(如 MIPS 不产生标志、 靠比较指令);判据是查该架构的 flags/条件码设计。x86-64 就是书里的这套。

7. 译码环:把整章装进一张图

本章最后给了一个叫译码环(decode ring)的直观工具:固定宽度的整数编码 本质上是环形的(circular)——加过了顶会回绕(wrap around)到另一端25。 3 位数的环长这样(我们重画为两行,原书是圆形):

  无符号解读:   0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 →(回到 0)
  有符号解读:   0 → 1 → 2 → 3 → −4 → −3 → −2 → −1 →(回到 0)
                       同一个环,两种读法!

用环做加减只需三步:选对环(有符号/无符号)→ 站到第一个数的位置 → 沿环走第二个数那么多格(加顺时针、减逆时针)26。 没有越过无符号环的顶部(7→0)、也没有越过有符号环的底部(3→−4),结果就对。

译码环同时解释了回绕现象的「合法性」:4 位无符号 1111 + 1 = 0000(CF=1), 和 4 位有符号 0111 + 1 = 1000(−8,OF=1)——都是环上多走一格,只是解读不同。 「回绕」这个词值得记住:它就是你平时说的整数溢出,在硬件层不过是一次正常的环行。

8. 作者的判断与证据

判断一:先十进制、后二进制。 加法借位算法先用十进制铺平,再换底数——这是 「在已知概念的基础上学习会更容易」这条作者立场(见前言)的标准执行。

判断二:补码的合法性不证明,只演示。 磁带计数器的例子给直觉,异或定义的 有效性靠三种情况穷举「说明」(原书的用词是「我们将逐一说明」)而非严格归纳证明。 对一本面向程序员的教材,这个力度刚好;但要知道补码的严格性质(它是模 2ⁿ 的 同余类表示)属于抽象代数,书里没走那条路。

判断三:诚实标注「不知道可以跳过」。 减法算法「不是我非得弄明白才能读本书」, 第 6 节的减法「同样的规则也适用,我诚邀你去自行探索!」——作者对「推导的完备性」 的态度很松弛,对「机制的理解」的态度很严格。这个配比是这本书的节奏, 也是它读起来不累的原因之一:该硬的地方(补码、异或)一步不退,该放的地方 (借位算法的细节)明说可以放。

9. 边界与局限

  • 只讲加减。 乘除留给第 16 章(那里会看到乘法指令对标志的不同用法);
  • 不展开 CPU 怎么「设置」标志的内部电路(加法器的进位链),只到「定义 + 行为」层;
  • 「许多高级语言并不进行这种检查」是一句重话,但本书不演示任何语言的溢出后果; 各语言今天的具体政策(未定义行为/panic/静默回绕)要查各语言文档(补充,不在书里);
  • 译码环只画到 3 位,32 位/64 位的环只是格子更多,性质不变。
  • 本章的演算全部可以用 gdb 复验:把两个数写进 C 变量,相加后用 i r 查看 rflags 寄存器(第 9 章教你认这个寄存器),CF 和 OF 就在里面各占一位—— 原书第 9、13 章会回到这两个标志,把它们接到条件跳转上。

10. 可带走的

全章主线例子,一行走完: 4 位跑道,4 + 14 = 18 装不下(CF=1 接住溢出位, 留下的 0010 是错的);负数进场后(补码),0x15 + 0x6f = 0x84 对有符号解读 是 +21 + +111 = −124,错得离谱,OF=1 抓住它;两个标志每次都被硬件设置, 用哪个,取决于你把结果当无符号数还是有符号数。 硬件只管算和记账,解释权在程序—— 这是「位模式无语义」第一次以电路的形式出现。

  1. 固定宽度是计算机算术和纸笔算术的本质区别;装不下 = 进位(加)或借位(减);
  2. CF(进位标志)是「无符号装不下」的记账员:每次加减都被无条件覆盖, 加法溢出置 1、减法借位置 1;
  3. 原码(符号位方案)让普通加法算错(+2 + −2 = −4),出局;
  4. 补码的定义:x 的负数 = 2ⁿ − x;效果:x + (−x) 得 n 个 0 加 1 个进位;
  5. 手算负数:取反加一(反码 + 1);验证口诀:奇偶性检查;
  6. n 位补码范围 −2ⁿ⁻¹ ~ 2ⁿ⁻¹ − 1:负数比正数多一个,0 占非负半边;
  7. 补码买到的东西:一套加法电路通吃有符号和无符号,硬件被简化;
  8. OF(溢出标志)= 次末级进位 XOR 最终进位;只对「有符号解读」有效;
  9. 异号相加永不溢出(和夹在中间);同号相加才可能,正+正得负或负+负得正即是;
  10. 译码环:整数编码是环,加减是沿环走;越过无符号顶部或有符号底部,结果错;
  11. 「有符号还是无符号」是程序的解释,不是硬件的属性——而多数高级语言 不替你查这两个标志;溢出一旦发生,后果由你的程序自行承担。

11. 原文地图

主题 原书章 原文位置
位数有限是现实 第3 章 计算机算术 text/07-ch03.txt:8(搜「位数有限」)
十进制加法算法(进位) 第3 章 计算机算术 text/07-ch03.txt:41(搜「进位」) · text/07-ch03.txt:50(搜「% 10」)
十进制减法与借位算法 第3 章 计算机算术 text/07-ch03.txt:83(搜「算法结束时 borrow」)
「花了很长时间才弄明白」 第3 章 计算机算术 text/07-ch03.txt:106(搜「花了很长时间」)
表 3-1 十六进制↔二进制↔十进制 第3 章 计算机算术 text/07-ch03.txt:132(搜「位模式和无符号十进制值」)
4 位加法:2+4、4+14 第3 章 计算机算术 text/07-ch03.txt:158(搜「进位」) · text/07-ch03.txt:172(搜「11102」)
减法 4−14 与 CF 第3 章 计算机算术 text/07-ch03.txt:179(搜「借位」)
CF 每次都被设置、主动清零 第3 章 计算机算术 text/07-ch03.txt:186(搜「总是」) · text/07-ch03.txt:187(搜「主动将 CF 设置为 0」)
原码及 +2 + −2 反例 第3 章 计算机算术 text/07-ch03.txt:195(搜「原码」) · text/07-ch03.txt:201(搜「算术上是错误的」)
+0 与 −0 的彩蛋 第3 章 计算机算术 text/07-ch03.txt:204(搜「+0 又有−0」)
补数定义(37 的补数是 63) 第3 章 计算机算术 text/07-ch03.txt:214(搜「基数 n」)
磁带计数器例子 第3 章 计算机算术 text/07-ch03.txt:221(搜「磁带」) · text/07-ch03.txt:227(搜「9999」)
4 位补码表与四条观察 第3 章 计算机算术 text/07-ch03.txt:245(搜「4 位补码表示法」) · text/07-ch03.txt:269(搜「重要观察」)
n 位范围公式、−x = 2ⁿ − x 第3 章 计算机算术 text/07-ch03.txt:278(搜「−2( n −1)」) · text/07-ch03.txt:280(搜「2n」)
反码 = 逐位翻转、取反加一 第3 章 计算机算术 text/07-ch03.txt:318(搜「翻转所有位」) · text/07-ch03.txt:321(搜「加 1」)
−123 = 0x85 演算 第3 章 计算机算术 text/07-ch03.txt:293(搜「10000101」)
OF = 次末级进位 XOR 最终进位 第3 章 计算机算术 text/07-ch03.txt:351(搜「次末级进位」) · text/07-ch03.txt:353(搜「1516」)
异号相加不溢出的证明 第3 章 计算机算术 text/07-ch03.txt:364(搜「两个符号相反」) · text/07-ch03.txt:391(搜「总是为 0」)
CF/OF 分工、区别由程序决定 第3 章 计算机算术 text/07-ch03.txt:441(搜「进位标志 CF 为 0」) · text/07-ch03.txt:448(搜「由程序决定」)
译码环与回绕 第3 章 计算机算术 text/07-ch03.txt:454(搜「环」) · text/07-ch03.txt:458(搜「译码环」)
高级语言不检查标志的警告 第3 章 计算机算术 text/07-ch03.txt:450(搜「令人费解的程序错误」)

  1. 出处:「第3 章 计算机算术」第 8 段(text/07-ch03.txt:8,搜「位数有限」)。原文:「在进行计算时,我们要面对的现实是位数有限」「这一限制甚至会使最基本的数学运算复杂化」。 ↩

  2. 出处:「第3 章 计算机算术」第 50 段(text/07-ch03.txt:50,搜「% 10」)。原文算法:sumi = (xi + yi + carryi) % 10、carryi+1 = (xi + yi + carryi) / 10。 ↩

  3. 出处:「第3 章 计算机算术」第 83 段(text/07-ch03.txt:83,搜「算法结束时 borrow」)。原文:「如果在这个算法结束时 borrow 为 1,则表明你不得不从两个值的 N 位数之外借位,所以 N 位数的结果是不正确的。」 ↩

  4. 出处:「第3 章 计算机算术」第 106 段(text/07-ch03.txt:106,搜「花了很长时间」)与第 114 段(text/07-ch03.txt:114,搜「不要担心」)。原文:「该算法并不像看起来那么复杂(但是我花了很长时间才弄明白!)」「并不是非得弄明白这个算法才能阅读本书。」 ↩

  5. 出处:「第3 章 计算机算术」第 165 段(text/07-ch03.txt:165,搜「相同位」)。原文:「和十进制加法一样,我们对相同位进行相加」;底数从 10 换为 2 是本章 3.1.3 节的主线。 ↩

  6. 出处:「第3 章 计算机算术」第 175 段(text/07-ch03.txt:175,搜「100102」)。原文:「如果我们将进位标志纳入结果,就得到了一个 5 位值,即 100102 = 1810,这个结果是正确的。在编写软件时必须要考虑进位标志。」 ↩

  7. 出处:「第3 章 计算机算术」第 84 段(text/07-ch03.txt:84,搜「不适合数据类型的位数」)。原文:「尽管名为进位标志,但它的目的是表明运算结果何时不适合数据类型的位数。因此,进位标志包含的就是减法运算完成时的借位值。」 ↩

  8. 出处:「第3 章 计算机算术」第 186 段(text/07-ch03.txt:186,搜「总是」)与第 190 段(text/07-ch03.txt:190,搜「记录此错误」)。原文:「每次执行加法或减法运算时,CPU 总是将 rflags 寄存器中的 CF 标志相应地设置为 0 或 1」「只要结果符合用于计算的数据类型的位数,就是正确的。如果结果有误……都会通过将进位标志设置为 1 来记录此错误。」 ↩↩

  9. 出处:「第3 章 计算机算术」第 195 段(text/07-ch03.txt:195,搜「原码」)与第 201 段(text/07-ch03.txt:201,搜「算术上是错误的」)。原文:「我们可以简单地使用最高位(假设 0 代表+,1 代表−)来表示有符号数,这种方法叫作原码(sign-magnitude code)」;「按照我们的表示方法,结果 11002 等于−410,这在算术上是错误的。」 ↩↩

  10. 出处:「第3 章 计算机算术」第 204 段(text/07-ch03.txt:204,搜「+0 又有−0」)。原文题外话:「(题外话:这种计算机既有+0 又有−0!)」 ↩

  11. 出处:「第3 章 计算机算术」第 214 段(text/07-ch03.txt:214,搜「基数 n」)。原文:「如果 x 是基数为 r 的 n 位数字,则其基数的补数(radix complement)¬x 被定义为 x + ¬x = 基数 n」;例:「37 的基数补码是 63,因为 37 + 63 = 102 = 100」。 ↩

  12. 出处:「第3 章 计算机算术」第 227 段(text/07-ch03.txt:227,搜「9999」)与第 237 段(text/07-ch03.txt:237,搜「进位并不重要」)。原文:「如果我们从 0 开始后退到−1,磁带计数器上的“代码”将显示 9999」;「当使用基数的补数表示法将两个有符号整数相加时,进位并不重要。」 ↩↩

  13. 出处:「第3 章 计算机算术」第 240 段(text/07-ch03.txt:240,搜「基数为 2」)与第 241 段(text/07-ch03.txt:241,搜「磁带计数器相同的方法」)。原文:「计算机使用的是二进制系统,基数为 2。所以让我们看看有符号整数的补码表示法。它使用与磁带计数器相同的方法,以位模式表示有符号的十进制整数。」 ↩

  14. 出处:「第3 章 计算机算术」第 269 段(text/07-ch03.txt:269,搜「重要观察」)。原文:「以下是关于此表的一些重要观察。每个正数的高位都是 0,每个负数的高位都是 1……这种方法能够表示的负数比正数多一个。」 ↩

  15. 出处:「第3 章 计算机算术」第 280 段(text/07-ch03.txt:280,搜「2n」)。原文:「使用补码表示法时,任何 n 位整数 x 的负数定义为:x + (− x) = 2n」;「将一个数与其负数相加会产生 n 个 0 和一个等于 1 的进位。」 ↩

  16. 出处:「第3 章 计算机算术」第 305 段(text/07-ch03.txt:305,搜「11111111」)与第 318 段(text/07-ch03.txt:318,搜「翻转所有位」)。原文:「因为这里所有的值都包含了 n 位,所以计算起来很容易——简单地翻转所有位,就可以得到减基数的补数,在二进制数字系统中也称为反码(one's complement)。」 ↩

  17. 出处:「第3 章 计算机算术」第 326 段(text/07-ch03.txt:326,搜「偶数还是奇数」)。原文:「仔细检查演算过程,注意要转换的值是偶数还是奇数。它在所有的数字基数中都是相同的。」 ↩

  18. 出处:「第3 章 计算机算术」第 217 段(text/07-ch03.txt:217,搜「减基数的补数」)。原文:「另一个有用的概念是减基数的补数(diminished radix complement),其定义为 x + 减基数的补数 = 基数 n–1。例如,37 的减基数的补数是 62。」 ↩

  19. 出处:「第3 章 计算机算术」第 446 段(text/07-ch03.txt:446,搜「不需要额外的指令」)。原文:「使用补码表示法意味着 CPU 不需要额外的指令来执行有符号数的加法和减法,从而简化了硬件。」 ↩

  20. 出处:「第3 章 计算机算术」第 350 段(text/07-ch03.txt:350,搜「溢出标志」)。原文:「当有符号整数之和过大的时候,CPU 通过使用标志寄存器 rflags 中的溢出标志(Overflow Flag,OF)进行记录。」 ↩

  21. 出处:「第3 章 计算机算术」第 351 段(text/07-ch03.txt:351,搜「次末级进位」)与第 353 段(text/07-ch03.txt:353,搜「1516」)。原文:「溢出标志的值是由一个乍一看并不直观的操作设置的:次末级进位(penultimate carry)和最终进位(ultimate carry)的异或(XOR)操作。」例:OF = 0 ∨ 1 = 1(∨ 下划线即 XOR)。 ↩

  22. 出处:「第3 章 计算机算术」第 369 段(text/07-ch03.txt:369,搜「始终保持在补码表示范围内」)与第 391 段(text/07-ch03.txt:391,搜「总是为 0」)。原文:「x + y 始终保持在补码表示范围内」;「将两个符号相反的整数相加,溢出标志总是为 0。」 ↩

  23. 出处:「第3 章 计算机算术」第 441 段(text/07-ch03.txt:441,搜「进位标志 CF 为 0」)。原文:「如果程序将结果视为无符号数,当且仅当结果在 n 位范围内时,进位标志 CF 为 0;OF 无关紧要。如果程序将结果视为有符号数,当且仅当结果在 n 位范围内时,溢出标志 OF 为 0;CF 无关紧要。」 ↩

  24. 出处:「第3 章 计算机算术」第 448 段(text/07-ch03.txt:448,搜「由程序决定」)。原文:「CPU 看到的只是位模式……有符号数和无符号数之间的区别完全是由程序决定的。」 ↩

  25. 出处:「第3 章 计算机算术」第 454 段(text/07-ch03.txt:454,搜「环」)。原文:「无符号整数和有符号整数的表示法本质上是环(circular)——也就是说,对于给定数量的位,每种编码都会“回绕”(wrap around)。」 ↩

  26. 出处:「第3 章 计算机算术」第 458 段(text/07-ch03.txt:458,搜「译码环」)。原文给出三步:「挑选与所用整数类型(有符号或无符号)对应的环」「移动到该环上对应于第一个整数的位置」「沿着环移动,移动的“辐条”数量等于第二个整数。顺时针移动为加,逆时针移动为减。」 ↩

  27. 出处:「第3 章 计算机算术」第 119 段(text/07-ch03.txt:119,搜「存储单个十进制数需要多少位」)。原书动手实践第 1 题:「存储单个十进制数需要多少位?发明一种在 32 位中存储 8 位十进制数的编码。使用该编码,二进制加法产生正确的结果吗?你将在本书随后部分看到这样的编码及其功用性的一些证明。」 ↩