C语言学习(九)整数在内存中是如何存储的?数值溢出的本质是什么?从源头了解奇怪的整数输出问题
目录
关于计算机中的加法和减法,设计计算机时是如何简化硬件电路的?
加法和减法是计算机中最基本的运算,计算机时时刻刻都离不开他们。所以他们由硬件直接支持。为了提高加减法的运算效率,硬件电路要设计的尽量简单。
对于有符号数,人脑很容易区别。但对于计算机来说,内存要区分符号位和数值位,这就要设计专门的电路,增加了硬件的复杂性,增加了计算时间。如果能把符号位和数值位等同起来,让他们一起参与运算,不特意区分他,这样硬件电路就简单多了。
加法和减法可以合并为一种运算,就是加法运算。因为减去一个数相当于加上这个数的相反数,例如,5-3等价于5+(-3),10-(-9)等价于10+9。
相反数指的是数值相同,符号不同的两个数。如,-3和3是一对相反数,-9和9也是一对相反数
如果能实现上面的目标,那么只要设计成一种简单的、不用区分符号位和数值位的加法电路,就能同时实现加法和减法运算,并且高效。实际上这已经实现了,真正的计算机电路就是这么简单。
然而,简化电路是有代价的,这个代价就是有符号数载存储和读取时都要经过一定的转换。那么,这个转换过程是怎样的呢?下面我们来说说。
原码、反码、补码
我们先记住几个概念。
原码
将一个整数转换成二进制形式,这个二进制就称为这个整数的原码。如short a = 6;
,a的原码就是0000 0000 0000 0110
,更改a的值a = -18;
,此时a的原码变成了1000 0000 0001 0010
。原码就是一个整数的二进制表示。
反码
正数和负数的反码不一样。
对于正数,他的反码就是他的原码(原码和反码相同);而负数的反码是将除了符号位以外的所有位取反,也就是0变成1。如short a = 6;
,a的原码和反码都是0000 0000 0000 0110
;如果更改a的值a = -18;
,那么此时a的反码是1111 1111 1110 1101
。
补码
正数和负数的补码也不一样。
对于正数,他的补码就是他的原码(原码、反码、补码都相同);负数的补码是其反码加1。如short a = 6;
,a的原码、反码、补码都是0000 0000 0000 0110
;更改a的值为a = -18;
,此时a的补码是1111 1111 1110 1110
。
总结
可以认为,补码是在反码的基础上打了一个补丁,进行了一个修正,所以叫“补码”。
原码、反码、补码只对负数有实际意义,对于正数,他们都一样。
至于,为什么需要这几个概念,我们将在后文解释。
先让我们总结一下6和-18从原码到补码的转换过程:
另外,有一点很重要。在计算机内存中,整数一律采用补码的形式来存储。这意味着,当读取整数时还要采用逆向的转换,也就是将补码转换成原码。将补码转成原码就是先减1,再将数值位去反。
采用补码存储是如何简化硬件电路的?
我们现在假设6和18都是short类型的(在内存中占有2Byte,也就是16B),先计算6-18的结果,根据我们上面说的,他等价于6+(-18)
如果我们用原码直接计算,那么运算过程为:
6 - 18 = 6 + (-18)
= [0000 0000 0000 0110]原 + [1000 0000 0001 0010]原
= [1000 0000 0001 1000]原
= -24
我们可以看出,用原码表示整数,让符号位也参与运算,对于类似上面的减法来说,结果是不正确的。
经过人们的探索,后来设计出了反码。下面演示了反码的运算过程:
6 - 18 = 6 + (-18)
= [0000 0000 0000 0110]反 + [1111 1111 1110 1101]反
= [1111 1111 1111 0011]反
= [1000 0000 0000 1100]原
= -12
这样计算结果就正确了。
然而这样还不算结束,我们现在将减数和被减数换一下位置,计算一下18-6的结果:
18 - 6 = 18 + (-6)
= [0000 0000 0001 0010]反 + [1111 1111 1111 1001]反
= [1 0000 0000 0000 1011]反
= [0000 0000 0000 1011]反
= 11
按照反码的计算结果是11,而真实结果应该是12,他们相差了1。
注意,蓝色的1是加法运算过程中的进位,他溢出了,内存容纳不了,所以直接截掉
6-18的结果正确,而18-6的结果就不正确,相差1.按照反码来算,是不是小数减大数正确,大数减小数就不对,始终相差1呢?
是这样的,不妨我们再看看其他的例子:
5 - 13 的运算过程为:
5 - 13 = 5 + (-13)
= [0000 0000 0000 0101]原 + [1000 0000 0000 1101]原
= [0000 0000 0000 0101]反 + [1111 1111 1111 0010]反
= [1111 1111 1111 0111]反
= [1000 0000 0000 1000]原
= -8
13 - 5 的运算过程为:
13 - 5 = 13 + (-5)
= [0000 0000 0000 1101]原 + [1000 0000