深入理解計算機系統(2.6)------整數的運算


  前面兩篇博客我們詳細講解了計算機中整數的表示,包括有符號和無符號(補碼編碼)的詳細介紹。那么這篇博客我們將對它們的運算有個詳細的了解。

  在講解之前首先看下面的一個程序,看看輸出結果是啥?

#include <stdio.h>

int main()
{
	int i = 2147483647;
    printf("%d\n",i+1);
    printf("%d\n",i+i);
	return 0;
}

  結果是:

  

  我們預期的:

  i+1 = 2147483647 + 1 = 2147483648

  i+i = 2147483647 + 2147483647  = 4 294 967 294

  為什么程序中的結果和我們數學中的常識會有這么大的區別?兩個正數相加得到負數。這就需要我們理解計算機中整數的運算原理。

 

1、計算機整數運算的局限

  我們知道計算機是用二進制序列來表示數的。而二進制序列的長度是和計算機本身的字長有關。不同的數據類型定義的二進制序列長度不一樣,即不同的數據類型表示數的大小范圍是不一樣的。但是不管是什么數據類型,它定義的二進制序列長度是有限的,即它表示的數的大小范圍是有限的

  所以兩個數做運算,如果結果超出了定義數據類型所表示數的大小范圍,那么結果將會出現失真。而且這個失真的結果也不是隨機的,而是有跡可循的,那么到底是怎么產生失真的,請接着往下面看。

  PS:下面給出 64 位機器上C語言的整型數據類型的取值范圍。本篇博客中程序運行環境都是在64位系統中進行。

  

 

2、無符號數加法運算

  前面我們講過,對於一個 w 位的無符號二進制整數[xw-1 , xw-2 , … , x2 , x1 , x0],其值大小滿足 0 <= x <= 2w-1.

  如果兩個無符號數相加,那么其結果應該是 0 <= x+y <=2w+1-2。很顯然表示這個范圍的數必須要 w+1 位二進制。

  當我們對無符號數做加法運算的時候,如果結果超過了 2w-1,那么這個結果就會失真。

#include <stdio.h>

int main()
{
	unsigned short int i = 65535;
	unsigned short int j = i+1;
    printf("%u\n",j);
	return 0;
}

  結果為:

  

  對於上面的程序,我們是在64位系統中進行運算。由上面給出的圖片我們可以知道 unsigned short int 在計算機中占用 2 個字節。表示的數據范圍是 0——216-1,即0——65535

  我們在程序中定義 i = 65535,那么 i+1=65536,這個結果是超出了 unsigned short 表示的數據范圍。所以結果失真了,但是結果為什么是 0 呢?

  上一篇博客我們講過C語言中二進制數的截斷:

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

  對於上面的 i = 65535,二進制表示為:[1111 1111 1111 1111],加1 結果為 65536,用二進制表示為 [1 0000 0000 0000 0000],為了將結果保持在 4個字節,即32位二進制序列,我們去掉最高位的 1,那么結果就變成了 [0000 0000 0000 0000],也就是打印出來的結果 0.

  一般而言,無符號加法等價於計算和模上2w

  比如上面的兩者計算和為 65536,模上 2w,即模上216=65536,結果為0

  ps:模表示兩者相處取余

  現在定義 0<= x,y <2w,那么它們運算滿足下面關系:

  

  注意:當 2<= x+y < 2w+1,對 x + y 進行2w的取模運算,與 x + y - 2w是等價的。

  所以如果兩個無符號整數作加法運算。當 x+y < 2w 時,它們的結果不變;當 2<= x+y < 2w+1,它們的結果為 x+y-2w

  

3、補碼加法運算 

  對於補碼加法運算,因為補碼編碼是表示有符號的整數。

  對於一個 w 位的補碼二進制整數[xw-1 , xw-2 , … , x2 , x1 , x0],其值大小滿足 -2w-1  <= x <= 2w-1-1。那么 -2w <= x+y <=2w-1

  想要表示上面的兩個數相加和的范圍,那么可能需要 w+1 來表示。這里我們也需要截取。

  與無符號加法運算不同,補碼加法會出現三種情況:正溢出、正常、負溢出。定義如下:

    范圍在  -2w-1  <= x,y <= 2w-1-1 做加法運算時,滿足

  

  簡單來說:補碼加法運算就是先按照無符號加法進行運算,而后在進行無符號和有符號的轉換。

   

這里我們看個例子:

#include <stdio.h>

int main()
{
	short int i = -32768;
 	short int j = i-1;
    printf("%d\n",j);
	return 0;
}

  結果為:

  

  為什么 -32768-1 結果會是 32767?

  根據上面的公式:

  

  我們需要先將 -32768 和 -1 分別轉換成無符號數進行加法運算,然后對得到的結果轉換成有符號數。

  ①、-32768 轉換成無符號數也就是 -32768+2^16=32768  

  ②、-1 轉換成無符號數也就是-1+2^16=65535 

  ③、將上面兩步的結果相加,然后轉換成有符號數:

    即(65535+32768)-2^16=65535+32768-65536=32767

  這個過程用到的公式分別有:

  

4、無符號數乘法運算

  對於一個 w 位的無符號二進制整數[xw-1 , xw-2 , … , x2 , x1 , x0],其值大小滿足 0 <= x <= 2w-1.

  如果兩個無符號數相乘,那么其結果應該是 0 <= x*y <=(2w-1)2=22w-2w+1+1。很顯然表示這個范圍的數可能需要 2w 位來表示。也就是 2w 位的整數乘積的低 w 位表示的值。根據我們前面講的截斷原理:可以看做是計算乘積模2w,即:

  

#include <stdio.h>

int main()
{
	unsigned short int i = 2;
 	unsigned short int j = i*2;
    printf("%u\n",j);
	return 0;
}

  比如上面的程序結果是:(2*2)mod 216=4 mod 65536=4

  

5、補碼乘法 

   對於一個 w 位的補碼二進制整數[xw-1 , xw-2 , … , x2 , x1 , x0],其值大小滿足 -2w-1  <= x <= 2w-1-1

  那么它們的乘積x*y的取值范圍在 -2w-1*(2w-1-1)=22w-2+2w-1 到 -2w-1*2w-1=-22w-2 之間。

  同理 2w 位的整數乘積的低 w 位表示的值。根據我們前面講的截斷原理:補碼乘法運算公式為

    

 

   

  假設對於w位的兩個補碼數來說,它們的乘積的低w位與無符號數乘積的低w位是一樣的。這意味着計算機可以使用一個指令執行無符號和補碼的乘法運算。下面我們來證明:

  其中x’和y’分別代表x和y的補碼編碼。

  那么:(應用有符號轉為無符號公式可得)

  

  即:          (2w mod 2w = 0)

 

   由於模運算符,所有帶權重 2w 的項都丟棄了,因此我們看到 x*y  和 x’*y’ 的低 w 位是相同的。

 

6、乘法優化

  由於在大多數機器上,整數乘法指令相當慢,需要 10 個或多個時鍾周期,而其他整數運算(比如加法、減法、位級運算和移位)只需要 1 個時鍾周期。

  因此編譯器使用了一項重要的優化,使用移位和加法的組合來代替乘法。

  結論:對於一個w位的二進制數來說,它與2k的乘積,等同於這個二進制數左移k位,在低位補k個0。

  證明過程如下:

   

  我們前面說過,整數乘法代價要比移位和加法代價大得多。那么C編譯器會以移位、加法、減法的組合來消除很多整數乘以常數的情況。

  比如:

    計算 x*14 的乘積。 由於 14 = 23+22+21   ,那么編譯器會將乘法重寫為(x<<3)+(x<<2)+(x<<1)。這樣就將乘法替換為三個移位和兩個加法。無論 x 是無符號還是補碼,甚至當乘法會導致溢出時,兩個計算都會得到一樣的結果。

    更好的編譯器,可能會將 14 = 24-21。那么就會變成(x<<4)-(x<<1),只需要兩個移位和一個減法。

 

7、除法運算

  實際上在大多數機器上,整數除法要比整數乘法更慢,需要 30 或更多個時鍾周期。

  結論:對於除以 2 的冪可以用移位來運算。無符號除法使用邏輯移位,補碼除法使用算術移位。

  ①、邏輯右移在左端補k 個0。C語言中對於無符號數據必須邏輯右移。

  對於位向量[xw-1,xw-2,...,x0]邏輯右移 k 位會得到位向量:[0,...,0,xw-1,xw-2,...,xk]。轉換成除法即 x/2k,從結果我們可以看出邏輯移位出現小數,總是舍入到零,比如 7/2應該是 3,而不是4

    

 

  ②、算術右移是在左端補 k 個最高有效位的值。對於一個正整數,由於最高有效位是 0 ,所以效果和邏輯右移是一樣的;對於非負數,算術右移 k 位與除以 2k 是一樣的。

    對於結果不需要舍入的情況結果是正確的。但是對於結果需要舍入的時候,算術右移導致的結果是向下舍入,比如 -7/2應該是 -3,而不是 -4。這是錯誤的。

    

 

8、總結

  那么本篇博客結束我們對於整數的表示以及運算都已經了解了。注意整數的運算我沒有將減法,其實減法也就是轉換為補碼相加。而且計算機中也只有加法器,是沒有減法器的。我們只需要將減法轉換為加法運算即可。

  整數的表示和運算結束了,下一篇博客我們將會講解浮點數,也就是有小數的數。

    


免責聲明!

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



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