1. 程式人生 > >程式設計師需要了解的硬核知識之二進位制

程式設計師需要了解的硬核知識之二進位制

我們都知道,計算機的底層都是使用二進位制資料進行資料流傳輸的,那麼為什麼會使用二進位制表示計算機呢?或者說,什麼是二進位制數呢?在拓展一步,如何使用二進位制進行加減乘除?二進位制數如何表示負數呢?本文將一一為你揭曉。 ## 為什麼用二進位制表示 我們大家知道,計算機內部是由IC電子元件組成的,其中 `CPU` 和 `記憶體` 也是 IC 電子元件的一種,CPU和記憶體圖如下 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163815398-1802828392.png) ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163824554-2058905504.png) CPU 和 記憶體使用IC電子元件作為基本單元,IC電子元件有不同種形狀,但是其內部的組成單元稱為一個個的引腳。有人說CPU 和 記憶體內部都是超大規模積體電路,其實IC 就是積體電路(Integrated Circuit)。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163832624-169033412.png) IC元件兩側排列的四方形塊就是引腳,IC的所有引腳,只有兩種電壓: `0V` 和 `5V`,IC的這種特性,也就決定了計算機的資訊處理只能用 0 和 1 表示,也就是二進位制來處理。一個引腳可以表示一個 0 或 1 ,所以二進位制的表示方式就變成 0、1、10、11、100、101等,雖然二進位制數並不是專門為 引腳 來設計的,但是和 IC引腳的特性非常吻合。 計算機的最小整合單位為 `位`,也就是 `位元(bit)`,二進位制數的位數一般為 8位、16位、32位、64位,也就是 8 的倍數,為什麼要跟 8 扯上關係呢? 因為在計算機中,把 8 位二進位制數稱為 **一個位元組**, 一個位元組有 8 位,也就是由 8個bit構成。 >為什麼1個位元組等於8位呢?因為 8 位能夠涵蓋所有的字元編碼,這個記住就可以了。 位元組是最基本的計量單位,位是最小單位。 用位元組處理資料時,如果數字小於儲存資料的位元組數 ( = 二進位制的位數),那麼高位就用 0 填補,高位和數學的數字表示是一樣的,**左側表示高位,右側表示低位。**比如 這個六位數用二進位制數來表示就是 `100111`,只有6位,高位需要用 0 填充,填充完後是 `00100111`,佔一個位元組,如果用 16 位表示 就是 `0000 0000 0010 0111`佔用兩個位元組。 我們一般口述的 32 位和 64位的計算機一般就指的是處理位數,32 位一次可以表示 4個位元組,64位一次可以表示8個位元組的二進位制數。 我們一般在軟體開發中用十進位制數表示的邏輯運算等,也會被計算機轉換為二進位制數處理。對於二進位制數,計算機不會區分他是 圖片、音訊檔案還是數字,這些都是一些資料的結合體。 ## 什麼是二進位制數 那麼什麼是二進位制數呢?為了說明這個問題,我們先把 `00100111` 這個數轉換為十進位制數看一下,二進位制數轉換為十進位制數,直接將各位置上的值 * 位權即可,那麼我們將上面的數值進行轉換 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163842261-28059622.png) 也就是說,二進位制數代表的 `00100111` 轉換成十進位制就是 39,這個 39 並不是 3 和 9 兩個數字連著寫,而是 3 * 10 + 9 * 1,這裡面的 `10 , 1` 就是位權,以此類推,上述例子中的位權從高位到低位依次就是 `7 6 5 4 3 2 1 0 `。這個位權也叫做次冪,那麼最高位就是2的7次冪,2的6次冪 等等。二進位制數的運算每次都會以2為底,這個2 指得就是基數,那麼十進位制數的基數也就是 10 。在任何情況下位權的值都是 **數的位數 - 1**,那麼第一位的位權就是 1 - 1 = 0, 第二位的位權就睡 2 - 1 = 1,以此類推。 那麼我們所說的二進位制數其實就是 用0和1兩個數字來表示的數,它的基數為2,它的數值就是每個數的位數 * 位權再求和得到的結果,我們一般來說數值指的就是十進位制數,那麼它的數值就是 3 * 10 + 9 * 1 = 39。 ## 移位運算和乘除的關係 在瞭解過二進位制之後,下面我們來看一下二進位制的運算,和十進位制數一樣,加減乘除也適用於二進位制數,只要注意逢 2 進位即可。二進位制數的運算,也是計算機程式所特有的運算,因此瞭解二進位制的運算是必須要掌握的。 首先我們來介紹`移位` 運算,移位運算是指將二進位制的數值的各個位置上的元素坐左移和右移操作,見下圖 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163849982-594237539.png) 上述例子中還是以 39 為例,我們先把十進位制的39 轉換為二進位制的 `0010 0111`,然後`向左移位 << `一個位元組,也就變成了 `0100 1110`,那麼再把此二進位制數轉換為十進位制數就是上面的78, 十進位制的78 竟然是 十進位制39 的2倍關係。我們在讓 `0010 0111` 左移兩位,也就是 `1001 1100`,得出來的值是 156,相當於擴大了四倍! 因此你可以得出來此結論,左移相當於是數值擴大的操作,那麼`右移 >> `呢?按理說右移應該是縮小 1/2,1/4 倍,但是39 縮小二倍和四倍不就變成小數了嗎?這個怎麼表示呢?請看下一節 ## 便於計算機處理的補數 剛才我們沒有介紹右移的情況,是因為右移之後空出來的高位數值,有 0 和 1 兩種形式。要想區分什麼時候補0什麼時候補1,首先就需要掌握二進位制數表示`負數`的方法。 **二進位制數中表示負數值時,一般會把最高位作為符號來使用,因此我們把這個最高位當作符號位。** 符號位是 0 時表示`正數`,是 1 時表示 `負數`。那麼 -1 用二進位制數該如何表示呢?可能很多人會這麼認為: 因為 1 的二進位制數是 `0000 0001`,最高位是符號位,所以正確的表示 -1 應該是 `1000 0001`,但是這個答案真的對嗎? 計算機世界中是沒有減法的,計算機在做減法的時候其實就是在做加法,也就是用加法來實現的減法運算。比如 100 - 50 ,其實計算機來看的時候應該是 100 + (-50),為此,在表示負數的時候就要用到`二進位制補數`,補數就是用正數來表示的負數。 為了獲得`補數`,我們需要將二進位制的各數位的數值全部取反,然後再將結果 + 1 即可,先記住這個結論,下面我們來演示一下。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163900863-114578815.png) 具體來說,就是需要先獲取某個數值的二進位制數,然後對二進位制數的每一位做取反操作(0 ---> 1 , 1 ---> 0),最後再對取反後的數 +1 ,這樣就完成了補數的獲取。 補數的獲取,雖然直觀上不易理解,但是邏輯上卻非常嚴謹,比如我們來看一下 1 - 1 的這個過程,我們先用上面的這個 `1000 0001`(它是1的補數,不知道的請看上文,正確性先不管,只是用來做一下計算)來表示一下 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163930856-1999596436.png) 奇怪,1 - 1 會變成 130 ,而不是0,所以可以得出結論 `1000 0001` 表示 -1 是完全錯誤的。 那麼正確的該如何表示呢?其實我們上面已經給出結果了,那就是 `1111 1111`,來論證一下它的正確性 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163938569-1581862956.png) 我們可以看到 1 - 1 其實實際上就是 1 + (-1),對 -1 進行上面的取反 + 1 後變為 `1111 1111`, 然後與 1 進行加法運算,得到的結果是九位的 `1 0000 0000`,結果發生了`溢位`,計算機會直接忽略掉溢位位,也就是直接拋掉 最高位 1 ,變為 `0000 0000`。也就是 0,結果正確,所以 `1111 1111` 表示的就是 -1 。 **所以負數的二進位制表示就是先求其補數,補數的求解過程就是對原始數值的二進位制數各位取反,然後將結果 + 1**, 當然,結果不為 0 的運算同樣也可以通過補數求得正確的結果。不過,有一點需要注意,當運算結果為負的時候,計算結果的值也是以補數的形式出現的,比如 3 - 5 這個運算,來看一下解析過程 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163946029-1921276339.png) 3 - 5 的運算,我們按著上面的思路來過一遍,計算出來的結果是 `1111 1110`,我們知道,這個數值肯定表示負數,但是負數無法直接用十進位制表示,需要對其取反+ 1,算出來的結果是 2,因為 `1111 1110`的高位是 1,所以最終的結果是 -2。 程式語言的資料型別中,有的可以處理負數,有的不可以。比如 C語言中不能處理負數的 `unsigned short`型別,也有能處理負數的`short`型別 ,都是兩個位元組的變數,它們都有 2 的十六次冪種值,但是取值範圍不一樣,short 型別的取值範圍是 -32768 - 32767 , unsigned short 的取值範圍是 0 - 65536。 仔細思考一下補數的機制,就能明白 -32768 比 32767 多一個數的原因了,最高位是 0 的正數有 0 ~ 32767 共 32768 個,其中包括0。最高位是 1 的負數,有 -1 ~ -32768 共 32768 個,其中不包含0。0 雖然既不是正數也不是負數,但是考慮到其符號位,就將其歸為了正數。 ## 算數右移和邏輯右移的區別 在瞭解完補數後,我們重新考慮一下右移這個議題,右移在移位後空出來的最高位有兩種情況 `0 和 1`。當二進位制數的值表示圖形模式而非數值時,移位後需要在最高位補0,類似於霓虹燈向右平移的效果,這就被稱為`邏輯右移`。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606163959264-216067487.png) 將二進位制數作為帶符號的數值進行右移運算時,移位後需要在最高位填充移位前符號位的值( 0 或 1)。這就被稱為`算數右移`。如果數值使用補數表示的負數值,那麼右移後在空出來的最高位補 1,就可以正確的表示 `1/2,1/4,1/8`等的數值運算。如果是正數,那麼直接在空出來的位置補 0 即可。 下面來看一個右移的例子。將 -4 右移兩位,來各自看一下移位示意圖 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606164008196-1637727849.png) 如上圖所示,在邏輯右移的情況下, -4 右移兩位會變成 `63`, 顯然不是它的 1/4,所以不能使用邏輯右移,那麼算數右移的情況下,右移兩位會變為 `-1`,顯然是它的 1/4,故而採用算數右移。 那麼我們可以得出來一個結論:**左移時,無論是圖形還是數值,移位後,只需要將低位補 0 即可;右移時,需要根據情況判斷是邏輯右移還是算數右移。** 下面介紹一下符號擴充套件:**將資料進行符號擴充套件是為了產生一個位數加倍、但數值大小不變的結果,以滿足有些指令對運算元位數的要求,例如倍長於除數的被除數,再如將資料位數加長以減少計算過程中的誤差。** 以8位二進位制為例,符號擴充套件就是指在保持值不變的前提下將其轉換成為16位和32位的二進位制數。將`0111 1111`這個正的 8位二進位制數轉換成為 16位二進位制數時,很容易就能夠得出`0000 0000 0111 1111`這個正確的結果,但是像 `1111 1111`這樣的補數來表示的數值,該如何處理?直接將其表示成為`1111 1111 1111 1111`就可以了。也就是說,不管正數還是補數表示的負數,只需要將 0 和 1 填充高位即可。 ## 邏輯運算的竅門 掌握邏輯和運算的區別是:將二進位制數表示的資訊作為四則運算的數值來處理就是`算數`,像圖形那樣,將數值處理為單純的 `0` 和 `1` 的羅列就是`邏輯` 計算機能夠處理的運算,大體可分為邏輯運算和算數運算,`算數運算`指的是加減乘除四則運算;`邏輯運算`指的是對二進位制各個數位的 0 和 1分別進行處理的運算,包括**邏輯非(NOT運算)、邏輯與(AND運算)、邏輯或(OR運算)和邏輯異或(XOR運算)**四種。 * `邏輯非` 指的是將 0 變成 1,1 變成 0 的取反操作 * `邏輯與` 指的是"兩個都是 1 時,運算結果才是 1,其他情況下是 0" * `邏輯或` 指的是"至少有一方是 1 時,運算結果為 1,其他情況下運算結果都是 0" * `邏輯異或` 指的是 "其中一方是 1,另一方是 0時運算結果才是 1,其他情況下是 0" ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606164016449-652453787.png) 掌握邏輯運算的竅門,就是要摒棄二進位制數表示數值這一個想法。大家不要把二進位制數表示的值當作數值,應該把它看成是 開關上的 `ON/OFF`。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200606164033487-18424219