從零開始給女朋友講計算機 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 個 bit 低位到高位每個 bit 分別具有不同的權重,分別代表 1,2,4,8,16,32,64,128。於是通過一個字節 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
舉例:1 0 0 0 0 0 0 0 1
舉例: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

先停一下

看到這里,如果問你,1000 0000 代表一個什么數字,你要怎么回答?千萬別急着回答,回答之前應該先問清楚,要按照什么規則去解析。比如這串 0/1 表示的是一個無符號數還是一個補碼表示的有符號數。

如何表示更大的數?

用多個字節表示。一個字節不夠就兩個,兩個不夠就四個、八個。用 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,其中字符 a-z、A-Z、0-9 的表示在數值上都是連續的。選取部分例子如下:

進制 十進制 十六進制 字符/縮寫 解釋
00000000 0 0x00 NUL (NULL) 空字符
00001010 10 0x0A LF/NL(Line Feed/New Line) 換行鍵
00001101 13 0x0D CR (Carriage Return) 回車鍵
00100000 32 0x20 (Space) 空格
00100001 33 0x21 !
00101100 44 0x2C ,
00101110 46 0x2E .
00110000 48 0x30 0
00110001 49 0x31 1
00110010 50 0x32 2
01000000 64 0x40 @
01000001 65 0x41 A
01000010 66 0x42 B
01000011 67 0x43 C
01011000 88 0x58 X
01011001 89 0x59 Y
01011010 90 0x5A Z
01100001 97 0x61 a
01100010 98 0x62 b
01100011 99 0x63 c
01111000 120 0x78 x
01111001 121 0x79 y
01111010 122 0x7A z
01111111 127 0x7F DEL (Delete) 刪除

如何表示漢字?

一個字節一共就 256 種排列組合,就算每個組合代表一個漢字,也只能表示 256 個漢字,這顯然是不夠的。要想表示一個漢字,至少需要 2 個字節。這樣就有 2^16 = 65536 種排列組合,可以表示 65536 個漢字了,應對常見的漢字已經不成問題。GB2312 編碼就是用兩個字節給漢字編碼的。比如 0xB0A1 代表漢字“啊”,0xD7D3 代表漢字“子”。完整編碼規則這里不詳細展開。

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

每個國家、地區都有自己的編碼方式。比如同樣的一串數字 1011 0000 1010 00010xB0A1 在 GB2312 編碼下代表漢字“啊”,而在某種日文編碼規則中則可能代表一個日文字符。如果一個日本程序員開發了一個軟件,在日文編碼的機器上可以正常顯示日文,但是如果拿到中文編碼的機器上就會顯示亂碼。為解決這一問題,推出了 Unicode 編碼,為全世界的每一個字符都分配了一個獨一無二的編碼,甚至每個 emoji 表情都有自己的編碼!現在只要所有的軟件開發人員都統一采用 Unicode 編碼方式,就再也不會出現亂碼了。Unicode 采用 4 字節編碼,可以表示 2^32 = 4294967295 個字符,足夠容納目前世界上所有已知的字符了。但是如果考慮到將這些字符信息保存下來,比如說保存到硬盤上,新的問題就來了:比如對於一個英文的純文本文件,如何之前按 ASCII 編碼,每個字符只要 1 個字節就夠了,但是現在 Unicode 用 4 個字節編碼,如果每個英文字符都用 4 個字節來存儲(UTF-32),那么文件的大小將變成原來的 4 倍!為了解決這一問題,人們發明了 UTF-8。注意區分 UTF-8 和 Unicode:Unicode 是給每一個字符分配一個編號,從 0 到 4294967295;UTF-8 並不是給字符重新分配編號,每個字符的編號還是 Unicode 中定義、分配的編號,UTF-8 只是想方設法讓 Unicode 在保存、傳輸的過程中減少所需的字節數的一種小技巧/規則,具體細節暫時不在這里展開,后期可能會專門寫一篇文章介紹 UTF-8 是如何減少 Unicode 編碼文件的體積。

總結

計算機的世界由 0/1 組成,數字、字母、圖片等等所有信息都由一串串的 0/1 表示。8 個比特組成一個字節,字節是計算機的基本單位。一個字節可以表示 2^8 = 256 種含義,如何解析完全取決於人們約定的規則,要想知道字節的含義,必須要知道解析的規則(數據類型)。如果一個字節不足以表示所有的范圍、可能性,就用多個字節表示。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM