← 全部文章 计算机基础

原码、反码、补码,以及负数取模

2021 年 6 月 17 日 · 2 分钟读完

目录
  1. 机器数与真值
  2. 原码、反码、补码
  3. 为什么要用补码做加减
  4. 为什么「取反加一」就对了
  5. 负数取模
  6. 延伸

上一篇讲了正整数怎么变成二进制。负数呢?这一篇以 1 个字节(8 位)为例。

机器数与真值

机器数:一个数在计算机里的二进制形式。有符号数用最高位存符号,正数为 0,负数为 1。

+3 → 0000 0011
-3 → 1000 0011   (这是原码,下面会讲计算机实际存的是补码)

真值:因为最高位是符号位,机器数「看起来」的值不等于它真正代表的值。1000 0011 如果当无符号数是 131,当有符号数(原码)则是 -3。带符号位的机器数所对应的真正数值,叫它的真值。

原码、反码、补码

先记住两点:

  1. 这三种码存在的意义是让计算机方便地做减法,也就是处理负数。
  2. 正数的原码、反码、补码完全相同,下面只讨论负数。
| 符号位 | ------- 数据位(7 位)------- |
十进制 原码 反码(符号位不变,其余取反) 补码(反码 + 1)
+3 0000 0011 0000 0011 0000 0011
-3 1000 0011 1111 1100 1111 1101
+0 0000 0000 0000 0000 0000 0000
-0 1000 0000 1111 1111 0000 0000(溢出位丢掉)

另一种好记的算法:负数的补码 = 它的绝对值的二进制按位取反再加 1。-3 的绝对值 0000 0011 取反是 1111 1100,加 1 得 1111 1101,和表里一致。

为什么要用补码做加减

人算原码减法时,会先看符号位再算数值位;可对电路来说,「先判断符号」很麻烦。最好是让符号位也直接参加运算,减法变成加一个负数。用原码试试:

3 - 2 = 3 + (-2)
      = 0000 0011 + 1000 0010(原码)
      = 1000 0101
      = -5    ← 错了

换成补码:

3 - 2 = 0000 0011 + 1111 1110(补码)
      = 1 0000 0001 → 丢掉溢出的最高位 → 0000 0001 = 1  ✓

4 - 4 = 0000 0100 + 1111 1100
      = 1 0000 0000 → 0000 0000 = 0  ✓

-1 - 127 = 1111 1111 + 1000 0001
         = 1 1000 0000 → 1000 0000 = -128

最后一个例子说明:补码里没有「-0」,原来 -0 的位置 1000 0000 被拿来表示 -128。所以有符号 8 位整数的范围是 -128 ~ 127。

为什么「取反加一」就对了

我们想找 0000 1111 的相反数 x,使 0000 1111 + x = 0。直接凑不好凑,但凑一个「相加得全 1」的数很简单,就是按位取反 1111 0000。

0000 1111 + 1111 0000     = 1111 1111
0000 1111 + 1111 0000 + 1 = 1 0000 0000 → 溢出位丢掉 → 0000 0000

所以 1111 0000 + 1 = 1111 0001 就是 0000 1111 的相反数。取反是为了凑全 1,加 1 是为了让它溢出成 0。

换个角度:8 位能表示 256 个状态,补码就是把 0~255 这个圈的后半段挪去表示负数,255 ↔ -1、254 ↔ -2……128 ↔ -128,x 的补码就等于 256 - |x|。

无符号 0~255 有符号 -128~127 二进制
255 -1 1111 1111
254 -2 1111 1110
246 -10 1111 0110
156 -100 1001 1100
129 -127 1000 0001
128 -128 1000 0000
127 127 0111 1111
1 1 0000 0001
0 0 0000 0000

负数取模

取模是求两数相除的余数:

  1. 求整数商 c = a / b 取整;
  2. 余数 r = a - c × b。

关键在第一步怎么取整。数学里的取模(mod)向负无穷取整(floor),所以:

 7 mod  4 =  3   (商在 1 和 2 之间,向下取 1)
-7 mod  4 =  1   (商在 -1 和 -2 之间,向下取 -2,r = -7 - (-2)×4 = 1)
 7 mod -4 = -1   (商向下取 -2,r = 7 - (-2)×(-4) = -1)
-7 mod -4 = -3   (商向下取 1)

数学取模的结果和除数同号。

延伸

  • 编程语言里的 % 不一定是数学取模:C#、C、C++、Java、JavaScript 的 % 是取余,商向 0 取整,结果和被除数同号,所以 C# 里 -7 % 4 == -3。Python 的 % 才是向下取整的数学取模,-7 % 4 == 1。要在 C# 里得到非负的结果,可以写 ((a % b) + b) % b。
  • 溢出在 C# 里默认不报错:int.MaxValue + 1 会悄悄变成 int.MinValue,这正是补码绕圈的结果。需要发现溢出时,用 checked { ... } 或在项目属性里打开「检查运算上溢/下溢」。
  • PLC 里也一样:西门子的 INT 是 16 位补码(-32768 ~ 32767),上位机读到 0xFFFF 时,按 short 解析是 -1,按 ushort 解析是 65535。读数异常时先查类型对不对。

# 二进制 # 补码

原文首发于 blog.csdn.net