数值类型

数值类型

本节主要讨论数值类型在计算机系统中的底层表示。

原码,反码与补码

一个数在计算机中的表现形式叫做机器数,这个数有正负之分,在计算机中用一个数的最高位(符号位)用来表示它的正负,其中0表示正数,1表示负数。例如正数7,在计算机中用一个8位的二进制数来表示,是00000111,而负数-7,则用10000111表示,这里的0000011110000111是机器数。计算机中的机器数对应的真实的值就是真数,对最高位(符号位)后面的二进制数转换成10进制,并根据最高位来确定这个数的正负。对于上面的0000011110000111来说,对最高位后面的二进制数转换成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.
  • 符号位权的总和,符号位权值为2n次方,最高位权值带符号
  • 数字系统的模减去数字本身得出的数字就是补码

从上面的图中发现还有一个 [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:取值01,决定一个数字的符号,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

Links