起因
在程式碼 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) | Enter鍵 |
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 種含義,如何解析完全取決於人們約定的規則。如果一個位元組不足以表示所有的範圍、可能性,就用多個位元組表示。