记得大一时C语言课一开始,老师便介绍了原码、反码、补码以及怎样进行运算,其实也很简单——
正数和0的原码、反码、补码是其本身,
负数的反码由原码按位取反得到,补码则取反后再加1即可,
此外首位作为符号位,0为正,1为负,取反过程中符号位保持不变……
也无非是这些罢了。
但是为什么要有这些码存在呢?
数据又为什么要以补码形式来存储呢?
一个字节为什么只能存储-128~127?
这些疑惑一直堆积在我的心里,于是本文就是近几天我的一点研究成果,这些码都是基于实际存储和运算的需要而诞生的,如果你有相似的疑惑,相信本文能够解决~
数据存储的单位
要了解数据是怎样被存储的,我们首先要了解的是数据容量的一些单位及相互换算关系,从小到大如下:
8bit=1Byte
1024Byte=1KB
1024KB=1MB
1024MB=1GB
1024GB=1TB
1024TB=1PB
1024PB=1EB
更大的单位还有,在此不必列举。
其中,bit(位)是最小单位,每一个位可以用来表示0或1,
计算机中一切数据都是0和1组成的序列。
接下来,抛开你之前学过的零碎的关于原码、反码、补码的相关知识,让我们一起来考虑整型如何存储:
假设我们用8个位来存储整数(全文以8位为例),例如存储正整数1,只需存0000 0001即可,那么它的数据范围即0000 0000~1111 1111(1~255),最大255。
负数如何存储?
但负数怎么存储?由于计算机这个铁憨憨每位只能表示0或者1两种状态,因此我们必须设立一个符号位,存储时将最高位用0表示正数,1表示负数,其他位则用来存储这个数的绝对值。(当然,1为负,0为正也可以,但我们姑且这样设定)
这样一来,正整数的范围为0000 0001~0111 1111(1~127),负整数的范围为1000 0001~1111 1111(-1~-127),此外还有0000 0000(+0),1000 0000(-0)。
将最高位作为符号位后,单是存储整数没问题了,
那让我们来尝试以下计算(大家跟着一起算~),
3+5=0000 0011+0000 0101=0000 1000=8,结果正确。
3-5=3+(-5)=0000 0011+1000 0101=1000 1000=-8 结果错误
问题在哪?
在计算时直接将3和5数据位的值相加,符号位不变,最终结果也就是-8
怎样解决这个问题?
这里我们先给出一个方案:
将-5除了符号位以外所有位取反(1变0 0变1),再与3相加,将得到的结果再次取反。
即3-5=3+(-5)=0000 0011+1000 0101(原)=0000 0011+1111 1010(反)=1111 1101(反)=1000 0010(原)=-2
为什么这样运算能得到正确结果?
-5的数据位原来是000 0101 取反后变为111 1010,实际上是变成了127-5=122
3-5就变成了3+122=125,对125再次取反,变成127-125=2,得到正确结果。
由于111 1111是127,而取反操作 保证了相应数据位之和必为1,因此,取反前后的数据和为127,取反后的值也就是127减去取反前的值。
前后的运算过程可以写成这样一个式子:
3-5=3+(-5)=-[127-(3+127-5)]=-2
在这里,出于计算3-5的需要,我们引入了反码的概念——除了符号位之外全部取反后的码。
当3+5和3-5都解决了,我们不妨再尝试一下5-3,大家可以手算一下,发现当5-3时,由于5+127-3大于127,此时数据会进位到符号位,符号位变成0并进位,由于只用8位数字存储,最前面的1无效,最终结果为0000 0001,显然,这样进行取反结果已经不正确了。
我们观察这里的数字,是实际结果减1。因此我们选择当存储的数字为负数时,对它进行取反再加1,于是乎,5-3=0000 0101+1000 0011(原码)=0000 0101+1111 1101=1 0000 0002 这里的1超出8位无效,因此结果就是2
在这里,出于计算5-3的需要,我们引入了补码的概念——反码加1的码。
这样的话,在计算机中,正数仍然以原码形式存储,只不过最高位作为符号位,负数则以反码加1——即补码的形式存储,当有负数参与运算时,只需要将结果从补码形式转化为原码即可得到结果。
知道了数字以补码形式存储,我们可以得到这样一个补码和数字相对应的表:
在这张图片中淡黄色的那一行是我要重点说明的:
补码从0000 0000~1111 1111,我们容易推算出,除了1000 0000,其他都有相对应的原码。但是1000 0000,由于减1时要向高位借位,但这里数据位全为0,符号位又不能借位,推断不出相应的原码。
那么这个位置是否就没有对应的原码呢?
表示0我们只需要有0000 0000即可,并不需要1000 0000也表示0,事实上,我们可以将它作为一个特殊的补码,来存储-128,从而使得与1000 0001的-127连贯起来,也让补码不至于中间断层。
到这里,我们能自然地理解,8位的存储空间为什么能够存储-128~127(-2^7—— 2^7-1)。 而相应地,如果是16位,相应地存储-2^15——2 ^15-1,32位也是类似。
总结:
出于存储正数和负数的需要,我们引入了符号位的概念;
当x<y时,x-y直接运算是错误的,出于这个需求,我们将负数存为反码;
当x>y时,x-y进行运算由于数据溢出,我们无法取反得到结果,但是发现作差结果只比实际结果小1,因此将负数的反码加1,也就是补码的概念。
从符号位到反码,从反码到补码,这些概念的出现,均是有实际计算的需求而产生的,仅仅背诵计算公式便应了那句——学而不思则罔。