try ai
科普
编辑
分享
反馈
  • 二进制减法

二进制减法

SciencePedia玻尔百科
核心要点
  • 二进制减法可以通过向相邻位借位来直接执行,这个过程可以在硬件中使用称为全减器的逻辑电路来实现。
  • 补码系统允许计算机使用加法来执行减法,从而能够创建一个单一、高效的加减法器电路。
  • 对两个几乎相等的浮点数进行减法运算可能导致“灾难性抵消”,这是一种严重的精度损失,处理器架构师通过使用保护位来缓解此问题。
  • 二进制减法的原理超越了基本计算,构成了数据转换(如ASCII到数字)的基础,并影响着如GPS等复杂系统的准确性。

引言

减法是我们最早学习的数学运算之一,然而它在数字计算机内部的执行过程却是一个充满逻辑优雅的奇迹。一个只理解‘开’和‘关’状态的机器,是如何处理“减去”这个看似复杂的概念的?本文将揭开二进制减法的神秘面纱,连接我们直观理解与硅芯片内部高速运算之间的鸿沟。它探讨了在数字逻辑的限制下实现数学运算这一根本问题,揭示了一个看似棘手的问题是如何通过深刻的独创性得以解决的。在接下来的章节中,您将探索使数字减法成为可能的核心概念,从简单的借位规则到驱动现代处理器的通用电路。

第一章“原理与机制”深入探讨了基础方法。它审视了二进制中的借位逻辑,介绍了半减器和全减器等基本电路,并揭示了将减法转化为加法的巧妙技巧——补码。第二章“应用与跨学科联系”则探索了这些原理的应用,从统一的加减法器电路设计及其在各种数字编码中的使用,到减法在GPS等领域所扮演的关键角色,在这些领域中,减法的局限性可能会带来现实世界的影响。

原理与机制

从本质上讲,减法是我们几乎一学会数数就接触到的运算。如果你有五个苹果,送走两个,就剩下三个。很简单。但是,一台只理解“开”和“关”,或者说“1”和“0”的计算机,是如何处理这个问题的呢?从我们对减法的直观理解,到硅芯片内部发生的闪电般快速的计算,这段历程充满了惊人的优雅和深刻的独创性。这个故事揭示了一个看似棘手的问题是如何被转化为一件美妙艺术品的。

借位的重新构想

让我们回到基础,回到我们在学校学习的减法,但这次我们用二进制来做。规则甚至比十进制更简单,因为我们只有两个数字!

  • 0−0=00 - 0 = 00−0=0
  • 1−1=01 - 1 = 01−1=0
  • 1−0=11 - 0 = 11−0=1

唯一棘手的部分出现在我们需要计算 0−10 - 10−1 时。就像十进制数学中较小的数字在上面时一样,我们需要从左边的列​​借位​​。但我们借的是什么呢?在十进制中,从十位借位得到的是10。在二进制中,左边的列代表下一个2的幂。所以,当我们从它借位时,我们借来的是一个‘2’。

我们来看一个简单的例子,从 110121101_211012​(十进制为13)中减去 010020100_201002​(十进制为4)。

loading

从最右边的位(个位)开始:

  • 1−0=11 - 0 = 11−0=1。很简单。
  • 0−0=00 - 0 = 00−0=0。依然简单。
  • 1−1=01 - 1 = 01−1=0。没问题。
  • 1−0=11 - 0 = 11−0=1。完成了。

结果是 100121001_210012​,即十进制的9。完全正确。

但是如果需要借位呢?考虑 110101102−01101101211010110_2 - 01101101_2110101102​−011011012​。在最右边列的第一步就是 0−10-10−1。我们必须从左边的位借位。二进制第二位(二位)上的‘1’变成‘0’,而我们个位上的‘0’得到一个‘2’。所以,该列的计算就变成了 (0+2)−1=1(0+2) - 1 = 1(0+2)−1=1。这个借位过程从右向左级联传递,就像十进制算术一样,只是数字不同。

这种“借位”机制有一个奇妙的推论。想象一个简单的无人机高度计,它将高度存储为4位无符号数。假设之前的高度是 100121001_210012​(90米),当前高度是 011020110_201102​(60米)。为了计算高度变化,系统会计算 01102−100120110_2 - 1001_201102​−10012​。当它尝试进行减法时,会发现需要从一个不存在的第五列借位。这个最终未被满足的请求被称为​​借出位 (borrow-out bit)​​。当这个位是‘1’时,它就是一个标志!它告诉系统:“嘿,我试图用一个较小的数减去一个较大的数。” 对于我们的无人机来说,这个借出信号立即意味着它已经下降了。

教石头做减法:从规则到逻辑门

这个“借位”规则对我们来说很直观,但是你如何用硅和导线制造出一台能够遵循这个规则的物理机器呢?我们如何教一块石头思考?答案在于将问题分解成最简单的部分。

让我们从一个只能减去两个单比特 A−BA - BA−B 的电路开始。我们称之为​​半减器 (half-subtractor)​​。它有两个输入(AAA 和 BBB)和两个输出:​​差值 (Difference)​​(DDD)和一个​​借位输出 (Borrow-out)​​(BoutB_{out}Bout​)。让我们列出所有可能性:

  • 0−00 - 00−0:差为0,借位输出为0。
  • 0−10 - 10−1:差为1,借位输出为1。(我们必须借位!)
  • 1−01 - 01−0:差为1,借位输出为0。
  • 1−11 - 11−1:差为0,借位输出为0。

观察这张表,一个模式浮现出来。仅当输入不同时,差值才为‘1’——这正是一个​​异或 (XOR)​​ 逻辑门的行为。仅在一种特定情况下,借位输出为‘1’:即当 A=0A=0A=0 且 B=1B=1B=1 时。这对应于逻辑表达式 NOT A AND B\text{NOT } A \text{ AND } BNOT A AND B,或 A′BA'BA′B。因此,仅用几个基本逻辑门,我们就构建了一个能够执行最基本减法操作的电路!

当然,对于多位数字,我们还需要处理可能从右边列传来的​​借入 (borrow-in)​​。这需要一个功能稍强的电路:​​全减器 (full-subtractor)​​。它有三个输入——被减数 AAA、减数 BBB 和一个借入位 BinB_{in}Bin​——并计算 A−B−BinA - B - B_{in}A−B−Bin​。就像你可以用相同的砖块建造一座多层建筑一样,你可以将这些全减器串联起来,为数字中的每一位配备一个,从而创造出一台可以减去任意给定长度的两个数字的机器。一个阶段的借位输出成为下一个阶段的借位输入,形成一个借位的涟漪,完美地模仿了笔算方法。

天才之举:伪装成加法的减法

为加法构建一个电路,再为减法构建另一个独立的复杂电路,这似乎很浪费。几十年来,工程师们一直在努力解决这个问题。他们已经有了优雅、简单的数字加法电路(加法器)。减法能否以某种方式被“欺骗”,从而使用相同的硬件呢?答案是肯定的,而且这是计算机科学中最美妙的思想之一:​​补码 (two's complement)​​ 系统。

这个魔法的诀窍在于重新定义我们所说的“负数”。我们不是简单地在前面加一个负号,而是使用一个巧妙的循环系统,就像时钟上的数字或旧式汽车的里程表。如果你在一个4位数的里程表上处于0的位置,然后倒车一英里,它不会显示“-1”,而是会回滚到9999。同样,在一个4位二进制系统中,如果我们从 000000000000“递减”,它会回滚到 111111111111。我们规定 111121111_211112​ 就是我们对 −1-1−1 的表示。

找到一个负数的补码表示(例如,找到 −B-B−B 的位模式)的一般规则很简单:

  1. 取正二进制数 BBB。
  2. 将每一位都翻转(0变1,1变0)。这被称为​​反码 (one's complement)​​。
  3. 加1。

让我们用 B=01012B=0101_2B=01012​(即5)来试试。首先,我们翻转各位得到 101021010_210102​。然后,我们加1得到 101121011_210112​。所以,在一个4位补码系统中,101121011_210112​ 代表 −5-5−5。

现在是关键所在。操作 A−BA - BA−B 在数学上等同于操作 A+(−B)A + (-B)A+(−B)。既然我们现在有了一种将 −B-B−B 表示为二进制数(即其补码)的方法,​​减法就变成了加法​​。

让我们来看它的实际应用。假设我们要计算 11002−010121100_2 - 0101_211002​−01012​(即 12−512 - 512−5)。我们将改为计算 11002+(01012 的补码)1100_2 + (0101_2 \text{ 的补码})11002​+(01012​ 的补码)。我们已经找到了 010120101_201012​ 的补码是 101121011_210112​。所以我们只需相加:

loading

和是 10111210111_2101112​。但我们是在一个4位系统中工作!最左边的‘1’是一个我们直接丢弃的进位输出位。剩下的4位结果是 011120111_201112​,即7。成功了!即使结果为负,这也成立。计算 9−149 - 149−14 变成了一个加法问题,它能正确地得出 −5-5−5 的5位补码表示。繁琐的级联借位逻辑消失了,取而代之的是清晰、直接的加法逻辑。

通用加法/减法器:硅片中的优雅

这个“减法即加法”的技巧不仅仅是一个数学上的奇趣;它是现代处理器设计的基石。它使我们能够构建一个可以同时执行两种运算的单一、统一的电路。

假设我们有一个标准的加法器电路,用于计算 X+Y+CinX + Y + C_{in}X+Y+Cin​。我们希望用它来执行 A+BA+BA+B 和 A−BA-BA−B 两种运算,并通过一个单一的控制线(我们称之为 SUB)来选择。

  • ​​加法 (SUB=0):​​ 我们希望电路计算 A+BA+BA+B。我们可以通过将加法器的输入设置为 X=AX=AX=A,Y=BY=BY=B 和初始进位 Cin=0C_{in}=0Cin​=0 来实现。

  • ​​减法 (SUB=1):​​ 我们希望电路计算 A−BA-BA−B,我们知道这等于 A+(B 的反码)+1A + (B \text{ 的反码}) + 1A+(B 的反码)+1。我们可以通过设置 X=AX=AX=A,使 YYY 成为 BBB 的反码,并设置初始进位 Cin=1C_{in}=1Cin​=1 来实现。

我们如何让输入 YYY 根据 SUB 信号在 BBB 和其反码之间切换呢?XOR门再次拯救了我们!对于任何位 BiB_iBi​:

  • Bi⊕0=BiB_i \oplus 0 = B_iBi​⊕0=Bi​
  • Bi⊕1=NOT BiB_i \oplus 1 = \text{NOT } B_iBi​⊕1=NOT Bi​ (翻转后的位)

所以,我们可以通过为每一位设置 Yi=Bi⊕SUBY_i = B_i \oplus \text{SUB}Yi​=Bi​⊕SUB 来生成整个 YYY 输入。那么减法所需的“+1”呢?我们只需将 SUB 信号直接连接到加法器的初始进位 CinC_{in}Cin​。

结果是一个惊人简洁的设计。

  • 当 SUB=0 时:Y=BY=BY=B 且 Cin=0C_{in}=0Cin​=0。加法器计算 A+B+0A+B+0A+B+0。
  • 当 SUB=1 时:Y=NOT BY=\text{NOT } BY=NOT B 且 Cin=1C_{in}=1Cin​=1。加法器计算 A+(NOT B)+1A + (\text{NOT } B) + 1A+(NOT B)+1。

一个单一的控制信号重新配置了整个数据路径。同一个加法器电路执行两种运算。这不仅高效,而且极其优雅。它证明了在两个看似不同的操作背后,寻找一个更深层次、统一的原理所蕴含的力量。

当计算机出错时:溢出的危险

这个补码系统非常出色,但它有一个有限的范围。一个8位数字只能表示从-128到127的整数。如果我们试图计算一个超出这个范围的结果,比如 100+50100 + 50100+50,会发生什么?答案150太大了。这种情况称为​​溢出 (overflow)​​,就像我们的里程表“翻转”到了错误的区域。计算出的8位结果 100+50100+50100+50 是 −106-106−106,这是一个危险的错误。

一个关键的经验法则出现了:对于加法,溢出只可能在两个​​符号相同​​的数相加时发生(两个正数相加得到一个负数,或者两个负数相加得到一个正数)。当进行减法 A−BA-BA−B 时,这可以转化为一个简单的条件:溢出只可能在 AAA 和 BBB ​​符号相反​​时发生。例如,减去一个负数等于加上一个正数。100−(−50)100 - (-50)100−(−50) 实际上是 100+50100+50100+50,这会溢出。相反,如果 AAA 和 BBB 的符号相同,结果 A−BA-BA−B 保证能够容纳,溢出是不可能的。

幸运的是,计算机不必对其自身的错误一无所知。硬件有一种简单而巧妙的方法来检测溢出的发生。这涉及到观察加法过程中的最后两个进位:进入最高有效位(MSB)的进位(Cin,MSBC_{in,MSB}Cin,MSB​)和从它产生的进位(Cout,MSBC_{out,MSB}Cout,MSB​)。当且仅当这两个进位位不同时,溢出发生。

如果 Cin,MSBC_{in,MSB}Cin,MSB​ 是0而 Cout,MSBC_{out,MSB}Cout,MSB​ 是1,这意味着两个大的正数相加,其和意外地产生了一个为1的符号位(使其看起来像负数)。如果 Cin,MSBC_{in,MSB}Cin,MSB​ 是1而 Cout,MSBC_{out,MSB}Cout,MSB​ 是0,这意味着两个绝对值很大的负数相加,其和“环绕”过了-128,产生了一个为0的符号位(使其看起来像正数)。

这个简单的检查,Cin,MSB⊕Cout,MSBC_{in,MSB} \oplus C_{out,MSB}Cin,MSB​⊕Cout,MSB​,给了处理器一个“溢出标志位”——一个小红灯,每当计算结果不可信时就会亮起。这是机器谦逊的承认:尽管它速度快、精度高,它也必须在基本限制内运行。在理解这些限制的过程中,我们看到了完整的图景:不仅是优雅的计算机制,还有使其成为可靠工具的内置保障措施。

应用与跨学科联系

我们已经看到了定义二进制减法的一和零之间安静而富有逻辑的舞蹈。但要真正欣赏它的力量,我们必须离开抽象规则的纯粹世界,看看这场舞蹈将我们带向何方。理解一个原理是一回事;将其视为我们数字文明的关键则是另一回事。二进制减法的旅程,从逻辑门中的一次闪烁到卫星导航系统的指引,揭示了工程、计算机科学乃至物理世界之间惊人的统一性。

以逻辑铸就减法

在最基础的层面上,计算机并不“知道”如何做减法。它是一台速度极快但结构简单的机器,由数百万个微小的开关——逻辑门——构成,这些开关只能执行像与、或、非这样最基本的操作。那么我们如何教这堆开关学会减法这门艺术呢?

首先,我们可以从头开始构建一个专用电路,即*全减器*。通过分析我们从小学课本上学到的向邻位借位的简单规则,我们可以将它们直接转化为布尔逻辑的语言。差值位 DDD 结果是一个优美的对称表达式, D=A⊕B⊕BinD = A \oplus B \oplus B_{in}D=A⊕B⊕Bin​,其中 AAA 是被减数, BBB 是减数, BinB_{in}Bin​ 是从前一列的借位。借出位 BoutB_{out}Bout​ 则稍微复杂一些,但它也可以由少数几个基本逻辑门构建而成。

但在这里,自然法则,或者至少是数字的本性,为我们呈现了一条更优雅、更经济的道路。当一个电路可以同时完成加法和减法的工作时,为什么还要为它们构建两个独立的电路呢?这就是*补码*系统的天才之处。将 BBB 从 AAA 中减去的行为,被转化为将 AAA 与 BBB 的“相反数”相加的行为。在这个系统中,BBB 的相反数是通过先将其所有位翻转(这个操作称为反码,¬B\neg B¬B),然后加一得到的。所以,减法 A−BA - BA−B 就变成了加法 A+(¬B)+1A + (\neg B) + 1A+(¬B)+1。

这不仅仅是一个数学上的奇趣;它是一份硬件设计蓝图。一个加法器电路只需极小的改动就能变成一个加减法器。我们需要一种方法来告诉电路是执行加法还是减法,这通过一个控制信号来实现,我们称之为 SUB。当 SUB 为0时,我们希望计算 A+BA+BA+B。当 SUB 为1时,我们希望计算 A+(¬B)+1A + (\neg B) + 1A+(¬B)+1。

这个解决方案是数字设计的一个奇迹。一个简单的异或门具有这样的特性:B⊕0=BB \oplus 0 = BB⊕0=B 和 B⊕1=¬BB \oplus 1 = \neg BB⊕1=¬B。通过在 BBB 的输入端放置一组由我们的 SUB 信号控制的异或门,我们就创造了一个受控反相器。当 SUB 为0时,BBB 不变地通过。当 SUB 为1时,BBB 的每一位都被翻转。那么关键的“+1”怎么办?我们只需将 SUB 信号本身送入加法器的初始进位端。这是一个惊人巧妙的技巧:仅用少数几个异或门和一根导线,一个加法器就获得了减法的能力。为了看到这套机制的运作,思考一下计算 A−AA - AA−A 时会发生什么。机器会尽职地计算 A+(¬A)+1A + (\neg A) + 1A+(¬A)+1。对于一个 NNN 位的数,这个和总是恰好等于 2N2^N2N,表示为一个最终的进位输出1和 NNN 个0的结果——完美的答案零。

通用技巧:编码世界中的减法

“求补相加”策略是一个通用原则,而不仅仅是纯二进制数的一个特性。许多应用,特别是金融或仪器仪表领域的应用,更喜欢直接使用十进制数,以避免二进制小数可能引入的转换误差。它们使用诸如*二进制编码的十进制* (BCD)之类的方案,其中每个十进制数字都有自己独立的4位编码块。即使在这里,减法也模仿了加法。为了在BCD中计算 A−BA - BA−B,可以加上 BBB 的*9的补码*,即 9−B9-B9−B。例如,要减去数字5,电路可以改为加上4的BCD码(0100),然后进行一个小小的校正步骤。

其他专用编码,如*余3码*,也展示了表示法与运算之间的这种相互作用。在这种自补码中,十进制数字 DDD 的表示就是 D+3D+3D+3 的二进制。一个奇特而有用的特性出现了:如果你对两个数字的余3码执行标准的二进制减法,原始结果就是它们差值的简单二进制表示!要将其转换回余3码,你只需加上3。

这种转换的主题在计算中是永恒的。数据很少以算术逻辑单元(ALU)所需的形式出现。当你在键盘上按下一个键时,计算机接收到一个ASCII码,这是一个表示文本的通用标准。字符‘7’不是数字7;它是一个特定的7位模式(011 0111)。为了进行算术运算,系统必须首先将这个字符转换为纯数字。如何转换?当然是通过减法。通过减去字符‘0’的ASCII码(011 0000),任何数字字符的编码都会立即转换为其数值。这个简单的减法是连接人类符号世界和机器数字世界的一座基本桥梁。

减法算法:状态与记忆

到目前为止,我们一直将N位减法想象成在一个宽并行电路中一次性完成的。但我们也可以将其视为一个顺序过程,就像我们在学校学的那样:一次处理一列,从右到左,并在需要时记得“借位”。这个视角揭示了其与计算理论的深层联系。

一个串行减法器,每个时钟周期处理一对位,可以被完美地描述为一个*有限状态机。这台机器需要对过去有所记忆,但是是哪部分过去呢?它只需要知道一件事:“我是否为了前一列而从当前列借位了?”这个单一的信息位——借入位——就是机器的状态*。机器可以处于两种状态之一:S0S_0S0​(无借入)或 S1S_1S1​(有借入,借入为1)。在每一步,机器将当前 AAA 和 BBB 的位作为输入,并根据其当前状态,产生一个输出位(差值)并决定其下一个状态(为下一列的借出位)。这个形式化模型表明,减法不仅仅是一个电路图;它是一个算法,一个可以通过状态和转换来定义的逐步过程,这正是计算本身的精髓。

精度的陷阱:当减法出错时

我们的旅程终结于现实世界测量的纷繁复杂和高风险之中,在这里,数字不是完美的整数,而是对现实的有限精度近似。在这里,看似简单的减法行为可能成为灾难性错误的根源。

这个问题被称为灾难性抵消。想象一下,你想找出两个非常大且几乎相等的数之间的微小差异。在计算机中,这些数以浮点格式存储,它只保留有限数量的有效数字。当你对它们进行减法时,前面的、最高位的有效数字相互抵消,留下的结果几乎完全由最低位有效数字的噪声和不确定性构成。这就像试图通过先称量一辆载有羽毛的卡车的重量,然后再称量没有羽毛的卡车的重量,来确定一根羽毛的重量,而使用的秤的精度只能到十磅。最终的答案将毫无意义。

计算机架构师们意识到了这种危险,开发了一种巧妙的防御措施:保护位 (guard digits)。处理器的ALU会使用比标准数字格式更多的精度位来进行计算。在减法过程中,这些保护位会保留那些否则会被截断和丢失的位,从而在操作完成之前保存宝贵的信息。在减法和规格化之后,结果会被四舍五入回标准格式。当减去几乎相等的数时,缺少这些保护位可能导致截然不同——且不正确——的答案。

这个原理在全球定位系统(GPS)中尤为关键。GPS接收器通过测量来自多个卫星的信号传播时间来确定其位置。这些时间对应着非常大的距离,或称伪距,量级在数百万米。为了消除某些误差,一种标准技术是从一颗卫星的伪距中减去另一颗卫星的伪距。但是,从接收器的角度来看,星座中的卫星在天空中的位置可能相对接近。这意味着接收器经常要对两个非常大且几乎相等的数进行减法。

在这里,我们所有的概念都交汇了。接收器硬件的有限精度(由IEEE 754等标准规定)意味着每次减法都会引入一个微小的舍入误差。虽然微不足道,但这个误差可能会被卫星的几何排列急剧放大。正如一个假设但现实的问题所示,不良的卫星几何分布会放大这些减法误差,导致最终的位置估算产生数米的偏差。在处理器芯片深处,执行二进制减法的一个根本限制,最终在我们的物理世界中表现为一个可感知的误差。

从一个关于翻转位的简单规则,到一个可能导致船只迷航的无形误差源,二进制减法的故事远比初看起来要宏大得多。它证明了一个事实:在科学和工程领域,没有“微不足道”的细节。最基本的原理,当被放大并应用于现实世界时,会产生最深刻和最意想不到的后果。

1101 (13) - 0100 (4) ------
1100 (12) + 1011 (-5) ------ 10111