天天看點

《C語言程式設計魔法書:基于C11标準》——2.2 整數在計算機中的表示

本節書摘來自華章計算機《c語言程式設計魔法書:基于c11标準》一書中的第2章,第2.2節,作者 陳轶,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

我們日常用的整數都是十進制數(decimal),也就是我們通常所說的逢十進一。因為我們人類有十根手指,是以自然而然地會想到采用十進制的計數和計算方式。然而,現在幾乎所有計算機都采用二進制數(binary)編碼方式,是以我們日常所用到的整數如果要用計算機來表示的話,需要表示成二進制的方式。

二進制數則是逢二進一,是以在整串數中隻有0和1兩種數字。比如,十進制數0,對應二進制為0;十進制數1,對應二進制數1;十進制數2,對應二進制數10;十進制數3,對應二進制數11。是以,對于非負整數而言,二進制數第n位(n從0開始計)如果是1,那麼就對應十進制數的2n,然後每個位計算得到的十進制數再依次相加得到最終十進制數的值。比如,一個5位二進制數10010,最低位為最右邊的位,記為0号位,數值為0;最高位為最左邊的位,記為4号位,數值為1。那麼它所對應的十進制數為:24+21=18。因為該二進制數除了4号位和1号位為1之外,其餘位都是0,是以0乘以2n肯定為0。圖2-3為二進制數10010換算成十進制數的方法圖。

《C語言程式設計魔法書:基于C11标準》——2.2 整數在計算機中的表示

在計算機術語中,把二進制數中的某一位數又稱為一個比特(bit)。比特這個機關對于計算機而言,在度量上是最小的機關。除了比特之外,還有位元組(byte)這個術語。一個位元組由8個比特構成。在某些單片機架構下還引入了半位元組(nybble或nibble)這個概念,表示4個比特。然後,還有字(word)這個術語。字在不同計算機架構下表示的含義不同。在x86架構下,一個字為2個位元組;而在arm等衆多32位risc體系結構下,一個字表示為4個位元組。随着計算機帶寬的提升,能被處理器一次處理的資料寬度也不斷提升,是以出現了雙字(double word)、四字(quad word)、八字(octa word)等概念。雙字的寬度為2個字,四字寬度為4個字,是以它們在不同處理器體系結構下所占用的位元組個數也會不同。

我們上面介紹了非負整數的二進制表達方法,那麼對于負數,二進制又該如何表達呢?在計算機中有原碼和補碼兩種表示方法,而最為常用的是補碼的表示方法。下面我們分别對原碼和補碼進行介紹。

對于無正負符号的原碼,其二進制表達如上節所述。而對于含有正負符号的原碼,其二進制表示含有一位符号位,用于表示正負号。一般都是以二進制數的最高有效位(即最左邊的比特)作為符号位,其餘各位比特表示該數的絕對值大小。比如,十進制數6用一個8位的原碼表示為0000 0110;如果是-6,則表示為1000 0110。二進制的原碼表示示例如圖2-4所示。

《C語言程式設計魔法書:基于C11标準》——2.2 整數在計算機中的表示

原碼的表示非常直覺,但是對于計算機算術運算而言就帶來了許多麻煩。比如,我們用上述的6與-6相加,即0000 0110+1000 0110,結果為1000 1100,也就是十進制數-12,顯然不是我們想要的結果。是以,如果某個處理器用原碼表示二進制數,那麼它參與加減法的時候必須對兩個操作數的正負符号加以判斷,然後再判定使用加法操作還是減法操作,最後還要判定結果的正負符号,可謂相當麻煩。是以,目前計算機的處理器往往采用補碼的方式來表達帶符号的二進制數。

正由于原碼含有上述缺點,是以人們開發出了另一種帶符号的二進制碼表示法——補碼。補碼與原碼一樣,用最高位比特表示符号位,其餘各位比特則表示數值大小。如果符号位為0,說明整個二進制數為正數或零;如果為1,那麼表示整個二進制數為負數。當符号位為0時,二進制補碼表示法與原碼一模一樣,但是當符号位為負數時,情況就完全不同了。此時,對二進制數的補碼表示需要按以下步驟進行:

1)先将該二進制數以絕對值的原碼形式寫好;

2)對整個二進制數(包括符号位),每一個比特都取反。所謂取反就是說,原來一個比特的數值為0時,則要變1;為1時,則要變0。

變換好之後,将二進制數做加1計算,最終結果就是該負數的補碼值了。

下面我們還是用6來舉例,+6的二進制補碼跟原碼一樣,還是0000 0110。而-6的計算過程,按照上述流程如下:

1)先将-6用絕對值+6的形式表示:0000 0110;

2)對每個比特位取反,包括符号位在内,得到:1111 1001;

3)将變換好的數做加1計算,最終得到:1111 1010。

由于二進制補碼的表示與通常我們可直接讀懂的二進制數的表示有很大不同,是以給定一個二進制補碼,我們往往需要先獲得其絕對值大小才能知道它的具體數值。獲得其絕對值的過程為:先判定符号位,如果符号位為0,那麼就以通常的二進制數表示法來讀即可。如果符号位為1,那麼就以上述同樣的過程得到其對應的絕對值。比如,如果給定1111 1010這個二進制數,我們看到最高位符号位為1,說明是負數,我們就以上述過程來求解:

1)先将該二進制數每個比特做取反計算,得到:0000 0101;

2)然後将變換得到的值做加1計算,最終獲得:0000 0110。

是以1111 1010的絕對值為0000 0110,即6。

對于補碼表示,我們已經知道最高位比特表示符号位,其餘的表示具體數值。但是這裡有一個特殊情況,即符号位為1,其餘位比特為都為0的情況。比如一個8位二進制補碼:1000 0000,此時它的值是多少?因為我們通過上述流程,求得其絕對值的大小也是1000 0000,是以目前大部分計算機處理器的實作将它作為-128,但估計仍然有一些處理器會把它作為-0。因為c語言标準中對于數值範圍的表示已經明确表示出8位帶符号的整數範圍可以是-128到+127,也可以是-127到+127,但最小值不得大于-127,最大值不得小于+127。第5章會有更詳細的描述。

補碼的這種表示法的優點就是可以無視符号位,随意進行算術運算操作。比如,像我們上面所舉的例子:6+(-6),計算結果:

<code>0000 0110+1111 1010=0000 0000</code>

最後,上述計算結果的最高位符号位所産生的進位被丢棄(在處理器中可能會設定相應的進位标志位)。我們自己計算的話也非常友善,在計算過程中,無需關心兩個二進制補碼的正負數的情況,也無需關心符号位所産生的影響。我們隻需要像計算普通二進制數一樣去計算即可。把最終的計算結果拿出來判斷,是正數還是負數。當然,二進制補碼會産生溢出情況,比如兩個8位二進制補碼加法:

<code>120+50=0111 1000+0011 0010=1010 1010</code>

然而,這個數并不是170,而是-86。首先,170已經超出了帶符号8位二進制數可表示的最大範圍了;其次,最高位變為1,用補碼表示來講就是負數表示形式。是以,這兩個正數的加法計算就産生了負數結果,這種現象稱為上溢。如果我們要避免在計算過程中出現上溢情況,需要用更高位寬的二進制數來表示,以提升精度。比如,如果我們将上述加法用16位二進制數表示,那麼就不會有上溢問題了。

另外,在c語言标準中沒有明确規定c語言編譯器的實作以及運作時環境必須采用哪種二進制編碼方式,而是對整數類型标明最大可表示的數值範圍。目前大部分c語言實作都是對帶符号整數采用補碼的表示方式。這些會在第5章做進一步講解。

上面我們對二進制數編碼形式做了比較詳細的介紹。我們在編寫程式或者檢視一些計算機相關的技術文檔時常常還會碰到八進制數與十六進制數的表示,尤其是十六進制數用得非常多。下面我們就簡單介紹一下這兩種基數(radix)的表示方法。

這裡跟各位再分享一個術語——基數。基數也就是我們通常所說的,某一個數用多少進制表達。對于像“01001000是幾進制數”這種話,如果用更專業的表達方式來說的話就是,“01001000的基數是幾”。基數為2就是二進制;基數為10則是十進制。

八進制數是逢八進一,是以每位數的範圍是從0~7。八進制數轉十進制數也很簡單,我們可以用二進制數轉十進制數類似的方法來炮制八進制數轉十進制數——以一個八進制數每位數值作為系數,然後乘以8n,然後計算得到的結果全都相加,最後得到相應的十進制數。其中,n表示目前該位所對應的位置索引(同樣以0開始計)。比如,八進制數5271對應的十進制數的計算過程如圖2-5所示。

《C語言程式設計魔法書:基于C11标準》——2.2 整數在計算機中的表示

八進制數對應于二進制數的話正好占用3個比特(範圍從000~111),一般在通信領域以及資訊加密等領域會用到八進制編碼方式。而十六進制數比八進制數用得更多,因為十六進制數正好占用4個比特,即4位二進制數(範圍從0000~1111)。4個比特相當于半個位元組。是以,無論是開發工具還是程式調試工具,一般都會用十六進制數來表示計算機内部的二進制資料,這樣更易讀,而且也更省顯示空間(因為一個位元組原本需要8位二進制數,而十六進制數隻要兩位即可表示)。下面就介紹一下十六機制數的表示方法。

十六進制數逢十六進一,是以每一位數的範圍是從0到15。由于我們通常在數學上所用的十進制數無法用一位來表示10~15這6個數,因而在計算機領域中,我們通常用英文字母a(或小寫a)來表示10;b(或小寫b)來表示11;c(或小寫c)來表示12;d(或小寫d)來表示13;e(或小寫e)來表示14;f(或小寫f)來表示15。十六機制數轉十進制數的方式與八進制數轉十進制數類似——以一個十六進制數每位數值作為系數,然後乘以16n,然後計算得到的結果全都相加,最後得到相應的十進制數。其中,n表示目前位所對應的位置索引(同樣以0開始計)。比如,一個4位十六進制數c0de的計算過程如圖2-6所示:

《C語言程式設計魔法書:基于C11标準》——2.2 整數在計算機中的表示

上述4位十六進制數c0de,倘若用二進制數表示,則為:1100 0000 1101 1110。可見,用十六進制數表示要簡潔得多,而且換算成十進制數也相對比較容易,尤其對于一個位元組長度的整數來說。為了能更快速地換算二進制數、十進制數與十六進制數,請各位讀者務必熟記下表:

《C語言程式設計魔法書:基于C11标準》——2.2 整數在計算機中的表示

習慣上,用0或0o打頭的數表示八進制數,0x打頭的數表示十六進制數。比如,0123、0777表示八進制數;0x123,0xabcd表示十六進制數。

繼續閱讀