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


  上一篇博客我們講解了計算機中整數的表示,包括無符號編碼和補碼編碼,以及它們之間的互相轉換,個人覺得那是非常重要的知識要點。這篇博客我們將介紹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\n",t,(unsigned char)t);
 
 	unsigned char u = 0xFF;
    //%d無符號轉換成有符號的 
    printf("u=%u,u2t=%d\n",u,(char)u);
	return 0;
}

  結果為:

  

  為什么是這個結果,我在上一篇博客:深入理解計算機系統(2.4)------整數的表示(無符號編碼和補碼編碼)已經講過了,這就是數據類型的強制轉換

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

#include <stdio.h>

int main()
{
	unsigned char u = 0xFF;
 	char t = u;
    //%d無符號轉換成有符號的是默認的 
    printf("u=%u,u2t=%d\n",u,t);
	return 0;
}

  結果是:

  

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

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

#include <stdio.h>

int main()
{
    printf("%d\n",-1<0u);//結果是0,0表示錯誤 1表示正確 
    printf("%d\n",-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\n",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只支持整型數據,並且要求補碼運算。

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM