原码, 反码, 补码

104 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情

机器数

机器数是将符号"数字化"的数, 是数字在计算机中的二进制表示形式. 机器数有2个特点: 一是符号数字化, 二是其数的大小受机器字长的限制.

根据小数点位置固定与否, 机器数又可以分为定点数和浮点数. 通常, 使用定点数表示整数, 而用浮点数表示实数.

在计算机用一个数的最高位存放符号, 正数为0, 负数为1.

在计算机中 这和计算机 字长(位数) 相关.

例如:

计算机字长为4位时:

+3=0011
-3=1011

真值(机器数的真值)

就是真正的数值. 比如 -1,-2,+1,+2;

计算机字长为4位时:

0011 的真值 +3
1011 的真值 -3

整数

原码

最高位为符号位, 0代表正数, 1代表负数, 非符号位为该数字绝对值(真值的绝对值)的 二进制 表示;

计算机字长为4位时:

+3的原码为 0 011
-3的原码为 1 011

因为第一位是符号位, 所以8字长二进制数的取值范围就是:[1111 1111 , 0111 1111], 也就是[-127 , 127];4字长二进制数的取值范围就是:[1111 , 0111], 也就是[-8 , 7];

原码的符号位不能直接参与运算(有负数时), 必须和其他位分开, 这就增加了硬件的开销和复杂性

反码

正数的反码是其本身

负数的反码是在其原码的基础上, 符号位不变, 其余各个位取反.

在4位机器上

+3=[0011]=[0011]+3=[0 011]_{原}= [0 011]_{反}

3=[1011]=[1100]-3=[1 011]_{原}= [1 100]_{反}

补码(也是机器使用的)

正数的补码就是其本身.

负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

在4位机器上

+3=[0011]=[0011]=[0011]+3=[0 011]_{原}= [0 011]_{反}= [0 011]_{补} 3=[1011]=[1100]=[1101]-3=[1 011]_{原}= [1 100]_{反}= [1 101]_{补}

使用

原码的符号位不能直接参与运算, 必须和其他位分开, 这就增加了硬件的开销和复杂性.

因此有了反码.

在4位机器上:

33=3+(3)=[0011]+[1011]=[0011]+[1100]=[1111]=[1000]3-3=3+(-3)=[0 011]_{原} + [1 011]_{原}=[0 011]_{反}+[1 100]_{反}=[1 111]_{反}=[1 000]_{原}

这里出现了一个问题, 这里出现了 -00.

当用反码计算两正数相减时, 2-1=3+(-1)=[0 010]{原} + [1 001]{原}=[0 010]{反}+[1 101]{反}=[1111]{反}=[1 000]{原}.显然这也是错误的.

由此加入了补码:

33=3+(3)=[0011]+[1011]=[0011]+[1100]=[0011]+[1101]=  [10000](这里溢出来了)=[0000]3-3=3+(-3)=[0 011]_{原} + [1 011]_{原}=[0 011]_{反}+[1 100]_{反}=[0 011]_{补}+[1 101]_{补}=~~[10 000]_{补}(这里溢出来了)=[0 000]_{反}

溢出及补码溢出的判断

无论采用何种机器数, 只要运算的结果大于数值设备所能表示数的范围, 就会产生溢出. 溢出现象应当作一种故障来处理, 因为它使结果数发生错误.

只有真正意义上的相加出现的溢出才需要处理, 比如: 正+正, 负+负, 正-负, 负-正.

  1. 不是同号数相加, 则不可能溢出, 但有可能出现正常进位;
  2. 同号数相加有可能溢出;
  3. 同号数相加, 如果结果的符号位和两加数不同, 既是溢出;

计算机判断:

查其最高位和次高位的进位位:

  1. 均无进位产生 ,结果正确
  2. 均产生进位, 符号位进位自然丢失, 结果正确
  3. 只有一位产生了进位,会出现溢出或进位到符号导致符号错误

此即为判断机器是正常进位还是溢出的基本依据,在微型机中可用异或电路来实现上述的判断.

[0000][0 000]_{反} 表示0, 那么 [1000][1 000]_{反} 也可以表示另外一个数 8-8 , 即最小值

因为机器使用补码, 所以对于32位int类型, 可以表示范围是: [231,(231)1][-2^31, (2^{31})-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.

原理

问题: 为什么使用补码可以计算出正确结果.

同余

当两个整数除以同一个正整数, 若得相同余数, 则二整数同余. 记作 a ≡ b (mod m).读作 a 与 b 关于模 m 同余.

求模: 对于正数实际上就是余数.

公式(正负通用):

amodb=ab×a/y,y0a\mod b=a-b\times\lfloor a/y\rfloor,y\neq0

其中\lfloor \rfloor表示取下界.另为\lceil\rceil表示取上界.

取上界(Ceiling):
向0方向舍入取整;
正数: 把小数部分全部去掉后加;
负数: 把小数部分全部去掉. 即所有数都是趋进于0.

取下界(Floor):
向负无穷方向舍入取整;
正数: 把小数部分全部去掉;
负数: 把小数部分全部去掉后减1. 即所有数都是取小.

不同的语言对求模结果不相同. 对于C/Java 类型语言一般是取上界.

例如:

 4 mod 12 = 4
16 mod 12 = 4
28 mod 12 = 4

解决

33=3+(3)=[0011]+[1011]=[0011]+[1100]=[1111]=[1000]3-3=3+(-3)=[0 011]_{原} + [1 011]_{原}=[0 011]_{反}+[1 100]_{反}=[1 111]_{反}=[1 000]_{原}

关键在于让-3(负数)变成一个正数, 加完以后和原来直接相加结果一样.

首先, 如过没有条件限制这是不可能的; 在计算机中有字长限制, 如 32位,64位等, 限制了一个数字最大有多大.

先看一个简单的例子:

在一个钟表中, 如果当前时间是6点, 希望将时间设置成4点, 需要怎么做呢?

  1. 回拨(2-12n)个小时:6-2-12n
  2. 往前拨(10+12n)个小时(n>0, n 是正整数):6+10+12n

由1,2 可知 1的减法可以由2的加法代替. 2 实际上是取模运算. 回拨2小时等同于前拨10小时

(6+10+12n) mod 12 =4(n>0, n 是正整数)(这一组数也叫剩余类);

根据同余性质:

反身性: a ≡ a (mod m)

保持基本运算性质:

image.png

2 ≡ 2 (mod 12)
(-2) ≡ 10 (mod 12)
6 - 2 ≡ 6 + 10 (mod 12)

-2 模12的正同余数之一是10, 即6 - 2 与 6 + 10 模 12的结果相等.

以4位机器二进制为例:

3=[1011]=[1100]-3=[1 011]_{原}=[1 100]_{反}

如果将[1100][1 100]_{反}认为是原码,则[1100][1 100]_{反-原}, 由于算机在运算时并不辨别符号位, 所以可看作12. 同样4位字长机最大数为16(16个数).

(-3) mod 16 = 13 // 真值
  12 mod 16 = 12 // 反码
  13 mod 16 = 13 // 补码

  12 mod 15 = 12 // 反码mod15
  -3 mod 15 = 12 // 真值mod15

这里可以看到只有补码与真值模16时是同余, 事实上, 正数的反码对应的无符号数与真值是关于16同余的, 而负数的反码对应的无符号数却与真值关于15同余.

重点: 实际上一个正数的反码, 是这个数对于一个 的同余数. 这个 对应 计算机字长 中所能表示的最大值. 为了解决负数的问题引入了补码, 对于补码在反码的基础上+1, 其实是解决0 这个数字造成的问题, 因为没有-0.