数值类型
数值类型
本节主要讨论数值类型在计算机系统中的底层表示。
原码,反码与补码
一个数在计算机中的表现形式叫做机器数,这个数有正负之分,在计算机中用一个数的最高位(符号位)用来表示它的正负,其中 0 表示正数,1 表示负数。例如正数 7,在计算机中用一个 8 位的二进制数来表示,是 00000111,而负数-7,则用 10000111 表示,这里的 00000111 和 10000111 是机器数。计算机中的机器数对应的真实的值就是真数,对最高位(符号位)后面的二进制数转换成 10 进制,并根据最高位来确定这个数的正负。对于上面的 00000111 和 10000111 来说,对最高位后面的二进制数转换成 10 进制是 7,在结合最高位的值,得出对应的真数分别是 7 和-1。
原码的编码较为直观,用第一位表示符号,其余位表示值。因为第一位是符号位,所以 8 位二进制数的取值范围就是:[1111_1111, 0111_1111]
即 [-127, 127]
,原码是容易被人脑所理解的表达方式。不过对于计算机来说,加减乘除是最最最基本的运算,要设计的尽量简单,计算机辨别符号位会让计算机的设计电路变得很复杂,于是人们想出了让符号位也参与到运算上来。减去一个数,等于加上他的负数。
原码计算。中可以看见左边每增加一个二进制单位对应的真数是递减的,而右边每增加一个二进制单位对应的真数是递增的,所以对于原码来说,能满足正数的加法,但无法满足负数的加法
2+1=[0000_0010]原+[0000_0001]原=[0000_0011]原=3
1+-1=[0000_00001]原+[1000_0001]原=[1000_0010]原=-2
为了满足负数对加法的需求,就必须让负数与他对应的二进制码是同步递增或者同步递减于是就通过符号位不变,其余位取反来满足这个同步递增或者递减的要求,由于正数本来就满足它本身的加法,所以不需要做任何改变。这就是反码的定义由来。
-2+1=[1111_1101]反+[0000_0001]反=[1111_1110]反=-1
127+1=[1000_0000]反=-127=128
-1+2=[1111_1110]反+[0000_0010]反=[0000_0000]反=0
但是这里有个不合理的地方,就是 [1111_1111]
和 [0000_0000]
都表示 0,这导致在实际计算中每当跨过 0 一次,就有一个单位的误差。要解决这个问题就必须让反码中的 [1111_1111]
和 [0000_0000]
合并,由于 [1111_1111]+[0000_0001]=[0000_0000]
,所以在负数反码的基础上 +1 就可以解决反码中跨 0 的误差问题,同时不会对负数与它对应的二进制反码的同步递增产生影响,所以在反码的基础上 +1 就完美的解决了符号参与预算的问题,这就是补码为什么是在负数反码的基础上 +1 的由来。
补码定义如下:
- 正数的补码为其本身,负数补码除了符号位将所有位置的数字取反,再加 1.
- 符号位权的总和,符号位权值为 2 的 n 次方,最高位权值带符号
- 数字系统的模减去数字本身得出的数字就是补码
从上面的图中发现还有一个 [1000_0000]
的二进制没有对应任何真数,于是就规定了这个数的真数是 -128。所以补码的表示范围是 [-128~127]
,这样一来 256 个二进制正好表示 256 个整数,在实际二进制的运算中超过范围其实就是对 256 的取余预算(x+128)mod 256 - 128
。
定数
浮点类型
计算机系统中直接以二进制形式存储和表达整数,但是对于浮点数而言,计算机本身并不识别小数点,也就导致了无法直接存储浮点数。历史上计算机科学家们曾提出过多种解决方案,最终获得广泛应用的是 IEEE 754 标准中的方案,目前最新版的标准是 IEEE std 754-2008。该标准提出数字系统中的浮点数是对数学中的实数(小数)的近似。
IEEE 754
前文介绍的定点数中的定点指的是约定小数点位置固定不变,然后对数字进行表示。那浮点数的浮点含义也就容易理解了:在对数字进行表示时,小数点的位置可以是漂浮不定的。浮点数采用一种科学计数法的方式表示,如果要表示十进制小数 8.345,用科学计数法表示,可以有多种方式:
8.345 = 8.345 * 10^0
8.345 = 83.45 * 10^-1
8.345 = 834.5 * 10^-2
...
同样,对于二进制数,也可以用科学计数法表示,把基数 10 换成 2 即可。因此我们可以以如下的格式来表示某个浮点数:
X = (-1)^S * M * R^E
其中各变量的含义如下:
- S:取值 0 或 1,决定一个数字的符号,0 表示正,1 表示负
- M:二进制定点小数,表示数字的尾数
- E:二进制定点整数,表示数字的阶码或指数
- R:基数,可以约定为 2、4、16
IEEE 754 规定,对于 32 位的浮点数,最高的 1 位是符号位 S,接着的 8 位是指数 E,剩下的 23 位为有效数字 M。对于 64 位的浮点数,最高的 1 位是符号位 S,接着的 11 位是指数 E,剩下的 52 位为有效数字 M。
为了使得表示的数字范围、精度最大化,浮点数标准还对阶码和尾数进行了规定:
-
尾数的第一位总是 1,因此可在尾数中省略第一位的 1,这个 1 称为隐藏位,使得单精度 23 位尾数表示了 24 位有效数字,双精度 52 位尾数表示了 53 位有效数字。
-
阶码不是用单纯的移码表示,而是在移码的基础上进行偏移修正,因为尾数第一位 1 在隐藏位中,所以阶码的移码需要加 1,即阶码为
(2^n-1)-1
。
当然,虽然规定了阶码和尾数的位数,但阶码和尾数有几种情况,分别表示不同的值:
-
E 不全为 0 或不全为 1。这时,浮点数就采用上面的规则表示,即指数 E 的计算值减去 127(或 1023),得到真实值,再将有效数字 M 前加上第一位的 1。
-
E 全为 0。这时,浮点数的指数 E 等于 1-127(或者 1-1023),有效数字 M 不再加上第一位的 1,而是还原为 0.xxxxxx 的小数。这样做是为了表示 ±0,以及接近于 0 的很小的数字。
-
E 全为 1。这时,如果有效数字 M 全为 0,表示 ± 无穷大(正负取决于符号位 s);如果有效数字 M 不全为 0,表示这个数不是一个数(NaN)。
浮点数的表示
譬如十进制数-0.75 转换成标准的单精度浮点数:
0.75(D) = -0.11(B) = -1.1 * 2^-1
所以符号位 S=1,尾数 M=1.1,阶码 E 的移码修正后为 2^7-1-1=126
。尾数小数点前的 1 为隐藏位,省略不写,尾数二进制表示为 100 0000 0000 0000 0000 0000
,阶码二进制表示为 0111 1110。所以 -0.75
规格化的浮点数表示为 1 0111 1110 100 0000 0000 0000 0000 0000
。
因为浮点数本身是近似表达,譬如 0x00000009
还原成了浮点数之后,就变成了 0.000000
。将 0x00000009
拆分,得到第一位符号位 S=0,后面 8 位的指数 E=00000000,最后 23 位的有效数字 M=000 0000 0000 0000 000001001
。由于指数 E 全为 0,浮点数就写作:
V=(-1)^0×0.00000000000000000001001×2^(-126)=1.001×2^(-146)
由于 V 是一个很小的接近于 0 的正数,所以用十进制小数表示就是 0.000000。