好久没接触这三个熟悉而陌生的概念,以前也没理解透彻这三个概念的真正含义与作用,现在来重新做一个清晰而简单的总结。
首先,原码、反码、补码只是机器中对于数字的三种不同的表示形式。下面说的数都默认为整数,关于小数的表示方法在本文中不做探讨。
机器数与真值
在机器中,往往以二进制来表示数字,这个数叫做机器数。为了区分正数和负数,则需要加入一位符号位来表示正负,而符号位便是第一位,其中0表示正数,1表示负数。
+1= [0000 0001]
-1= [1000 0000]
+3=[0000 0011]
-3=[1000 0011]
在上面用8位二进制表示的数中,真正用于表示数值的,其实只有后面7位。因此机器数的形式值(如1000 0011=2^7 + 2^1 + 2^0=131
)不等于机器数的真值(如1000 0011=-000 0011=-2^1+2^0=-3
)。
即带符号位的机器数对应的真正的数值才被称为机器数的真值。
原码、反码、补码
可以看到,如果机器采用机器数进行计算,那么会存在两个明显的问题。其一,是0的表示存在歧义,即存在+0和-0两种表示方法;其二,是同一个数没法通过按位相加得到0,如(+1) + (-1) = [0000 0001] + [1000 0001] = [1000 0010]=-2
。基于以上两个显著问题,提出了使用原码、反码、补码的思想。
原码
原码可以简单理解为上面所说的机器码,即一个符号位加上真值的绝对值。
+1= [0000 0001]
-1= [1000 0000]
+3=[0000 0011]
-3=[1000 0011]
反码
对于正数而言,原码=反码
对于负数而言,反码为保持符号位不变,其余位按位取反。
显然,反码的反码=原码
+3=[0000 0011]原=[0000 0011]反
-3=[1000 0011]原=[1111 1100]反
从下式可以看出,反码已经能使正负数进行正确的加法运算了,然鹅±0的情况依然存在。
(+3) + (-3) = [0000 0011]原 + [1000 0011]原 = [0000 0011]反 + [1111 1100]反 = [1111 1111]反 = [1000 0000]原 = -0
因此,为了解决±0的问题,需要引入补码。
补码
对于正数而言,原码=反码=补码
对于负数而言,补码=反码在最后一位加1
补码的补码=原码
+3=[0000 0011]原=[0000 0011]反=[0000 0011]补
-3=[1000 0011]原=[1111 1100]反=[1111 1101]补
在补码中,只会存在一个0的表示,即[0000 0000]补
,即
(+3) + (-3) = [0000 0011]原 + [1000 0011]原 = [0000 0011]补 +[1111 1101]补 = [0000 0000]补 = [0000 0000]原 = 0
因此补码很好地解决了上述两个问题。同时,由于补码中不存在-0的表示,即[1000 0000]
,因此为了充分利用存储资源,[1000 0000]补
成为了原码和反码中都不存在的一个值,即同样长度的表示中,补码比原码和反码能多表示一个数字。该数字的计算方式有点特殊,可以脑补给它转成原码时添加一位符号位以避免内存溢出(只是这么理解,实际并不会这样),将其进行求补运算以计算其原码,过程如下:
[1000 0000]补 =[1 1000 0000]补 (脑补加一位) = [1 0111 1111]反 = [1 1000 0000]原 = -[1000 0000]原 = -256
需要特别注意的是,上面1000 0000的原码和补码是相同的。
而同样用8位表示的正数,最大值为:
[0111 1111]原 = 255
因此使用补码后,负数会比正数多表示一个数。
C语言中int的最大最小值的十六进制表示
在理解清楚原码、反码、补码的基本知识后,可以看看C语言中的int的表示范围:
maxint = 0x7fffffff;
minint = 0x80000000;
首先要说明的是,在C语言中,或者说在内存中,十六进制的负数是以原码的形式保存的,而十进制的数字是以负数的形式保存的。
其中,maxint
的表示为首位为0,其余全1,这是最大正数。
然而如果说十六进制的负数是原码存储,那么minint
的表示为首位为1,其余全0,也就是前面所说的多出来的一位,即最小值-2147483648。
然鹅细心一点就会提出疑惑,0x8000 0001
并不是-1,而是-2147483647。这么看起来似乎保存的不像是原码,更像是补码,毕竟它的补码是0xfffffffe
,而这个用二进制的计算方法计算其十进制就可以得到-2147483647了。所以可以理解成这也是存储为补码。
不过网上更广泛的解释是,0x8000 0001
是对0x8000 0000
加1,所以是-2147483647。两种解释见仁见智,但结果都是一样的。
参考资料
- 原码,补码和反码
- 关于0x80000000为什么等于-2147483648和负数在内存上储存的问题