深入理解計算機系統(tǒng)(2.4)------整數(shù)的表示(無符號編碼和補碼編碼)
上一篇博客我們主要介紹了布爾代數(shù)和C語言當(dāng)中的幾個運算符。那么這一篇博客我們主要介紹在計算機中整數(shù)是如何表示的,諸如我們在編碼過程中遇到的對數(shù)據(jù)類型進行強制轉(zhuǎn)換可能會得到意想不到的結(jié)果在這篇博客里你會得到解答。
?
1、什么是整數(shù)?
整數(shù)包含正整數(shù),0,負整數(shù)。我們從小的數(shù)學(xué)常識,整數(shù)是無窮無盡的,即整數(shù)的大小沒有限制。
但是在計算機中則不能這樣理解,因為計算機是靠數(shù)字信號來表示數(shù),計算機所能處理的整數(shù)的長度是由計算機的字長來決定的,所以,在計算機中,我們必須制定一個規(guī)則來表示整數(shù)。
?
2、C 語言中的整型數(shù)據(jù)類型
C 語言是支持多種整型數(shù)據(jù)類型的,下面我們看一下在 32 位機器和 64 位機器中,C 語言整型數(shù)據(jù)類型的取值范圍。

?
?

?
? 我們可以看到 :
?、?、C 語言數(shù)據(jù)類型是可以用來指定大小,同時還可以指示表示的數(shù)是非負數(shù)(聲明為 unsigned),或者負數(shù)(默認)。
?、凇?shù)據(jù)類型分配的字節(jié)數(shù)會根據(jù)機器的字長和編譯器有所不同,不同的大小所表示的范圍是不同的。上圖唯一一個與機器有關(guān)的取值范圍是 long 類型的,64位機器使用8個字節(jié)(264),而32位機器使用4個字節(jié)(232)。
?、邸⒇摂?shù)的范圍要比正數(shù)的范圍大1。這是為什么呢,請接著往下面看。
?
下面我們看一下 C 語言標準所定義的每種數(shù)據(jù)類型所能表示的最小的取值范圍。

C 語言標準我們可以從上圖得到:
?、佟⒄龜?shù)和負數(shù)的取值范圍是對稱的。
②、int 數(shù)據(jù)類型可以用 2 個字節(jié)來實現(xiàn)。(216)
?、?、long 數(shù)據(jù)類型用4 個字節(jié)來實現(xiàn)。(232)
?
?
3、無符號數(shù)的編碼
? ? 無符號數(shù),在C語言中,即用 unsigned 聲明的整數(shù)。
定義:假設(shè)對于一個w位的無符號整數(shù),用二進制比特位可以表示為[xw-1?, xw-2?, … , x2?, x1?, x0]。那么我們可以用一個函數(shù)表示如下:
這個函數(shù)可以舉幾個簡單的例子來看:

那么很顯然,對于一個無符號編碼的數(shù),由 w 位的二進制序列構(gòu)成,那么它的最小值,即所有位都為 0 ,用位向量表示即:【000......000】。
UMinw?= 0
最大值即所有位都為 1,用位向量表示即:【111......111】
? UMaxw?= 1 * (1-2w) / 1 - 2 = 2w?- 1?
?
? 我們可以得出一個結(jié)論:無符號的二進制,對于任意一個w位的二進制序列,都存在唯一一個整數(shù)介于0 到?2w-1之間,與這個二進制序列對應(yīng)。反過來,在0 到 2w-1之間的每一個整數(shù),存在唯一的二進制序列與其對應(yīng)。
?
4、補碼編碼?
上面我們講解了正整數(shù)的編碼,那么在實際應(yīng)用中,是存在負數(shù)的。而在計算機中,最常見的表示有符號的數(shù)就是補碼。補碼的定義如下:

其中最高有效位?xw-1 也稱為符號位,符號位為 1 時表示負數(shù),當(dāng)設(shè)置為 0 時,表示非負數(shù)。下面我們看幾個例子:

?
? 那么我們可以得出:當(dāng)最高位為1,其余為全部是 0 的時候,即【1000......000】,表示補碼格式的最小值:
TMinw?= -2w-1?
當(dāng)最高位為 0,其余為全部是 1 時,即【0111......111】,表示補碼格式的最大值:
TMaxw?= 1 * (1 - 2w-1) / 1 - 2 = 2w-1-1
? 通過上面的兩個公式,我們就很好理解為什么上面C語言數(shù)據(jù)類型負數(shù)的范圍要比正數(shù)的范圍大1。
和上面無符號編碼一樣,我們對于補碼格式編碼也可以得到一個結(jié)論:
對于任意一個w位的二進制序列,都存在唯一一個介于-2w-1?到?2w-1-1的整數(shù),與這個二進制序列對應(yīng)。反過來,對于任意介于-2w-1?到?2w-1-1的整數(shù),存在唯一的長度為w二進制序列與其對應(yīng)。
? 那么你就應(yīng)該明白了為什么十進制 -1,在計算機中二進制表示為 1111 1111,而不是1000 0001,因為計算機是以補碼的形式表示的。
?5、反碼和原碼
反碼定義:除了最高有效位的權(quán)是-2w-1-1,而不是-2w-1其余的和補碼表示方式一樣

原碼定義:最高有效位是符號位,用來確定剩下的位是正還是負

我們可以和補碼的定義進行對比:

原碼:一個整數(shù),按照絕對值大小轉(zhuǎn)換為二進制數(shù),最高位為符號位。
反碼:將原碼除最高位(符號位)外,其余各位按位取反,所得到的二進制碼。正數(shù)的反碼為原碼。
補碼:反碼最低位加1即為補碼。
對于正整數(shù),原碼、反碼、補碼完全一樣,即符號位固定為0,數(shù)值位相同。
對于負整數(shù),原碼和補碼互相轉(zhuǎn)換的簡便方法:從數(shù)的右邊往左開始數(shù),遇到“0”不理它,直到遇到第一個“1”為止,以后的每一位數(shù)取反即是它的原碼或補碼,符號位不變,還是“1”(補碼的補碼是原碼)。
比如:11010100 ----- 從右往左數(shù),第一位是0,不理它,第二位還是0不理它,第三位是1,那么從此以后的每位取反,即為它的補碼了.答案為:10101100
事實上,程序員如果希望代碼具有最大的可移植性,能夠在所有可能的機器上運行,就應(yīng)該用補碼的形式來表示有符號整數(shù)。雖然過去生產(chǎn)過基于反碼表示的機器,但是幾乎所有的現(xiàn)代機器都是使用補碼。
注意:浮點數(shù)有使用原碼編碼。
關(guān)于整型數(shù)據(jù)類型的表示和取值范圍,Java標準是非常明確的,它要求采用補碼形式,取值范圍和C語言在64位機器中的情況一樣。在Java中,單字節(jié)數(shù)據(jù)類型稱為 byte,而不是char,而且沒有l(wèi)ong long 數(shù)據(jù)類型。這些具體的要求都是為了保證無論在什么機器上,Java程序運行的表現(xiàn)都能完全一樣。
?
6、有符號和無符號數(shù)之間的轉(zhuǎn)換
在?信息的存儲和表示?這篇博客中我們講過計算機在解釋一個數(shù)據(jù)類型的值時主要有四個因素:位排列規(guī)則(大端或者小端)、起始位置、數(shù)據(jù)類型的字節(jié)數(shù)、數(shù)據(jù)類型的解釋方式。對于特定的系統(tǒng)來說,前兩種因素都是特定的,而對于后兩種因素的改變,則可以改變一個數(shù)據(jù)類型的值的最終計算結(jié)果,這就是強制類型轉(zhuǎn)換。
那么考慮相同整數(shù)類型的無符號編碼和補碼編碼,數(shù)據(jù)類型的大小是沒有任何變化的,變化的就是它們的解釋方式。比如1000這個二進制序列,如果用無符號編碼解釋的話就是表示8,而若采用補碼編碼解釋的話,則是表示-8。
①、有符號數(shù)強轉(zhuǎn)為無符號數(shù)
前面我們說過:無論是無符號編碼還是補碼編碼,其映射方式都是雙射,因此它們都一定存在逆映射。如果我們定義U2Bw(x)為B2Uw(x)的逆映射,則對于任意一個整數(shù)x,如果0 =< x < 2w,經(jīng)過U2Bw(x)的計算之后,將得到唯一一個二進制序列。同樣的,如果我們定義T2Bw(x)為B2Tw(x)的逆映射,則對于任意一個整數(shù)x,如果-2w-1?=< x <?2w-1,經(jīng)過T2Bw(x)的計算之后,也將得到唯一一個二進制序列。
可以很明顯的看出,對于0到2w-1-1這個區(qū)間內(nèi)的整數(shù)來說,兩種編碼得到的二進制序列是一樣的。為了得到其它區(qū)間里的整數(shù)的映射關(guān)系,我們定義:
T2Uw(x)?= B2Uw(T2Bw(x))
這個函數(shù)代表的含義是補碼編碼轉(zhuǎn)換為無符號編碼的時候,先將補碼編碼轉(zhuǎn)換為二進制序列,再將二進制序列轉(zhuǎn)換為無符號編碼,最終也就是補碼編碼轉(zhuǎn)為無符號編碼的計算。
下面我們簡單的推算一下上面的定義,究竟是如何轉(zhuǎn)換的,也就是有符號數(shù)?x?和與之對應(yīng)的無符號數(shù)T2Uw(x)?的關(guān)系。我們將上面無符號編碼和補碼編碼的公式相減,
將0到w-2的位的加權(quán)和互相抵消),即? ? ? ? ? ? ? ? ? ? ? ? ? ? ?B2Uw(x)?- B2Tw(x)?= xw-12w-1?- (-xw-12w-1) = xw-12w?
將等式左邊的B2Tw(x)移到等式右邊,即 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??B2Uw(x) =?xw-12w?+ B2Tw(x)
此處我們令x為T2Bw(x),則 ? ? ? ? ?B2Uw(T2Bw(x)) =?xw-12w?+ B2Tw(T2Bw(x)) =?xw-12w?+ x
即 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?T2Uw(x) =?xw-12w?+ x
此時考慮xw-1的情況,當(dāng)xw-1為1時,也就是補碼編碼表示負數(shù)的時候,T2Uw(x)則為2w?+ x 。(此時x為負數(shù),也就是說2w?+ x < 2w)
若xw-1為0時,則補碼編碼為正數(shù),此時T2Uw(x) = x 。
綜上可知,有下列式子成立

從這個式子中可以很明顯的看出,最終得到的無符號數(shù)范圍為0 =< x < 2w。
下圖為表示補碼編碼與無符號編碼的對應(yīng)關(guān)系,可以看出在0至2w-1-1之間,兩者是相等的,而其余區(qū)間則不同。

? 從上圖我們也可以得出:當(dāng)將一個有符號數(shù)映射為它相應(yīng)的無符號數(shù)時,負數(shù)就被轉(zhuǎn)換成了大的正數(shù);而非負數(shù)會保持不變。
? 這里我們看一個小例子來理解一下:
#include <stdio.h> int main() { char t = 0xFF; unsigned char u = (unsigned char)t; //%d把對應(yīng)的整數(shù)按有符號十進制輸出,%u把對應(yīng)的整數(shù)按無符號十進制輸出 printf("t=%d,t2u=%u\n",t,u); return 0;//c標準規(guī)定建議main函數(shù)返回值為int }
輸出結(jié)果為:

?
? 這個結(jié)果怎么解釋呢,首先有符號char t=0xFFFF。這是因為C語言在64位系統(tǒng)中占用一個字節(jié),轉(zhuǎn)換成二進制數(shù)即:1111 1111,轉(zhuǎn)換為補碼也是:1111 1111,我們套用下面補碼的公式可以得到:

1111 1111的值為 -1。然后根據(jù)我們上面的轉(zhuǎn)換公式:

可以得到轉(zhuǎn)換之后的值為 -1+28=255。也就是上面打印的結(jié)果。
?
②、無符號數(shù)轉(zhuǎn)換為有符號數(shù)
相反,我們用同樣的方式也可以證明從無符號編碼到補碼編碼的公式,我們依然將無符號編碼和補碼編碼的公式相減
? ? ? ? ? ? ?即? ? ? ? ? ? ? ? ? ? ? ? ? ? ??B2Uw(u)?- B2Tw(u)?= uw-12w-1?- (-uw-12w-1) = uw-12w?
? ? ? ? ? ? ?即 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??B2Tw(u)?=?B2Uw(u) -?uw-12w
? ? ? ? ? ? ?此時我們令u為U2Bw(u),則 ? ?B2Tw(U2Bw(u))?=?B2Uw(U2Bw(u)) -?uw-12w?= u -?uw-12w
? ? ? ? ? ? ?即 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??U2Tw(u) =?u -?uw-12w
? ? ? ? ? ? ?此時考慮uw-1的情況,當(dāng)uw-1為0時,也就是無符號編碼數(shù)值小于2w-1的時候,U2Tw(u)則為u 。
? ? ? ? ? ? ?若uw-1為1時,也就是無符號編碼數(shù)值大于或等于2w-1的時候,此時U2Tw(u)= u - 2w。(此時U2Tw(u)為負數(shù),因為?u < 2w)
? ? ? ? ? ? ?綜上,我們可以得到無符號編碼轉(zhuǎn)換為補碼編碼的公式
?

同樣的,在0至2w-1-1之間,兩者依然是相等的,而其余區(qū)間則不同。

還是看一下下面的例子來理解:
#include <stdio.h> int main() { unsigned char u = 0xFF; char t = (char)u; //%d把對應(yīng)的整數(shù)按有符號十進制輸出,%u把對應(yīng)的整數(shù)按無符號十進制輸出 printf("u=%u,u2t=%d\n",u,t); return 0;//c標準規(guī)定建議main函數(shù)返回值為int }
輸出結(jié)果:

?
? 這應(yīng)該很好理解了,無符號 0xFF,即1111 1111,采用的是無符號編碼,第一位不是符號位,那么轉(zhuǎn)換為十進制就是255,然后套用上面的公式:u-2w=255-28=-1
?
7、總結(jié)
本篇博客主要講解了有符號數(shù)和無符號數(shù)之間的轉(zhuǎn)換,我們需要明白它的原理,這篇博客也涉及到很多公式推導(dǎo)證明,LZ也是看了好幾遍才理解這些,大家如果第一遍看不懂也沒關(guān)系,多看幾遍,然后多用筆推導(dǎo)推導(dǎo),還是不難理解的。下一章會介紹C語言中的有符號數(shù)和無符號數(shù)以及擴展和截斷數(shù)字。