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

前言

大家好,我是小彭。

在前面的文章里,咱们聊到了核算机的冯诺依曼架构的 3 个基本原则。其间第 1 个原则是核算机中一切信息都是采用二进制格局的编码。也便是说,在核算机中程序的数据和指令,以及用户输入的一切数据,核算机都需求把它们转化为二进制的格局,才干进行识别和运算。

然而,咱们日常日子接触到的大部分数字却是十进制编码,例如手机号码、工商标、学号。那为什么核算机要运用二进制数制?二进制数据怎样进行运算,以及核算机做了哪些优化来怎样进步运算的功率?今天咱们就围绕这些问题打开。


思维导图:

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


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 位数为例,无符号数能够表明 065536,而有符号数能够表明 -3276832768。

提示: 无符号数和有符号数表明的数值规模巨细是相同大的,n 位二进制最多只能表明 2n2^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、补码的要害是找到一个与负数等价的正补数,运用该正补数替代负数参加核算,从而将减法运算替换为加法运算。

在前文讲补码的地方,咱们说到核算机一切 “整型类型” 的负数都会运用补码表明法,故意强调 “整数类型” 是什么原因呢,难道浮点数和整数在核算机中的表明办法不同吗?这个问题咱们在 下一篇文章 里讨论,请重视。


参考资料

  • 核算机组成原理教程(第 2、6 章) —— 尹艳辉 王海文 邢军 著
  • 深入浅出核算机组成原理(第 11 ~ 16 讲) —— 徐文浩 著,极客时刻 出品
  • 10分钟速成课 核算机科学 —— Carrie Anne 著
  • Binary number —— Wikipedia

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

本文正在参加「金石方案 . 瓜分6万现金大奖」