为什么计算机中的负数要用补码表示?

为什么,计算机,负数,要用,补码,表示 · 浏览次数 : 1626

小编点评

**方法 1 - 借位做减法** 借位是一种将两个数字借位相减的方法。 **方法 2 - 减模加补** 减模加补是一种将两个数字进行减法运算的方法,但它使用补码来消除负数的影响。 **补码的出现** 补码是一种将负数表示为正数的方法。它通过找到一个与负数等价的正补数来实现。 **补码的用途** 补码可以用于消除负数在有符号机器数运算中的影响。它可以将减法运算替换为加法运算,从而实现简化电路。

正文

本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。

前言

大家好,我是小彭。

在前面的文章里,我们聊到了计算机的冯·诺依曼架构的 3 个基本原则。其中第 1 个原则是计算机中所有信息都是采用二进制格式的编码。也就是说,在计算机中程序的数据和指令,以及用户输入的所有数据,计算机都需要把它们转换为二进制的格式,才能进行识别和运算。

然而,我们日常生活接触到的大部分数字却是十进制编码,例如手机号码、工牌号、学号。那为什么计算机要使用二进制数制?二进制数据如何进行运算,以及计算机做了哪些优化来如何提高运算的效率?今天我们就围绕这些问题展开。


小彭的 Android 交流群 02 群已经建立啦,扫描文末二维码进入~


思维导图:


1. 为什么计算机要使用二进制数制?

所谓数制其实就是一种 “计数的进位方式”。

常见的数制有十进制、二进制、八进制和十六进制:

  • 十进制是我们日常生活中最熟悉的进位方式,它一共有 0、1、2、3、4、5、6、7、8 和 9 十个符号。在计数的过程中,当某一位满 10 时,就需要向它临近的高位进一,即逢十进一;

  • 二进制是程序员更熟悉的进位方式,也是随着计算机的诞生而发展起来的,它只有 0 和 1 两个符号。在计数的过程中,当某一位满 2 时,就需要向它临近的高位进一,即逢二进一;

  • 八进制和十六进制同理。

那么,为什么计算机要使用二进制数制,而不是人类更熟悉的十进制呢?其原因在于二进制只有两种状态,制造只有 2 个稳定状态的电子元器件可以使用高低电位或有无脉冲区分,而相比于具备多个状态的电子元器件会更加稳定可靠。


2.有符号数与无符号数

在计算机中会区分有符号数和无符号数,无符号数不需要考虑符号,可以将数字编码中的每一位都用来存放数值。有符号数需要考虑正负性,然而计算机是无法识别符号的 “正+” 或 “负-” 标志的,那怎么办呢?

好在我们发现 “正 / 负” 是两种截然不同的状态,正好可以映射到计算机能够理解的 “0 / 1” 上。因此,我们可以直接 “将符号数字化”,将 “正+” 数字化为 “0”,将 “负-” 数字化为 “1”,并将数字化后的符号和数值共同组成数字编码。

另外,为了计算方便,我们额外再规定将 “符号位” 放在数字编码的 “最高位”。例如,+1110-1110 用 8 位二进制表示就是:

  • 0000, 1110(符号作为编码的一部分,最高位 0 表示正数)
  • 1000, 1110(符号作为编码的一部分,最高位 1 表示负数)

从中我们也可以看出无符号数和有符号数的区别:

  • 1、最高位功能不同: 无符号数的编码中的每一位都可以用来存放数值信息,而有符号数需要在编码的最高位留出一位符号位;

  • 2、数值范围不同: 相同位数下有符号数和无符号数表示的数值范围不同。以 16 位数为例,无符号数可以表示 0~65536,而有符号数可以表示 -32768~32768。

提示: 无符号数和有符号数表示的数值范围大小是一样大的,n 位二进制最多只能表示 $2^n$ 个信息量,这是无法被突破的。


3. 机器数的运算效率问题

在计算机中,我们会把带 “正 / 负” 符号的数称为真值(True Value),而把符号化后的数称为机器数(Computer Number)。

机器数才是数字在计算机中的二进制表示。 例如在前面的数字中, +1110 是真值,而 0000, 1110 是机器数。新的问题来了:将符号数字化后的机器数,在运算的过程中符号位是否与数值参与运算,又应该如何运算呢?

我们先举几个加法运算的例子:

  • 两个正数相加:
0000, 1110 + 0000, 0001 = 0000, 1111 // 14 + 1 = 15 正确
^            ^            ^
符号位        符号位        符号位
  • 两个负数相加:
1000, 1110 + 1000, 0001 = 0000, 1111 // (-14) + (-1) = 15 错误
^            ^            ^
符号位        符号位        符号位(最高位的 1 溢出)
  • 正负数相加:
0000, 1110 + 1000, 0001 = 1001, 1111 // 14 + (-1) = -15 错误
^            ^            ^
符号位        符号位        符号位

可以看到,在对机器数进行 “按位加法” 运算时,只有两个正数的加法运算的结果是正确的,而包含负数的加法运算的结果却是错误的,会出现 -14 - 1 = 1514 - 1 = -15 这种错误结果。

所以,带负数的加法运算就不能使用常规的按位加法运算了,需要做特殊处理:

  • 两个正数相加:

    • 直接做按位加法。
  • 两个负数相加:

    • 1、用较大的绝对值 + 较小的绝对值(加法运算);
    • 2、最终结果的符号为负。
  • 正负数相加:

    • 1、判断两个数的绝对值大小(数值部分);
    • 2、用较大的绝对值 - 较小的绝对值(减法运算);
    • 3、最终结果的符号取绝对值较大数的符号。

哇🤩?好好的加法运算给整成减法运算? 运算器的电路设计不仅要多设置一个减法器,而且运算步骤还特别复杂。那么,有没有不需要设置减法器,而且步骤简单的方案呢?


4. 原码、反码、补码

为了解决有符号机器数运算效率问题,计算机科学家们提出多种机器数的表示法:

机器数 正数 负数
原码 符号位表示符号
数值位表示真值的绝对值
符号位表示数字的符号
数值位表示真值的绝对值
反码 无(或者认为是原码本身) 符号位为 1
数值位是对原码数值位的 “按位取反”
补码 无(或者认为是原码本身) 在负数反码的基础上 + 1
  • 1、原码: 原码是最简单的机器数,例如前文提到从 +1110-1110 转换得到的 0000, 11101000, 1110 就是原码表示法,所以原码在进行数字运算时会存在前文提到的效率问题;

  • 2、反码: 反码一般认为是原码和补码转换的中间过渡;

  • 3、补码: 补码才是解决机器数的运算效率的关键, 在计算机中所有 “整型类型” 的负数都会使用补码表示法;

    • 正数的补码是原码本身;
    • 零的补码是零;
    • 负数的补码是在反码的基础上再加 1。

很多教材和网上的资料会认为正数的原码、反码和补码是相同的,这么说倒也不影响什么。 但结合补码的设计原理,小彭的观点是正数是没有反码和补码的,负数使用补码是为了找到一个 “等价” 的正补数代替负数参与计算,将加减法运算统一为两个正数加法运算,而正数自然是不需要替换的,所以也就没有补码的形式。

提示: 为了便于你理解,小彭后文会继续用 “正数的补码是原码本身” 这个观点阐述。


5. 使用补码消除减法运算

理解补码表示法后,似乎还是不清楚补码有什么用❓

我们重新计算上一节的加法运算试试:

举例 真值 原码 反码 补码
+14 +1110 0000, 1110 0000, 1110 0000, 1110
+13 +1101 0000, 1101 0000, 1101 0000, 1101
-14 +1110 1000, 1110 1111, 0001 1111, 0010
-15 -1110 1000, 1111 1111, 0000 1111, 0001
+1 +0001 0000, 0001 0000, 0001 0000, 0001
-1 -0001 1000, 0001 1111, 1110 1111, 1111
  • 两个正数相加:
// 补码表示法
0000, 1110 + 0000, 0001 = 0000, 1111 // 14 + 1 = 15 正确
^            ^            ^
符号位        符号位        符号位
  • 两个负数相加:
// 补码表示法
1111, 0010 + 1111, 1111 = 1111, 0001 // (-14) + (-1) = -15 正确
^            ^            ^
符号位        符号位        符号位(最高位的 1 溢出)
  • 正负数相加:
// 补码表示法
0000, 1110 + 1111, 1111 = 0000, 1101 // 14 + (-1) = 13 正确
^            ^            ^
符号位        符号位        符号位(最高位的 1 溢出)

可以看到,使用补码表示法后,有符号机器数加法运算就只是纯粹的加法运算,不会因为符号的正负性而采用不同的计算方法,也不需要减法运算。因此电路设计中只需要设置加法器和补数器,就可以完成有符号数的加法和减法运算,能够简化电路设计。

除了消除减法运算外,补码表示法还实现了 “0” 的机器数的唯一性:

在原码表示法中,“+0” 和 “-0” 都是合法的,而在补码表示法中 “0” 只有唯一的机器数表示,即 0000, 0000 。换言之补码能够比原码多表示一个最小的负数 1000, 0000

最后提供按照不同表示法解释二进制机器数后得到的真值对比:

二进制数 无符号真值 原码真值 反码真值 补码真值
0000, 0000 0 +0 +0 +0
0000, 0001 1 +1 +1 +1
1000, 0000 128 -0(负零,无意义) -127 -128(多表示一个数)
1000, 0001 129 -1 -126 -127
1111, 1110 254 -126 -1 -2
1111, 1111 255 -127 -0(负零) -1

6. 补码我懂了,但是为什么?

理解原码和补码的定义不难,理解补码作用也不难,难的是理解补码是怎么设计出来的,总不可能是被树上的苹果砸到后想到的吧?

这就要提到数学中的 “补数” 概念:

  • 1、当一个正数和一个负数互为补数时,它们的绝对值之和就是模;
  • 2、一个负数可以用它的正补数代替。

6.1 时钟里的补数

听起来很抽象对吧❓其实生活中,就有一个更加形象的例子 —— 时钟,时钟里就蕴含着补数的概念!

比如说,现在时钟的时针刻度指向 6 点,我们想让它指向 3 点,应该怎么做:

  • 方法 1 : 逆时针地拨动 3 个点数,让时针指向 3 点,这相当于做减法运算 -3;
  • 方法 2: 顺时针地拨动 9 个点数,让时针指向 3 点,这相当于做加法运算 +9。

可以看到,对于时钟来说 -3 和 +9 竟然是等价的! 这是因为时钟只能 12 个小时,当时间点数超过 12 时就会自动丢失,所以 15 点和 3 点在时钟看来是都是 3 点。如果我们要在时钟上进行 6 - 3 减法运算,我们可以将 -3 等价替换为它的正补数 +9 后参与计算,从而将减法运算替换为 6 + 9 加法运算,结果都是 3。

6.2 十进制的例子

理解了补数的概念后,我们再多看一个十进制的例子:我们要计算十进制 354365 - 95937 = 的结果,怎么做呢?

  • 方法 1 - 借位做减法: 常规的做法是利用连续向前借位做减法的方式计算,这没有问题;
  • 方法 2 - 减模加补: 使用补数的概念后,我们就可以将减法运算消除为加法运算。

具体来说,如果我们限制十进制数的位长最多只有 6 位,那么模就是 1000000,-95937 对应的正补数就是 1000000 - 95937 = 904063 。此时,我们可以直接用正补数代替负数参与计算,则有:

354365 - 95937 // = 258428

= 354365 - (1000000 - 904063)

= 354365 - 1000000 + 904063 【减整加补】

= 258428

可以看到,把 -95937 等价替换为 +904063 后,就把减法运算替换为加法运算。细心的你可能要举手提问了,还是需要减去 1000000 呀?🙋🏻‍♀️

其实并不用,因为 1000000 是超过位数限制的,所以减去 1000000 这一步就像时针逆时针拨动一整圈一样是无效的。所以实际上需要计算的是:

// 实际需要计算的是:
354365 + 904063
= 1258428 = 258428
  ^
  最高位 1 超出位数限制,直接丢弃

6.3 为什么要使用补码?

继续使用前文提到的 14 + (-1) 正负数相加的例子:

// 原码表示法
0000, 1110 + 1000, 0001 = 1001, 1111 // 14 + (-1) = -15 错误
^            ^            ^
符号位        符号位        符号位

// 补码表示法
0000, 1110 + 1111, 1111 = 1, 0000, 1101 // 14 + (-1) = 13 正确
^            ^            ^
符号位        符号位        最高位 1 超出位数限制,直接丢弃

如果我们限制二进制数字的位长最多只有 8 位,那么模就是 1, 0000, 0000 ,此时,-1 的二进制数 1000, 0001 的正补数就是 1111, 1111

我们使用正补数 1111, 1111 代替负数 1000, 0001 参与运算,加法运算后的结果是 1, 0000, 1101。其中最高位 1 超出位数限制,直接丢弃,所以最终结果是 0000, 1101,也就是 13,计算正确。

补码示意图

到这里,相信补码的设计原理已经很清楚了。

补码的关键在于:找到一个与负数等价的正补数,使用该正补数代替负数,从而将减法运算替换为两个正数加法运算。 补码的出现与运算器的电路设计有关,从设计者的角度看,希望尽可能简化电路设计和计算复杂度。而使用正补数代替负数就可以消除减法器,实现简化电路的目的。

所以,小彭认为只有负数才存在补码,正数本身就是正数,根本就没必要使用补数,更不需要转为补码。而且正数使用补码的话,还不能把负数转补码的算法用在正数上,还得强行加一条 “正数的补码是原码本身” 的规则,就离谱好吧。


7. 总结

  • 1、无符号数的编码中的每一位都可以用来存放数值信息,而有符号数需要在最高位留出一位符号位;

  • 2、在有符号数的机器数运算中,需要对正数和负数采用不同的计算方法,而且需要引入减法器;

  • 3、为了解决有符号机器数运算效率问题,计算机科学家们提出多种机器数的表示法:原码、反码、补码和移码;

  • 4、使用补码表示法后,运算器可以消除减法运算,而且实现了 “0” 的机器数的唯一性;

  • 5、补码的关键是找到一个与负数等价的正补数,使用该正补数代替负数参与计算,从而将减法运算替换为加法运算。

在前文讲补码的地方,我们提到计算机所有 “整型类型” 的负数都会使用补码表示法,刻意强调 “整数类型” 是什么原因呢,难道浮点数和整数在计算机中的表示方法不同吗?这个问题我们在 下一篇文章 里讨论,请关注。


参考资料

小彭的 Android 交流群 02 群

与为什么计算机中的负数要用补码表示?相似的内容:

为什么计算机中的负数要用补码表示?

本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。 前言 大家好,我是小彭。 在前面的文章里,我们聊到了计算机的冯·诺依曼架构的 3 个基本原则。其中第 1 个原则是计算机中所有信息都是采用二进制格式的编码。也就是说,在计算机中程序的数据和指令,以及用户输入的

[转帖]计算机二进制的源码、反码、补码详讲解

一、原码、反码、补码的概念 原码 :最高位是符号位,0代表正数,1代表负数,非符号位为该数字绝对值的二进制。 反码:正数的反码与原码一致,负数的反码是对原码按位取反,只是最高位(符号位)不变。 补码:正数的补码与原码一致,负数的补码是对原码按位取反加1,符号位不变。 二、示例 例如 十进制数字: +

[转帖]《Linux性能优化实战》笔记(十七)—— Linux网络基础与性能指标

一、 网络模型 1. OSI 网络模型(七层) 为了解决网络互联中异构设备的兼容性问题,并解耦复杂的网络包处理流程,OSI 模型把网络互联的框架分为七层,每个层负责不同的功能。其中, 应用层,负责为应用程序提供统一的接口。表示层,负责把数据转换成兼容接收系统的格式。会话层,负责维护计算机之间的通信连

【0day漏洞复现】北京慧飒科技有限责任公司WEB VIDEO PLATFORM存在未授权访问漏洞

阅读须知 花果山的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。本文所提供的工具仅用于学习,禁止用于

流量调度、微服务可寻址性和注册中心

前言 现代计算机基于计算、存储和调度的体系, 于是现代架构都是围绕这三大话题不断演进。 在基础架构部, 也是主要为了解决这三个难题,为业务事业部提供透明的、高可用、可快速伸缩的 三大能力, 我们组主要负责 [流量调度] 这个话题,下面是一些宏观的技术笔记。 在单体结构, 流量调度是直观且无感的(DN

为什么你学不会递归?谈谈我的经验

本文已收录到 GitHub · AndroidFamily,有 Android 进阶知识体系,欢迎 Star。技术和职场问题,请关注公众号 [彭旭锐] 进 Android 面试交流群。 前言 大家好,我是小彭。 今天分享到计算机科学中一个基础又非常重要的概念 —— 递归。递归是计算机中特有的概念,你

面试官:字节流可以处理一切文件为什么还需要字符流呢?

一、写在开头 在计算机领域中百分之九十以上的程序拥有着和外部设备交互的功能,这就是我们常说的IO(Input/Output:输入/输出),所谓输入就是外部数据导入计算机内存中的过程,输出则是将内存或者说程序中的数据导入到外部存储中,如数据库、文件以及其他本地磁盘等。 二、什么是IO流 这种输入输出往

探索计算机的I/O控制方式:了解DMA控制器的作用与优势

对于有科班背景的读者,可以跳过本系列文章。这些文章的主要目的是通过简单易懂的汇总,帮助非科班出身的读者理解底层知识,进一步了解为什么在面试中会涉及这些底层问题。否则,某些概念将始终无法理解。这些计算机基础文章将为你打通知识的任督二脉,祝你在编程领域中取得成功!

空闲空间管理和文件系统结构的优化策略

对于有科班背景的读者,可以跳过本系列文章。这些文章的主要目的是通过简单易懂的汇总,帮助非科班出身的读者理解底层知识,进一步了解为什么在面试中会涉及这些底层问题。否则,某些概念将始终无法理解。这些计算机基础文章将为你打通知识的任督二脉,祝你在编程领域中取得成功!

react17源码中部分二进制计算的解释

theme: qklhk-chocolate highlight: a11y-dark react17放弃了之前的expirationTime而启用了lane模型,故而在原来16的基础上又产生了更多的二进制运算,在接下来的一段时间我打算把这些二进制运算都整明白了、 关于react为什么会启用lane