我们知道,计算机内部只能处理 0 和 1,二进制才是计算机的“母语”,我们在计算机里用到的数字、图片、视频,最终都是转换成二进制形式在计算机内进行存储和运算的。
我们现在重点关注数字,无论是正数、负数还是小数,它们是如何用二进制来表示的?
正整数的二进制表示比较直观,比如 5 的二进制是 101
,假设每个正整数用 8 位二进制来表示,就是 0000 0101
。
但负数怎么表示呢?比如 -5?也不能写成 -0000 0101
,因为二进制里只有 0 和 1,并没有 -
这个符号。
最直观的想法是,用一个特殊的位来表示正负号,这个特殊的位也被称为符号位。例如,用最高位作为符号位,比如:0 表示正,1 表示负,这种表示方法就是这个数的原码形式,简称 原码。
例如:
在 C++ 中,一个整数(
int
类型)通常占 4 个字节,而 1 个字节为 8 位,也就是说一个int
整数有 32 位。为了便于讲解,在本章节的讨论中,我们用 8 位二进制来表示。
原码的最大特点是表示简单,易于辨认,一个十进制数转换为原码形式也很方便。
例如,对于数字 13,其二进制表示(8 位)是 0000 1101
,因此
+13
的原码就是 0000 1101
-13
的原码是 1000 1101
虽然原码能表示负数,但它在进行加法运算时会遇到问题。
例如:计算 1 + (-1)
:
0000 0001
1000 0001
0000 0001 + 1000 0001 = 1000 0010
。这个结果的原码是 -2。1 + (-1)
的结果应该是 0,这个结果不对。另一个还有一个问题,原码有两种表示 0 的方式:
0000 0000
1000 0000
这不仅浪费了一个编码空间,而且在判断是否为零时也需要特殊处理。
为了解决原码在加法运算和零表示上的问题,人们引入了反码和补码。
反码是为了解决原码加法运算的问题而引入的过渡编码方式。
例如:
0000 0101
0000 0101
(与原码相同)1000 0101
1111 1010
,符号位为 1(表示负数),其余位取反 (000 0101 -> 111 1010
)0000 0000
1111 1111
(符号位 1 不变,其余 7 位取反)反码的加法规则:将两个数的反码相加,如果最高位(符号位)产生了进位,则将这个进位加到最低位上(循环进位)。
再来计算 1 + (-1)
:
0000 0001
1111 1110
(注意,-1 的原码是 1000 0001
,反码是 1111 1110
)反码按照加法规则相加:
将最高位的进位 1 加到最低位:
这里最终得到的结果是反码,反码是 0000 0000
。这个反码对应的原码是 0000 0000
,表示 0。结果正确!
再来计算一个,-3 + 2
,同样的方法:
1111 1100
(注意,-3 的原码是 1000 0011
,反码是 1111 1100
)0000 0010
反码按照加法规则相加:
反码是 1111 1110
。这个反码对应的原码是 1000 0001
,表示十进制数 -1
。结果正确!
优点:反码的引入解决了使用原码是加法运算需要额外判断符号位的问题,可以直接按位相加。
缺点:
+0
和 -0
两种 0 的表示补码是为了解决上述反码遗留的两个缺点(0 的两种表示和循环进位处理)而引入的。它也是现代计算机中最常见的整数编码方式。
先来看 0 的两种表示的问题。
+0
:补码是 0000 0000
(与原码 / 反码相同)-0
:反码是 1111 1111
,最低位加 1,1111 1111 + 0000 0001 = 1 0000 0000
。由于是 8 位,最高位的进位直接舍弃,结果也是 0000 0000
也就是说,在补码中,+0
与 -0
都表示为 0000 0000
,零的表示现在是唯一的了!
补码的加法规则:将两个数的补码直接按位相加,最高位的进位自然舍弃。
计算 1 + (-1)
:
0000 0001
1111 1111
结果的补码是 0000 0000
,表示 0。结果正确!
计算 5 + (-2)
:
0000 0101
1111 1110
补码相加:
结果的补码是 0000 0011
,表示 +3。结果正确!
A - B
等价于计算 A + (-B)
,而 -B
的补码可以通过 B
的补码(如果是正数,就直接是原码)按位取反再加 1 得到。对于一个 n 位二进制数:
int
)的最大和最小值宏定义的值:输出:
最小值也就是 的结果,最大值也就是 的值。
注意负数的最小值的绝对值比正数的最大值大 1,这是因为 0 占用了正数的一个编码空间,而补码中只有一个 0 的表示。那个额外的负数(如 8 位中的 -128)没有对应的正数原码。
现代计算机中,整数(包括有符号和无符号)都是以补码形式存储和运算的。
理解原码、反码、补码,特别是补码的设计思想,对于理解计算机底层如何进行整数运算非常有帮助。在算法竞赛中,虽然直接手算这些转换不多,但理解它们能帮助你更好地理解位运算、溢出等概念。