本文主要对整数类型的一些重要或者容易忽略的要点进行总结。
整数
在计算机中,整数可分为无符号和有符号两种。在 C/C++ 中包含了这两种,Java 则只包括了有符号整数。
无符号整数的编码
无符号整数编码的公式如下:
其中 B2U 表示 Binary to Unsigned, w 表示整数数据类型位数,x (上带小箭头)表示该整数表示成二进制数向量,x 表示向量中的具体一位。
如:
有符号整数的编码
对于有符号整数的编码就是补码编码(two’s-complement)。在这里,编码的最高位是符号位,即为 0 是表示整数,为 1 是表示负数。也就该最高位可解释为负权。补码编码公式定义为:
其中 B2T 表示 Binary to Two’s-complement, w 表示整数数据类型位数,x (上带小箭头)表示该整数表示成二进制数向量,x 表示向量中的具体一位。
如:
8 位有符号数可表示范围
我们都知道 8 位有符号数可表示的范围在 -128~127,但对 -128 的由来却很难解释清楚。下边将个人所知几种原因陈列如下:
别浪费
有人就说了,8 位的有符号数除了表示 -127~127,还剩下一个二进制数 0b1000 0000 没有用,这太浪费了,所以用来表示 -128。支持这一说法的还有一书《C++ 反汇编与逆向分析技术揭秘》第 2.1.1 节说的:
对于 4 字节补码,0x8000 0000 所表达的意义可以是负数 0,也可以是 0x8000 0000 减去 1。由于 0 的正负值是相等的,没有必要还来个负数 0,因此,也就把这个值的意义规定为 0x8000 0000 减去 1,这样 0x8000 0000 也就成为 4 字节负数的最小值了。这也是为什么有符号整数的取值范围中,负数区间总是比正数区间多一个最小值的原因。
这种说法很明显是站不住脚的。原因主要有两个:1)既然浪费,那该二进制数也可用来表示除了 -128 之外的数;2)数学是一门严谨的语言,不存在如此模糊的说法(要不如何保证数据运算的正确性?)。
列举法
来自百度文库的一个说法:
1 | 正数:补码跟原码一样 |
按照规律, -127 的补码再往下应该还有补码 “1000 0000”,该二进制数表示 -128。
作者的列举法看起来确实形象了许多,但却仍未能够解释根本的数学原理(为什么补码 “1000 0000”可用于运算而不出问题? )。
个人胡诌的一个说法
这种说法是个人的想法,不过跟说法二一样,都是为了解释而解释。
我们知道 -128 = -127 - 1,那我们就可以通过 -127 和 -1 的相加得到 -128 的补码了。而 -127 的补码为 1000 0001,-1 的补码为 1111 1111,两者相加得到的结果是 1 1000 0000。因为只有 8 位可表示,所以我们可以舍掉补码最高位 1,剩下的就表示 -128 的补码了,也即 -128 的补码为 1000 0000。
这种说法就更加问题多多,而且还牵扯到了溢出问题。
最靠谱的说法
最靠谱的说法一定是来自严格的数学推导或定理。
如果我们回头看一下前边的补码编码公式,我们就很清楚 -128 是怎么来的了。不过这里需要注意的是,是"补码"能够表示 -128,而不是原码能够表示 -128(原码只能表示 -127~127)。
关于有符号数的运算可参考《深入理解计算机系统》一书的 “2.3 节 整数运算”。
Note:
值得注意的是,我们对有符号数参与的运算得格外小心。下边就是一个例子:
-128 加上 127 应该等于多少?我们从上文已经知道 -128 的补码是 1000 0000,而 127 的补码是 0111 1111,那 -128+127 的结果用补码表示就是 1111 1111。在思维没有切换到原码之前,总是将 1111 1111 当做 -127,而没有将其由补码转化为原码,从而导致理解上的错误。所以,得时刻记住下图: