有符号数的算术运算
有符号数的补码表示及其运算可以说是编程最基本的知识,但国内教材普遍使用了错误的教学方法,造成网络上也充斥着错误的认识,增加了学习和使用的复杂度。实际上计算补码和补码算术运算的原理非常简单。
反码的英文是 one’s complement,补码的英文 two’s complement,实际上无论中英,这两个东西的叫法都不准确,因此可以当作专有名词进行认识。同时,补码实际上不存在符号位,任何通过“符号位”来解释补码运算的内容都是错误的。
反码和补码的计算
反码的计算方式很简单,将原码按位取反即可,需要注意的是,原码和反码都是有符号位的,因此存在 +0 和 -0。
补码的标准计算过程如下:
假设(可为正负的)数字 x 可由 N 位的补码表示,那么先计算 2N,然后使用 2N 加上 x,并在溢出时舍弃溢出位,留下 N 位,即可得到补码。
例如对于 4 位数,24 = 1’00002:1 的补码就是用 1’00002 + 12 = 1’00012,然后再舍弃溢出位得到 00012,换句话说正数的补码等于它自己;-1 的补码是 1’00002 - 12 = 11112。
使用取反 + 1 的方式是对标准计算方式的模拟:取反等于用 2N - 1 减去原数,因此负数的补码就等于取反 + 1。
补码的算术移位
在现代计算机和编程语言中,有符号数具有“算术移位”这种运算方式。无符号数的左移和右移非常好理解,就是将所有位按左和右的顺序移位即可,有时候被叫做逻辑移位。而算术移位通常具有非常复杂的解释,但这完全是错误教学导致的。
算术移位和逻辑移位的真相是按照它们的数值进行 2 的幂运算,同时保持负数不变:右移 N 就是除以 2 的 N 次幂,左移 2 就是乘以 2 的 N 次幂。
例如:-2 是 11102,右移 1 就是除以 2,得到 -1,也就是 11112,但有一点特殊的是,-1 右移任意位都是 -1,而不是 0,这就是保持负数不变。注意,左移等于不断乘 2,因此当值大于最大正数时,会发生溢出,溢出的行为是不确定的。
实际实践中,有符号移位非常鸡肋,几乎不考虑它右移保持负数永远为负的性质。