计算机中的二进制表示

在计算机中数据运算的本质其实各种门电路的组合,这些门实现了加减乘除运算,这么门的输入和输出都是由0和1组成。由此不难推出,在计算机的世界中是使用0和1存储和计算所有数据的。其实在内存和硬盘中也是如此。据说量子计算机可以维持多种状态,跳出0和1期望可以看到。

数据

数据在计算机中可以分为正整数,负整数,浮点数三种。

正整数

2^{^{0}}
2^{^{31}}
2^{^{32}}-1

为了方便数据表示,计算机中定义多种数据类型,每种数据类型取值范围和占用的内存不同,我们可以根据他们占用的内存来计算出他们的取值范围。比如C语言中unsigned int类型(这里先说无符号类型),占用4个字节,一个字节由8个bit,所以一共有32个bit,很明显:32个比特全为1时值最大,全为0时值最小。所以他的最大值是+….+ = ,最小值则是0。

负整数

在计算机0和1的世界中,没有’-‘号,意味着它没法直接表示一个负数,我们如果想表示一个负数,只能在程序中去体现:比如声明成有符号的数据类型signed int(一般简写为int),这个类型既可以表示负整数也可以表示正整数。这就意味着:在内存中有一段二进制数据,你可以把它当成正整数也可以负数,甚至浮点数。

好了,比如计算机中有这样一段二进制数据:1010,当作正整数我们可以理解成10,或者-10。这里有个问题,如果把1010理解成-10,那么10怎么表示?所以这里负数的表示跟正数略有不同。

为了更好的表示正数和负数,计算机规定:有符号数据的最高位是1则表示负数,为0则表示正数。而具体可以分为原码,反码,补码三种表示法(计算机就是这么令人惊讶)。

原码表示法:最高位为1表示负数,剩余数据的表示与正整数相同。比如01010表示正数10,11010表示负整数-10。这种表示方法最符合我们正常人的思维方式。

(-1)^{x^{w-1}}\cdot \sum_{i=0}^{w-2}{x_{i}}2^{i}

转换成数学表达式:,w最高位数,i表示第几个bit。

反码表示法:最高位为1表示负数,剩余的数据按照正整数的值按位取反,即:最高位1,然后对剩下的1010按位取反,得到0101,然后组合起来:10101。

-x_{w-1}\cdot (2^{w-1}-1)+\sum_{i=0}^{w-2}x_{i}2^{i}

转换成数学表达式:

看到这里有个问题,这两种表达方式式有点瑕疵:0的表示都有两种,原码表示法中10000(负)和00000(正)都表示0,反码表示法中,11111(负)和000000(正)都表示0(这里先假设数据只有5bit,如果是类型则是32个1),这样会浪费一个字节,而且也不利于计算。(注意无论是什么表示,正数的表达都是一样的),所以计算机出现了第三中表示方法。

补码表示法:最高位表示负数,剩余数据按照正整数的值按位取反,再加1,这同时也是反码转补码的方法。即:-10转成二进制是10110。

-x_{w-1}\cdot 2^{w-1}+\sum_{i=0}^{w-2}x_{i}2^{i}

转换成数学表达式:

所以我们看到,补码表示法中11111表示-1,00000则表示0,这样负数就没办法表示0了,所以补码表示法中,所有能表示负数的个数要不正整数多一个,因为正整数中有一个00000被当做0了呀。比如int的取值范围:-2147483648 到 2147483647,现在计算机普遍使用补码表示法。

浮点数类似整数的表示法

浮点数即小数,延续负整数的思路,我们假设计算机中的小数用二进制表示的话可以让某一bit代表小数点,比如3.14可以表示成1101110,其中红色的零表示小数点,如果计算机采用这种方式的话,那么他的表示范围是多少呢?为了不浪费,我们规定前16bit是整数,后16bit是小数,以无符号int型为例,最大值是65535.65535。最大精度到小数点后五位。

定点表示法

2^{w}
2^{-w}
\frac{1}{2}+\frac{1}{4}=\frac{3}{4}
\frac{1}{^{2^{16}}}=0.0000152587890625

然而实际中计算机采用的是另一种更为巧妙表示方法,同样的假设前16bit是整数部分,后16bit是小数部分,整数部分的计算方法不变,小数部分计算方法由改成,即小数部分的11表示,即0.75,以此类推,最小精度是,这个精度显然要高的多了!

由此可见在计算机中其实小数使用分数来表示的,而且是可以除尽的分数,因为分母都是偶数。而且小数最后一位肯定都是5(因为分母都是偶数),那么问题来了3.14怎么表示?整数3不变,用11表示,0.14可以人肉计算一下,大概是1/8 + 1/64,即001001,转换成10进制就是0.140625,四舍五入,约等于3.14,要是把小数部分的16bit全用上,会出现更加接近的值。有里就有弊,有了足够高的精度,但是却牺牲了表示的准确性。

IEEE表示法

(-1)^{S}\cdot M\cdot2 ^{E}
(-1)^{S}\cdot M\cdot 2^{E}

IEEE是IEEE为了统一表示标准而制定的。他用三个元素来表示浮点数:

符号(S):1表示负数,0表示正数,这一点和上面类似。

尾数(M):表示二进制小数。

阶码(E):表示浮点数的权重。

浮点数被IEEE定义为三种类型:

规格化的值:我们日常用到的小数值,阶码不能全为0或1

非规格化的值:正负0,阶码全为0

特殊值:正负无穷大,以及无理数,阶码全为1

看到这应该是一头雾水,以float类型规格化的值举例,他的符号位占1位,阶码占8位,尾数占23位,这次以-3.14为例:

符号位:很明显符号位是1

2^{8-1}-1
2^{8-1}-1

阶码:阶码实际是2的次幂,因为是规格化的数,所以8位取值是1-254,而阶码被规定可以是负数,所以IEEE就对他做了偏移运算,即11111110(十进制254)表示的是254-()=127,那么00000001(十进制1)表示的是1-()= -126,那么8位阶码的取值范围是(-126,127),很明显这里阶码只能是1,再大就会溢出,那么阶码的值就是1+127=128(二进制10000000),

(-1)^{1}\cdot M\cdot2 ^{1}

到这里可以看到目前的值是这样的:,剩下就是M的值了。

尾数:我门直接人肉计算,M=3.14/2=1.57,转换二进制约等于1/2 + 1/16 + 1/32=0.5675,即110011,红1表示整数1,后面的表示小数,因为尾数默认是加1的,所以应该是1011.

那么完整的-3.14二进制就是1(符号位)   10000000(阶码)   10011000000000000000000(尾数)

参考:<深入理解计算机系统>