计算机二进制趣事

计算机使用二进制表示信息

计算机内部是由 IC (Integrated Circuit) 集成电路这种电子部件构成的,每个 IC 内部并排排列着许多引脚,而这些引脚只能表示直流电压 0V 或 5V 两种状态。也就是说 IC 的一个引脚,只能表示两种状态。所以,也决定了计算机的信息数据只能用二进制数来处理。
ic 引脚

二进制和十进制或者八进制、十六进制,本质上没什么区别,只不过我们从开始认识数字起就是十进制,已经习惯了十进制思维。十进制是逢十进一,二进制则是逢二进一。把十进制的 100.00 向右移动一位,变为 10.000,也就是原来的 1/10 倍;100.00 左移一位变为 1000.0,也就是原来的 10 倍。二进制也是同样的道理,左移后变为原来的 2 倍、4 倍、8倍……反之,二进制右移则会变成原来的 1/2、1/4、1/8。

从小学开始学习十进制的时候,我们就知道个、十、百、千、万……,这样按照位权去数数, 个、十、百分别是 10^010^110^2;二进制也是如此,从低位向高位走,位权依次是 2^02^12^2……反方向,向小数位数,则依次是 1/2^1 、1/2^2、1/2^3……

计算机中处理信息的最小单位(bit 缩写于 binary digit),相当于二进制中的一位,二进制数的位数一般是 8 位、16位、32位,也就是 8 的倍数,因为计算机处理信息的基本单位是 8 位二进制数。8 位二进制数被称为一个字节(bite 咬衍生而来),字节是计算机信息的基本单位。

补数、计算机如何实现减法

二进制用最高为表示符号位,0 表示正数,1 表示负数。得到一个负数,要使用二进制的补数。将二进制的各数位全部取反,然后再将结果加 1,就得到了负数。

所以补数的变化方法就是 “取反+1”。对补数再次求补数,得到原来的值,好比负负得正。例如十进制 1 用二进制表示就是 0000 0001, 取反+1 后得到 (1最高位溢出会被忽略)1111 1111,这个数就表示 -1,如果对 1111 1111 再次 取反+1 ,又得到 0000 0001,即 1 。补数的思考方式是 1+(-1) = 0 这一基本运算法则。用 0000 0000 减去 0000 0001 便能得到 1111 1111 (最高位溢出被忽略)。所以一个数的补数与原来的值相加,结果为 0。

有了便于计算的补数,计算机再做减法的时候,就可以转换为加法运算,比如 2 – 3,可以转为为 2 + (-3) ,-3 即 3 的补数,也就是 0000 0010 + 1111 1101 (0000 0011 的补数,表示 -3) ,相加后结果为 1111 1111 ,最高位为 1,表示这个数是负数,根据前边的例子我们知道,这个数表示十进制的 -1。如果不知道一个负数具体是多少,可以先求它的补数,例如 1111 1111 的补数是 0000 0001 (表示 1),因为是原值为负数,所以原值为十进制的 -1。

知道了符号位,我们再做二进制右移运算时,空出的多余高位,全部用符号位补齐即可。

二进制表示小数

计算机在表示小数的时候用的是浮点数,浮点数是指用 符号、尾数、基数、指数这四个部分表示的小数。公式:(+-)m*2^e,除去符号位,m 表示尾数,2是基数(二进制,所以基数是2),e 是指数。浮点数的表示方式很多,最普遍的是 IEEE (美国电气和电子工程师协会 Institute of Electrical and Electronics Engineers) 标准;浮点数又分为单精度(32 位)和双精度(64 位)浮点数。公式:(+-)m*2^e 中,除了基数 2 ,其他的值都需要存储。下面是 IEEE 的浮点数内部结构标准。
* 双精度浮点数 (64 位)
双精度浮点数
* 单精度浮点数 (32 位)
单精度浮点数

符号位比较好存储,剩下的尾数和指数就较为复杂了。

正则表达式来规范尾数部分
先以十进制举例子,0.25 这个十进制小数,我么可以写成 “25 * 10 的 -2 次幂” 或者 “2.5 * 10 的 -1 次幂” ,二进制也可以用同样的办法改写。

计算机中使用的正则表达式规则是:对一个二进制小数进行左移或者右移动,最终将小数点前面的值固定为 1 。例如 1101.0001 需要移位为 “0001.1010001 * 2 的 3 次幂”。这时才取尾数部分为 0001.1010001 。由于小数点的左边第一位固定为 1 了,就无需存储了,这样就多出了一位用来存更多的小数,例如单精度浮点数尾数部分可以存 23 位,实际上可以表示 24 位的数值。

EXCESS 系统表示指数部分
因为指数存在负数的可能,有负数,就需要符号位,本着勤俭节约的优良传统,计算机先辈们不想浪费一位二进制位用来存储符号位,所以就巧妙的使用 EXCESS 系统来表示指数。

具体做法是把指数部分表示的中间值作为 0 ,这样中间值左边的数字用来表示负数,右边的数字用来表示正数。例如扑克牌1~13(A~K),可以把中间值 7 认为是 0 。那么 6 就表示 -1,8表示 +1。这样就不需要符号位也能表示负数了。

将 0.1 累加 100 次后结果并不是 10

使用 C 代码测试以下代码:

#include <stdio.h>

void main() {
    float sum;
    int i;

    sum = 0;

    for (i = 1; i<= 100; i++) {
        sum += 0.1;
    }

    printf("%f\n", sum);
}

结果输出:10.000002,并不是 10;
这个其实不难理解,例如十进制中,无法用有限循环小数表示 1/3 (0.333333……) 一样,二进制也一样,二进制在表示十进制的 0.1的时候会变成 0.00011001100….(1100 循环)的循环小数,计算机的存储是有限的,无法表示无限循环小数。

举个极端的例子,如果计算机只能存储 4 位二进制小数位,那么能表示的小数范围就是 0.0000 ~ 0.1111 ,也就是十进制的 0.5、0.25、0.125、0.0625 这个四个小数以及位权组合而成的其他小数。但是无论如何组合都是能表示的小数都是有限的,这种漏掉的其他小数就无法表示了。

解决上面的问题,我们可以先将 0.1 乘以 10 ,变为 1,然后累计 100 次,再将结果除以 10。除此之外,还有一种 BCD (Binary Coded Decimal) 二进制转换为十进制的方法来解决。

(完)

点赞