从零开始给女朋友讲计算机 1 – 从比特、字节、补码到 ASCII、GB2312、Unicode

起因

在代码 review 的过程中,总是发现有人在数据类型转换(reinterpret_cast)、大小端等问题(什么情况下需要考虑大小端,什么情况下不需要考虑)上犯错误,究其原因是没有彻彻底底地搞懂数据的二进制表示。我想写篇文章,用通俗易懂的语言把这件事情说明白,通俗易懂到我希望我的小白女友也能看懂。于是我就尝试着先做些铺垫,给她讲了些基础。发现效果出奇的好,于是赶紧把这一过程记录如下。

0 和 1 的世界

计算机的世界只有 0 和 1,所有的数据都由 0 和 1 的组合:数字、字母、汉字、图片、音乐、电影、游戏、网页等都可以由很多的 0 和 1 组成。

计算机如何知道一长串的 0 和 1 是什么含义呢?

比如 0100 0001 可能表示数字 65,可能表示大写字母A,可能和更多的 0 和 1 共同组成一个汉字,也可能表示图片上某个点的颜色,其意义完全取决于人们约定的规则

比特和字节

正着说:每一个 0 和 1 叫做一个比特(bit),8 个比特组成一个字节(Byte)。字节是计算机的基本单位,通常计算机一次最少处理一个字节。
例如:人们常说的一个 Word 文档 100 KB,一张图片 2 MB,一首歌 10 MB,一部电影 4 GB,内存 8 GB,硬盘 512 GB 等等。这里的大“B”就是 Bytes,字节。
比特(bit)最常见于宽带的宣传:例如 500M 宽带的完整单位是 500 Mbps(注意这里是小“b”,不是大“B”)。bps 即 bits per second,500Mbps 指的是每秒最大传输 500 兆比特(bit)。所以 500M 的宽带最快下载速度不是 500 MB/s,而是 500/8 = 62.5 MB/s。
反着再说一次:一个字节(byte)有 8 个比特(bit);每个比特只能是 0 或 1,8 个比特一共有 2^8 = 256 种组合,可以代表 256 种含义(具体含义完全取决于人们约定的规则)。

数字的二进制表示:用 0 和 1 表示数字

首先想到用 8 个比特表示 0-255:人们约定,高位到低位每个 bit 有不同的权重,代表不同的值,如此便可用 8 个 bit 表示 0-255 的所有数字。

高位 -> 低位 bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
权重 128 64 32 16 8 4 2 1
举例:0 0 0 0 0 0 0 0 0
举例:35 0 0 1 0 0 0 1 1
举例:65 0 1 0 0 0 0 0 1
举例:128 1 0 0 0 0 0 0 0
举例:255 1 1 1 1 1 1 1 1

对于这种不考虑负数的情况,我们称之为无符号数

那如何表示一个负数(有符号数)?

有很多种方法,只要约定好一个规则即可。比如我们可以约定,最高位 bit7 代表符号位,0 代表正数,1 代表负数。于是一个字节,8 个 bit 可以表示 -127 ~ 127 的数字。注意其中 0 有两种表示,+0 和 -0。

高位 -> 低位 bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
权重 +/- 64 32 16 8 4 2 1
举例:+0 0 0 0 0 0 0 0 0
举例:-0 1 0 0 0 0 0 0 0
举例:35 0 0 1 0 0 0 1 1
举例:-65 1 1 0 0 0 0 0 1
举例:127 0 1 1 1 1 1 1 1
举例:-127 1 1 1 1 1 1 1 1

现实计算机世界的负数几乎都是补码表示。和无符号数的规则相比,差别仅在最高位的权重为。于是一个字节,8 个 bit 可以表示 -128 ~ 127 的数字。其中 0 只有一种表示。

高位 -> 低位 bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
权重 -128 64 32 16 8 4 2 1
举例:0 0 0 0 0 0 0 0 0
举例:35 0 0 1 0 0 0 1 1
举例:65 0 1 0 0 0 0 0 1
举例:-128 1 0 0 0 0 0 0 0
举例:127 0 1 1 1 1 1 1 1
举例:-127 1 0 0 0 0 0 0 1
举例:-1 1 1 1 1 1 1 1 1

先停一下

看到这里,如果问你,1 0 0 0 0 0 0 0 代表一个什么数字,你要怎么回答?千万别急着回答,回答之前应该先问清楚,要按照什么规则去解析。比如这串 0/1 表示的是一个无符号数还是一个补码表示的有符号数。

如何表示更大的数?比如 10000

用多个字节表示。一个字节不够就两个,两个不够就三个、四个甚至八个十六个,直到够用!用 2 个字节就能够表示 0 – 65535 之间的无符号数,用 4 个字节就能表示 0 – 4294967295 的无符号数!

高位 -> 低位 bit 15 bit 14 bit 13 bit 12 bit 11 bit 10 bit 9 bit 8 bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
权重 32768 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1
举例:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
举例:65 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1
举例:255 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
举例:10000 0 0 1 0 0 1 1 1 0 0 0 1 0 0 0 0
举例:40256 1 0 0 1 1 1 0 1 0 1 0 0 0 0 0 0
举例:60666 1 1 1 0 1 1 0 0 1 1 1 1 1 0 1 0

有符号数(补码)也是类似的,只不过最高位的权重为负。用 2 个字节就能够表示 -32768 到 32767 之间的有符号数,用 4 个字节就能表示 -2147483648 到 2147483647 的有符号数!
直接使用上面的表格(二进制表示的 bit 15 到 bit 0 和上面一模一样),但是现在按照补码的规则进行解析(最高位权重为负),于是得到的结果就不一样了。

高位 -> 低位 bit 15 bit 14 bit 13 bit 12 bit 11 bit 10 bit 9 bit 8 bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
权重 -32768 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1
举例:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
举例:65 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1
举例:255 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
举例:-25280 1 0 0 1 1 1 0 1 0 1 0 0 0 0 0 0
举例:-4870 1 1 1 0 1 1 0 0 1 1 1 1 1 0 1 0

十六进制:二进制的简化表示法

二进制要用 8 个 0/1 表示一个 byte,太不方便,为简化表示,十六进制用分别用一个 0-F 表示一个字节的前 4 位和后 4 位。一般还会加上前缀0x,以提醒读者后面是 16 进制表示法。

如何表示带小数点的数(浮点数)?

还是一样,只要约定好一个规则就行。计算机界流行的浮点数规则是 IEEE 定义单精度浮点(4 字节表示)和双精度浮点(8 字节表示)。具体规则比较复杂,不在这里展开。

如何表示字符?

我们可以约定,0000 0001 代表 a,0000 0002 代表 b,以此类推。从 0000 0000 到 1111 1111 的 256 种组合中表示 a-z、A-Z,加上各种标点符号也是绰绰有余。现实计算机世界几乎都心照不宣地采用 ASCII 规则来表示常见的英文字符、标点以及一些不显示的控制字符等。ASCII 只用了 7 个 bit。

进制 十进制 十六进制 字符/缩写 解释
00000000 0 00 NUL (NULL) 空字符
00001010 10 0A LF/NL(Line Feed/New Line) 换行键
00001101 13 0D CR (Carriage Return) 回车键
00100000 32 20 (Space) 空格
00100001 33 21 !
00101100 44 2C ,
00101110 46 2E .
00110000 48 30 0
00110001 49 31 1
00110010 50 32 2
01000000 64 40 @
01000001 65 41 A
01000010 66 42 B
01000011 67 43 C
01011000 88 58 X
01011001 89 59 Y
01011010 90 5A Z
01100001 97 61 a
01100010 98 62 b
01100011 99 63 c
01111000 120 78 x
01111001 121 79 y
01111010 122 7A z
01111111 127 7F DEL (Delete) 删除

如何表示汉字?

一个字节一共就 256 种排列组合,就算每个组合代表一个汉字,也只能表示 256 个汉字,这显然是不够的。要想表示一个汉字,至少需要 2 个字节。这样就有 2^16 = 65536 种排列组合,可以表示 65536 个汉字了。应对常见的汉字已经不成问题。GB2312 编码就是用两个字节给汉字编码的。具体编码规则网上找得到,这里不详细展开。

如何表示韩文、日文、阿拉伯文等所有字符?

每个国家、地区都有自己的编码方式。比如同样的一串数字 1011 0000 1010 0001 在GB2312 编码下代表汉字“啊”,而在某种日文编码规则中则可能代表一个日文字符。
比如一个日本程序员开发了一个软件,在日文编码的机器上可以正常显示日文,但是如果拿到中文编码的机器上就会显示乱码。为解决这一问题,推出了 Unicode 编码。Unicode 采用 4 字节编码,可以表示 2^32 = 4294967295 个字符,足够容纳目前世界上所有已知的字符了,甚至包括各种 emoji 表情!

总结

计算机的世界由 0/1 组成,数字、字母、图片等等所有信息都由一串串的 0/1 表示。8 个比特组成一个字节,字节是计算机的基本单位。一个字节可以表示 2^8 = 256 种含义,如何解析完全取决于人们约定的规则。如果一个字节不足以表示所有的范围、可能性,就用多个字节表示。