深入理解計算機系統(九):C語言中的有符號數和無符號數以及擴展和截斷數字

深入理解計算機系統(九):C語言中的有符號數和無符號數以及擴展和截斷數字

來自專欄 Python程序員5 人贊了文章

目錄

1、C語言中的有符號數和無符號數

2、擴展一個數字的位表示

3、截斷數字

4、總結

  上一篇博客我們講解了計算機中整數的表示,包括無符號編碼和補碼編碼,以及它們之間的互相轉換,個人覺得那是非常重要的知識要點。這篇博客我們將介紹C語言中的有符號數和無符號數以及擴展和截斷數字。

正文:

1、C語言中的有符號數和無符號數

  上一篇博客我們給出了C語言中在32位機器和64位機器中支持的整型類型數據,我們這裡只給出32位機器上的:

  儘管 C 語言標準沒有指定有符號數要採用某種編碼表示,但是幾乎所有的機器都使用補碼。通常大多數數字是默認有符號的,比如當聲明一個像12345或者0xABC這樣的常量的時候,這個值就被認為是有符號的。

  C 語言允許有符號數和無符號數之間的轉換。在一台採用補碼的機器上:

①、無符號數轉換成有符號數

②、有符號數轉換成無符號數

  我們可以看下面這個程序:

#include <stdio.h> int main(){ char t = 0xFF; //%d把對應的整數按有符號十進位輸出,%u把對應的整數按無符號十進位輸出 //有符號的轉換成無符號的 printf("t=%d,t2u=%u
",t,(unsigned char)t); unsigned char u = 0xFF; //%d無符號轉換成有符號的 printf("u=%u,u2t=%d
",u,(char)u); return 0;}

  結果為:

  為什麼是這個結果,我在上一篇博客:深入理解計算機系統(八):整數的表示已經講過了,這就是數據類型的強制轉換

  還有第二種情況是當一種類型的表達式被賦值給另一種類型的變數時,轉換是隱式的。比如:

#include <stdio.h> int main(){ unsigned char u = 0xFF; char t = u; //%d無符號轉換成有符號的是默認的 printf("u=%u,u2t=%d
",u,t); return 0;}

  結果是:

  我們將一個無符號的數賦值給有符號的,其轉換是隱式的發生的。這對於標準的運算來說並無差異,但是對於像 < 和 > 這樣的關係運算來說,會導致錯誤的結果。

注意:在 C 語言中,當執行一個運算,會隱式的將有符號參數強轉為無符號參數。

#include <stdio.h> int main(){ printf("%d
",-1<0u);//結果是0,0表示錯誤 1表示正確 printf("%d
",-123<123u);//0 return 0;}

  我們解釋第一個 -1 < 0u 為什麼是錯誤的。因為0u是無符號的,-1是有符號的。那麼-1就會被轉換成無符號的。

  也就是T2Uw(-1)=-1+232=4 294 967 296-1=4 294 967 295

  那麼 -1 < 0u 表達式也就變成了 4 294 967 295u < 0u ,結果當然是錯誤的。第二個例子我們也可以這樣分析,這裡就不詳細描述了。

  所以我們要注意實際編碼過程中由於隱式轉換所造成的錯誤運算。

2、擴展一個數字的位表示

  擴展一個數字的位,簡單來說就是在不同字長的整數之間轉換,而這種轉換我們可以需要保持前後數值不變。當然將一個數據轉換為字長更小的數據類型的時候,它的值肯定會發生變化。那麼我們只能將較小的數據類型轉換為較大的數據類型。比如將短整型short int 轉換為整型 int。,那該怎麼辦呢?

  ①、零擴展

    將一個無符號數轉換為一個更大的數據類型,我們只需要簡單的在二進位序列前面添加 0 即可。

  ②、符號位擴展

    將一個補碼數字轉換為一個更大的數據類型,我們需要在開頭添加符號位。

  由上面兩條我們可以總結:如果我們原始位為[xw-1 , xw-2 , … , x2 , x1 , x0],那麼擴展後就可以表示為:[xw-1 ,xw-1 ,...,xw-1 , xw-2 , … , x2 , x1 , x0]。

  即我們想證明:

  在表達式的左邊,我們增加了 k 位的xw-1副本。如果我們證明符號位擴展一位,即 k=1,而值是保持不變的。那麼對於任意的k都能保持這種屬性。那麼等式變為:

  由於無符號的,添加0,這很好理解,前後數值不變。那麼我們證明有符號的補碼編碼:

  由於:

  將上面的補碼編碼替換等式右邊,即:

  上面的證明我們只需要知道:2w-2w-1=2w-1 即很好理解了。

3、截斷數字

  這和上面的擴展剛好相反。即我們不需要額外的擴展一個數的位,而是減少一個數字的位數。

將一個 w 位的數 [xw-1 , xw-2 , … , x2 , x1 , x0] 截斷為一個 k 位數字時,我們會丟棄高 w-k 位。得到 [xk-1 , xk-2 , … , x2 , x1 , x0]

  對於無符號截斷公式為:

  證明過程如下:

  而對於有符號(補碼編碼)的截斷,我們只需要多加一步,將無符號編碼轉換為補碼編碼就可以了。

  比如下面這個程序:

#include <stdio.h> int main(){ int i = 53191; short int j = (short)i; int k = j; printf("%d %d %d
",i,j,k); return 0;}

  結果為:

  我們將 i 強轉為 short int,在 64位機器上,就是將 32 位的 int 截斷為 16 位的short int,這個16位的位模式就是 -12345 的補碼錶示。當我們把它強轉為 int 時,符號位擴展把高 16 位設置為 1,從而生成 -12345 的32 位補碼錶示。

4、總結

  本篇博客講解了 C 語言中的有符號數和無符號數,以及擴展和截斷一個數值是如何進行的,理解它們的原理是十分必要的。

  我們從上面已經看到了許多無符號運算的特殊性,尤其是有符號數到無符號數的隱式轉換會導致錯誤。而避免這類錯誤的方法是不使用無符號數。實際上,除了 C 語言,很少有語言支持無符號數。比如 Java只支持整型數據,並且要求補碼運算。

  那麼計算機中整數的表示就已經講完了,下篇博客將會講解計算機中整數的運算,我們出現的兩個數運算會產生莫名其妙的結果在下一篇博客會得到解答。

推薦閱讀:

重啟《GacUI的設計與演化》
IDE集成開發環境 Web圖形化 AI編程 Integrated Development Environment
Python3 函數03
初學者如何上手Lede/OpenWrt?需要具備哪些基礎知識?
生命編程:修行應從何做起?

TAG:編程 | 計算機 | Python |