位运算 补码 反码
位运算 补码 反码
很多初学者经常搞不懂补码反码原码这些东西,今天我就在这里好好理一下。
基本运算
先来讲讲基本的与或等运算,会的可以直接跳过
与 AND | 或 OR | 异或 XOR | 非 NOT | |
---|---|---|---|---|
1,1 | 1 | 1 | 0 | |
1,0 | 0 | 1 | 1 | |
0,0 | 0 | 0 | 0 | |
1 | 0 | |||
0 | 1 | |||
有0则为0 | 有1则为1 | 异常的或,在都为1的时候为0 | 取反 |
容易看出,与,或,异或是二元运算(即有两个操作数,如加减乘除也是二元运算),且满足交换律。而非是一元运算。当然也可以有多位一起参加运算,如10 ^ 11 = 01,各位运算是独立的
另外,与,或,异或也满足结合律:
1 | (a & b) & c == a & (b & c) |
其中& ,|,^ 在C语言中分别表示与,或,异或运算,现在来证明一下:
与:只要位上有1,结果必是1
或:只要位上有0,结果必是0
异或:位上1的个数为奇数,结果就是1,反之为0
原码,反码
现在来讲讲正题。首先我们要知道补码和反码是两种储存数据的方式
大家都知道计算机里存储的是二进制数,如果我们用八个位表示一个整数,
如2 = (00000010),其中00000010就叫做2的原码。
这样我们就可以用八位表示0~255的任意整数了。但是我们如何表示负数呢。我们就想出了反码这种东西。要理解这个还得了解一下计算机如何执行加法。
1 | 2+1 = (00000010) + (00000001) = (00000011) = 3 |
可以看出就是两个加数各位异或,再加个与运算判断是否进位。(可以用这个原理在MC用红石搭个简单的加法器)。
所以我们如果要表示负数(最关键的问题),可以用 a + (-a) = 0来定义表示方法。我们不妨定义(11111111)为-0(也就是0)这样我们我们就可以把一个数各位取反来表示她的相反数。如(~表示取反)
1 | 2 = (0000 0010) |
这就是反码,指的不仅是按位取反,还指这种存储数据的方式
由此可见,(1000 0000)不能再像原来那样表示128了,因为他现在表示-127,以及128以上数(最高位为1)的表示方法同样被抢走了,所以我们就不表示这些数,那现在我们用八位表示的整数范围便变成了[-127,127]
我们还可以知道,最高位是0的数现在都是正数,而正数取反后得到负数,所以负数的最高位是1(不严谨的讨论,-0看作负数,0看作正数^_^)所以我们现在把最高位叫做符号位
补码
可是我们知道我们现在都是以补码方式存储数据,补码又是什么,相对于反码又有什么优势呢
补码也叫做二补数,而刚刚介绍的反码叫做一补数,这也可以看出补码是比反码先进一点。
仔细看看我们刚刚的反码,我们用了(0000 0000)和(1111 1111)两种方式表示0,这使0的编码不唯一了,很多情况下我们需要特殊判断,导致效率低下(特别是电路设计中)。所以我们用补码解决了这个问题。
补码和反码的区别就在于负数的表示。回到最初的问题,我们需要表示负数,但现在我们不能再用(1111 1111)表示0了,要坚持一个0原则(0000 0000),可我们的 a + (-a) = 0还是要成立的,所以
1 | 2 = (0000 0010) |
我们就有了另一种数据存储方式,(1111 1110)就是-2的补码。现在我们来研究一下她的性质
我们容易得到:(1溢出到最高位之外了,我们不要了)
1 | (1111 1111) + (0000 0001) = (0000 0000) |
可得(1111 1111)表示-1
还有:(再说一次~表示取反)
1 | 2 = (0000 0010) |
所以我们得到(~a+1)就是a的补码,也就是我们常说的补码等于反码加一
这样子我们就解决了0的唯一性,现在我们有:(可以去和上面的反码方式对比一下)
1 | 0 = (0000 0000) |
那我们现在就可以比反码多表示一位了,可以看出(1000 0000)这一种表示方法没有被抢走,所以我们就可以用(1000 0000)表示-128。所以我们能表示的整数范围现在就是[-128,127]。
这就是补码和反码两种存储数据方式。
我们可以用程序实验一下计算机里是如何用补码存储数据的:
1 |
|
程序输出为
1 | 0 -1 0 |