原码、反码、补码,以及负数取模
2021 年 6 月 17 日 · 2 分钟读完
上一篇讲了正整数怎么变成二进制。负数呢?这一篇以 1 个字节(8 位)为例。
机器数与真值
机器数:一个数在计算机里的二进制形式。有符号数用最高位存符号,正数为 0,负数为 1。
+3 → 0000 0011
-3 → 1000 0011 (这是原码,下面会讲计算机实际存的是补码)
真值:因为最高位是符号位,机器数「看起来」的值不等于它真正代表的值。1000 0011 如果当无符号数是 131,当有符号数(原码)则是 -3。带符号位的机器数所对应的真正数值,叫它的真值。
原码、反码、补码
先记住两点:
- 这三种码存在的意义是让计算机方便地做减法,也就是处理负数。
- 正数的原码、反码、补码完全相同,下面只讨论负数。
| 符号位 | ------- 数据位(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 |
负数取模
取模是求两数相除的余数:
- 求整数商
c = a / b取整; - 余数
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