上一篇博客咱們主要介紹了布爾代數和C語言當中的幾個運算符。那麼這一篇博客咱們主要介紹在計算機中整數是如何表示的,諸如咱們在編碼過程當中遇到的對數據類型進行強制轉換可能會獲得意想不到的結果在這篇博客裏你會獲得解答。html
整數包含正整數,0,負整數。咱們從小的數學常識,整數是無窮無盡的,即整數的大小沒有限制。java
可是在計算機中則不能這樣理解,由於計算機是靠數字信號來表示數,計算機所能處理的整數的長度是由計算機的字長來決定的,因此,在計算機中,咱們必須制定一個規則來表示整數。程序員
C 語言是支持多種整型數據類型的,下面咱們看一下在 32 位機器和 64 位機器中,C 語言整型數據類型的取值範圍。函數
咱們能夠看到 :編碼
①、C 語言數據類型是能夠用來指定大小,同時還能夠指示表示的數是非負數(聲明爲 unsigned),或者負數(默認)。spa
②、數據類型分配的字節數會根據機器的字長和編譯器有所不一樣,不一樣的大小所表示的範圍是不一樣的。上圖惟一一個與機器有關的取值範圍是 long 類型的,64位機器使用8個字節(264),而32位機器使用4個字節(232)。3d
③、負數的範圍要比正數的範圍大1。這是爲何呢,請接着往下面看。htm
下面咱們看一下 C 語言標準所定義的每種數據類型所能表示的最小的取值範圍。blog
C 語言標準咱們能夠從上圖獲得:get
①、正數和負數的取值範圍是對稱的。
②、int 數據類型能夠用 2 個字節來實現。(216)
③、long 數據類型用4 個字節來實現。(232)
無符號數,在C語言中,即用 unsigned 聲明的整數。
定義:假設對於一個w位的無符號整數,用二進制比特位能夠表示爲[xw-1 , xw-2 , … , x2 , x1 , x0]。那麼咱們能夠用一個函數表示以下:
這個函數能夠舉幾個簡單的例子來看:
那麼很顯然,對於一個無符號編碼的數,由 w 位的二進制序列構成,那麼它的最小值,即全部位都爲 0 ,用位向量表示即:【000......000】。
UMinw = 0
最大值即全部位都爲 1,用位向量表示即:【111......111】
UMaxw = 1 * (1-2w) / 1 - 2 = 2w - 1
咱們能夠得出一個結論:無符號的二進制,對於任意一個w位的二進制序列,都存在惟一一個整數介於0 到 2w-1之間,與這個二進制序列對應。反過來,在0 到 2w-1之間的每個整數,存在惟一的二進制序列與其對應。
上面咱們講解了正整數的編碼,那麼在實際應用中,是存在負數的。而在計算機中,最多見的表示有符號的數就是補碼。補碼的定義以下:
其中最高有效位 xw-1 也稱爲符號位,符號位爲 1 時表示負數,當設置爲 0 時,表示非負數。下面咱們看幾個例子:
那麼咱們能夠得出:當最高位爲1,其他爲所有是 0 的時候,即【1000......000】,表示補碼格式的最小值:
TMinw = -2w-1
當最高位爲 0,其他爲所有是 1 時,即【0111......111】,表示補碼格式的最大值:
TMaxw = 1 * (1 - 2w-1) / 1 - 2 = 2w-1-1
經過上面的兩個公式,咱們就很好理解爲何上面C語言數據類型負數的範圍要比正數的範圍大1。
和上面無符號編碼同樣,咱們對於補碼格式編碼也能夠獲得一個結論:
對於任意一個w位的二進制序列,都存在惟一一個介於-2w-1 到 2w-1-1的整數,與這個二進制序列對應。反過來,對於任意介於-2w-1 到 2w-1-1的整數,存在惟一的長度爲w二進制序列與其對應。
那麼你就應該明白了爲何十進制 -1,在計算機中二進制表示爲 1111 1111,而不是1000 0001,由於計算機是以補碼的形式表示的。
反碼定義:除了最高有效位的權是-2w-1-1,而不是-2w-1其他的和補碼錶示方式同樣
原碼定義:最高有效位是符號位,用來肯定剩下的位是正仍是負
咱們能夠和補碼的定義進行對比:
原碼:一個整數,按照絕對值大小轉換爲二進制數,最高位爲符號位。
反碼:將原碼除最高位(符號位)外,其他各位按位取反,所獲得的二進制碼。正數的反碼爲原碼。
補碼:反碼最低位加1即爲補碼。
對於正整數,原碼、反碼、補碼徹底同樣,即符號位固定爲0,數值位相同。
對於負整數,原碼和補碼互相轉換的簡便方法:從數的右邊往左開始數,遇到「0」不理它,直到遇到第一個「1」爲止,之後的每一位數取反便是它的原碼或補碼,符號位不變,仍是「1」(補碼的補碼是原碼)。
好比:11010100 ----- 從右往左數,第一位是0,不理它,第二位仍是0不理它,第三位是1,那麼今後之後的每位取反,即爲它的補碼了.答案爲:10101100
事實上,程序員若是但願代碼具備最大的可移植性,可以在全部可能的機器上運行,就應該用補碼的形式來表示有符號整數。雖然過去生產過基於反碼錶示的機器,可是幾乎全部的現代機器都是使用補碼。
注意:浮點數有使用原碼編碼。
關於整型數據類型的表示和取值範圍,Java標準是很是明確的,它要求採用補碼形式,取值範圍和C語言在64位機器中的狀況同樣。在Java中,單字節數據類型稱爲 byte,而不是char,並且沒有long long 數據類型。這些具體的要求都是爲了保證不管在什麼機器上,Java程序運行的表現都能徹底同樣。
在 信息的存儲和表示 這篇博客中咱們講過計算機在解釋一個數據類型的值時主要有四個因素:位排列規則(大端或者小端)、起始位置、數據類型的字節數、數據類型的解釋方式。對於特定的系統來講,前兩種因素都是特定的,而對於後兩種因素的改變,則能夠改變一個數據類型的值的最終計算結果,這就是強制類型轉換。
那麼考慮相同整數類型的無符號編碼和補碼編碼,數據類型的大小是沒有任何變化的,變化的就是它們的解釋方式。好比1000這個二進制序列,若是用無符號編碼解釋的話就是表示8,而若採用補碼編碼解釋的話,則是表示-8。
①、有符號數強轉爲無符號數
前面咱們說過:不管是無符號編碼仍是補碼編碼,其映射方式都是雙射,所以它們都必定存在逆映射。若是咱們定義U2Bw(x)爲B2Uw(x)的逆映射,則對於任意一個整數x,若是0 =< x < 2w,通過U2Bw(x)的計算以後,將獲得惟一一個二進制序列。一樣的,若是咱們定義T2Bw(x)爲B2Tw(x)的逆映射,則對於任意一個整數x,若是-2w-1 =< x < 2w-1,通過T2Bw(x)的計算以後,也將獲得惟一一個二進制序列。
能夠很明顯的看出,對於0到2w-1-1這個區間內的整數來講,兩種編碼獲得的二進制序列是同樣的。爲了獲得其它區間裏的整數的映射關係,咱們定義:
T2Uw(x) = B2Uw(T2Bw(x))
這個函數表明的含義是補碼編碼轉換爲無符號編碼的時候,先將補碼編碼轉換爲二進制序列,再將二進制序列轉換爲無符號編碼,最終也就是補碼編碼轉爲無符號編碼的計算。
下面咱們簡單的推算一下上面的定義,到底是如何轉換的,也就是有符號數 x 和與之對應的無符號數T2Uw(x) 的關係。咱們將上面無符號編碼和補碼編碼的公式相減,
將0到w-2的位的加權和互相抵消),即 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的狀況,當xw-1爲1時,也就是補碼編碼表示負數的時候,T2Uw(x)則爲2w + x 。(此時x爲負數,也就是說2w + x < 2w)
若xw-1爲0時,則補碼編碼爲正數,此時T2Uw(x) = x 。
綜上可知,有下列式子成立
從這個式子中能夠很明顯的看出,最終獲得的無符號數範圍爲0 =< x < 2w。
下圖爲表示補碼編碼與無符號編碼的對應關係,能夠看出在0至2w-1-1之間,二者是相等的,而其他區間則不一樣。
從上圖咱們也能夠得出:當將一個有符號數映射爲它相應的無符號數時,負數就被轉換成了大的正數;而非負數會保持不變。
這裏咱們看一個小例子來理解一下:
#include <stdio.h> int main() { char t = 0xFF; unsigned char u = (unsigned char)t; //%d把對應的整數按有符號十進制輸出,%u把對應的整數按無符號十進制輸出 printf("t=%d,t2u=%u\n",t,u); return 0;//c標準規定建議main函數返回值爲int }
輸出結果爲:
這個結果怎麼解釋呢,首先有符號char t=0xFFFF。這是由於C語言在64位系統中佔用一個字節,轉換成二進制數即:1111 1111,轉換爲補碼也是:1111 1111,咱們套用下面補碼的公式能夠獲得:
1111 1111的值爲 -1。而後根據咱們上面的轉換公式:
能夠獲得轉換以後的值爲 -1+28=255。也就是上面打印的結果。
②、無符號數轉換爲有符號數
相反,咱們用一樣的方式也能夠證實從無符號編碼到補碼編碼的公式,咱們依然將無符號編碼和補碼編碼的公式相減
即 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的狀況,當uw-1爲0時,也就是無符號編碼數值小於2w-1的時候,U2Tw(u)則爲u 。
若uw-1爲1時,也就是無符號編碼數值大於或等於2w-1的時候,此時U2Tw(u)= u - 2w。(此時U2Tw(u)爲負數,由於 u < 2w)
綜上,咱們能夠獲得無符號編碼轉換爲補碼編碼的公式
一樣的,在0至2w-1-1之間,二者依然是相等的,而其他區間則不一樣。
仍是看一下下面的例子來理解:
#include <stdio.h> int main() { unsigned char u = 0xFF; char t = (char)u; //%d把對應的整數按有符號十進制輸出,%u把對應的整數按無符號十進制輸出 printf("u=%u,u2t=%d\n",u,t); return 0;//c標準規定建議main函數返回值爲int }
輸出結果:
這應該很好理解了,無符號 0xFF,即1111 1111,採用的是無符號編碼,第一位不是符號位,那麼轉換爲十進制就是255,而後套用上面的公式:u-2w=255-28=-1
本篇博客主要講解了有符號數和無符號數之間的轉換,咱們須要明白它的原理,這篇博客也涉及到不少公式推導證實,LZ也是看了好幾遍才理解這些,你們若是第一遍看不懂也不要緊,多看幾遍,而後多用筆推導推導,仍是不難理解的。下一章會介紹C語言中的有符號數和無符號數以及擴展和截斷數字。