用基本位運算實現加減乘除


一、計算機加法的實現:

(1).一位二進制加法
首先給出一位二進制加法的真值表,然后我們通過分析真值表來得出如果進行二進制加法的規則。
 
一位二進制加法真值表:(對應於硬件中的半加器)
       
       
       
       
       
x y sum carry
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1

 

 
 
 
 
 
 
 
分析上面一位二進制加法的真值表,可以看出和其實就是x XOR y的結果。而進位恰好是x AND y的結果。下面提供XOR和AND的真值表,進行驗證。
 
x XOR y真值表:
x y output
0 0 0
0 1 1
1 0 1
1 1 0

 

 

 

 

 

 

 

x AND y真值表:
x y output
0 0 0
0 1 0
1 0 0
1 1 1

 

 
 
 
 
 
 
 
 
 
(2).多位二進制加法
因此進行一位二進制加法,只要對x和y進行XOR和AND運算就可以得出和以及進位。如果要求多位的二進制加法,則要把低位傳上來的進位也要計算進去。修改一位二進制加法的真值表,在輸入部分中加入低位傳入的進位,然后對計算結果進行修正就可以得到多位二進制加法的真值表了。
 
多位二進制加法真值表:(對應於硬件中的全加器)
x y icarry sum ocarry
0 0 0 0 0
0 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
1 0 1 0 1
1 1 0 0 1
1 1 1 1 1

 

 
 
 
 
 
 
 
 
 
 
 
 
 
分析上面的真值表就可以總結出,多位二進制加法的規則了。如下:
sum = (x XOR y) XOR icarry
ocarry = (x AND y) OR (icarry AND (x XOR y)) = (x AND y) OR (y AND icarry) OR (icarry AND x)
 
利用以上的分析結果,可將x, y的每一位級聯計算,先計算x和y的第零位,該位的輸入進位(icarry=0)為零,將計算所得的進位傳入到x和y的第一位的計算中,依次進行直到計算完最高位為止,此時將每一位計算所得的和連接起來就是最終的和,最高位計算所得的進位就是最終的進位。至此,二進制的加法應該沒有什么問題了,很簡單XOR為和,AND為進位。計算機中的加法也是使用這種原理來實現的,有興趣的可以看看《編碼的奧秘》這本書。
 
(3).在C++中實現加法
通過以上分析似乎用代碼實現計算機加法的方法已經很明了了,將參加計算的x,y分別一位一位的進行XOR和AND,然后將結果打印出來,OK完事了,很簡單不是嗎?但問題是如果將x和y的每一位拆分,然后記錄每一位計算后所得的進位,然后做為下一位的進位輸入。仔細想想又似乎是問題多多啊,煩啊。其實也不然我們用代碼實現的時候已經不需要再將每一位拆分計算了(如果你確實像模擬計算機硬件的執行過程也可以這么做,只是這樣做很麻煩,而且計算的速度比較慢)。首先,我們通過對x和y進行&位運算,得出每一位上的進位。然后對x和y進行^位運算,得出沒有加進位的和。最后將所得的和當做新的x,所得的進位往左移一位(第零位的進位輸入為0)當做新的y,繼續做上面的步驟,直到進位為0,此時x中保存的就是我們要求的x和y的和了。
具體的實現代碼也很簡單,如下:
int add(int a, int b) {   int sum = a;   int carry = b;   while(carry)   {     int tmps = sum;     sum = tmps ^ carry;     carry = (tmps & carry) << 1;   }   return sum; }

 

二、計算機減法的實現

(1).減法概述
減法是加法的逆運算,加法中有進位,相應的減法中需要借位。加法中的進位可以從低位開始依次往上傳遞,而且高位對低位的計算不產生影響。而減法的借位,則需要從高位獲得,高位會對低位的計算產生影響。如果是小數字減大數字則計算過程更復雜,因此直接實現減法對計算機來說很復雜,而且效率很低。那計算機要如何實現減法呢?也許你們聽說過2-補碼,這就是計算機要施展的魔法,搖身一變,很難實現的減法運算將變成加法。然后直接利用上一節實現的加法過程完成計算。什么是補碼?取反運算就是最簡單的補碼2-補碼比取反多了一個步驟,即取反后加1。還是用實例來說話吧。下面是8位(即一個字節)的2-補碼編碼以及所表示的數字,一個字節能表示從-128到127的數字。如果要更詳細的了解2-補碼那就看這里
 
二進制十進制
10000000   -128
10000001   -127
10000010   -126
10000011   -125
.
.
.
11111101   -3
11111110   -2
11111111   -1
00000000   0
00000001   1
00000010   2
.
.
.
01111100   124
01111101   125
01111110   126
01111111   127
 
通過上面的表,不難看出只要想得到一個數的相反數,只要對這個數求2-補碼就可以了,即取反加1操作。然后繼續對得到的結果進行該操作就可以得到原來的數字。通過使用2-補碼形式的編碼,我們可以把減法運算順利的轉換成加法。只要對減數求2-補碼,然后跟被減數相加即可得到差值。不信,那就驗證一下 例如 124 - 127 = -3 。我們利用上面的規則來進行驗證一下,先算出減數的2-補碼,減數為127,對其求2-補碼后為-127(10000001)。然后跟124(01111100)執行加法操作。即可獲得我們需要的差值 01111100 + 10000001 = 11111101(-3)剛好獲得的結果就是-3的編碼。這里就不舉更多的例子了,有興趣的可以自己驗證。
 
(2).減法在C++中的實現
通過上面的研究,要在代碼中實現減法已經很明了,很簡單了。第一步對減數取反然后加1,第二步將第一步所得值和被減數相加。而加法運算已經在上一節實現過,具體代碼如下: 
int subtract(int a, int b) {   int subtrahend = add(~b, 1);   int sub = add(a, subtrahend);   return sub; }

 

是不是很簡單,事情往往就是這樣,在沒有弄清楚的時候感覺很復雜。但是等到真正理解的時候一切又都變的簡單無比。所有只要用心,再難的事情也會變的簡單。
 
三、計算機乘法的實現
(1).原始的乘法實現
乘法最簡單的理解就是,將被乘數加乘數次即可得到乘積。考慮到負整數的乘法,我們這里先對乘數和被乘數求絕對值,然后對絕對值進行上述的乘法操作。確定乘積符號的規則為同號為正,異號為負。這中實現方式比較簡單,代碼如下:
int multiply(int a, int b) {   //將乘數和被乘數都取絕對值
  int multiplier = a < 0 ?  add(~a , 1) : a;   int multiplicand = b < 0 ? add(~b, 1) : b;   //計算絕對值的乘積
  int product = 0;   int count = 0;   while(count < multiplier)   {     product = add(product, multiplicand);     count = add(count, 1);   }   //計算乘積的符號
  if((a ^ b) < 0)   {     product = add(~product, 1);   }   return product; }

 

(2).改進的乘法實現
上面的第一種實現方式雖然簡單,但是效率太低。如果乘數和被乘數小一點還可以,如果大了那效率是不能忍受的。這里要實現的這種乘法,最多做log(n)次的加法操作,就可以求出乘積。這種方式和第一方式的相同點是,這里也是先對兩數的絕對值就乘積,最后確定符號。這種實現方式就是對手動計算乘數的模擬。具體步驟如下:
1)根據乘數每一位為1還是為0,決定相加數取被乘數移位后的值還是取0;
2)各相加數從乘數的最低位開始求值,並逐次將相加數(被乘數)左移一位,最后一步求和
3)符號位根據同號為正異號為負的原則
如果對上面的步驟還很難明白的話,那我下面舉個簡單的例子。對照着例子然后在仔細分析下上面的計算步驟,將不難明白這種實現方式的原理。例如 11 * 13 = 143 ,轉換成二進制 1011 * 1101 =10001111 ,首先判斷乘數的第一位,這里第一位為1,因此第一個相加數為1101。然后判斷乘數的第二位,這里第二位為1,所以第二個相加數為11010,即被乘數左移一位。繼續看乘數的第三位,這里第三位為0,則第三個相加數為0。最后看乘數的第四位,這里為1,所以最后一個相加數為1101000即被乘數左移三位。最后將獲得的所有相加數都加起來,而這個和就是所要求的乘積。我們驗證一下是否正確,將我們算出的各個相加數依次加起來。(1101+11010+0+1101000=10001111)哦,果然和上面給出的一樣,我們把二進制轉成十進制即為143。不難看出來相加數就是根據乘數的第n位是否為1,如果為1,就將被乘數左移n位所得(這里n從0開始)。
 
計算過程演示: 
               1 0 1 1
*             1 1 0 1
 ------------------
               1 1 0 1
            1 1 0 1 0
         0 0 0 0 0 0
      1 1 0 1 0 0 0
 ------------------
   1 0 0 0 1 1 1 1
 
C++中的實現代碼如下:
int multiply(int a, int b) {   //將乘數和被乘數都取絕對值
  int multiplier = a < 0 ?  add(~a , 1) : a;   int multiplicand = b < 0 ? add(~b, 1) : b;   //計算絕對值的乘積
  int product = 0;   while(multiplier)   {     if(multiplier & 0x1)     {       product = add(product, multiplicand);     }     multiplicand = multiplicand << 1;     multiplier = multiplier >> 1;   }   //計算乘積的符號
  if((a ^ b) < 0)   {     product = add(~product, 1);   }   return product; }

 

四、計算機除法的實現
(1).原始的除法實現
最簡單的除法實現就是不停的用除數去減被除數,直到被除數小於除數時,此時所減的次數就是我們需要的商,而此時的被除數就是余數。唯一需要注意的就是商的符號和余數的符號。商的符號確定方式也乘法是一樣,即同號為正,異號為負。而余數的符號和被除數的符號是一樣的。和簡單的乘法實現一樣,這里我們要先對兩數的絕對值求商,求余數。最后再確定符號。具體實現代碼如下:
//求商
int divide(int a, int b)
{
  //對被除數和除數取絕對值
  int dividend = a < 0 ? add(~a, 1) : a;
  int divisor = b < 0 ? add(~b, 1) : b;
 
  //對被除數和除數的絕對值求商
  int remainder = dividend;
  int quotient = 0;
 
  while(remainder >= divisor)
  {
    remainder = subtract(remainder, divisor);
    quotient = add(quotient, 1);
  }
 
  //求商的符號
  if((a ^ b) < 0)
  {
    quotient = add(~quotient, 1);
  }
 
  return quotient;
}

//求余
int remainder(int a, int b)
{
  //對被除數和除數取絕對值
  int dividend = a < 0 ? add(~a, 1) : a;
  int divisor = b < 0 ? add(~b, 1) : b;
 
  //對被除數和除數的絕對值求商
  int remainder = dividend;
  int quotient = 0;
 
  while(remainder >= divisor)
  {
    remainder = subtract(remainder, divisor);
    quotient = add(quotient, 1);
  }
 
  //求余的符號
  if(a < 0)
  {
    remainder = add(~remainder, 1);
  }
 
  return remainder;
}
 
(2).改進的除法實現
在改進乘法的時候我們是從乘法的手動計算入手的,然后根據手動計算總結乘法的規律,最后動手實現。這里我不進行手動計算除法的分析。只給出相對應的算法描述:
 
首先對被除數和除數求絕對值,下面的過程中用到的除數和被除數都是求過絕對值的數值。
0)設定invert為2,即二進制10。
1)如果被除數大於0則將被除數的第一位與invert進行或運算,否則轉到3)繼續。
2)然后對invert左移一位,被除數右移一位。轉到1)繼續。
3)余數左移一位,並將最后一位置為invert的最后一位。invert右移一位。
4)商左移一位,如果余數大於除數,則從余數中減去除數,並把商的最后一位置1。
5)如果invert等於1則返回商,否則轉到3)繼續。
符號確定規則,與上面的簡單實現方式相同。上面算法中唯一比較疑惑的就是為什么要用invert而不是直接用被除數,同時為什么要設定invert的初始值為2呢?如果你對除法的手動計算進行過分析的話,細心的你也許已經發現了模擬過程中的一個難點。那就是怎么從被除數的最高位開始,然后每一步都往低位逐漸移動。如果不用任何技巧而直接從最低位開始一直尋找到最高位也是可行的,但是這樣的話就必須要知道你現在所用的整形是多少位的。為了避免這個問題我就把被除數反序表示。這樣的話用完被除數最高位,只要將invert右移一位,原來的次高位很容的就變成了現在的最高位,從而大大簡化了計算過程。不要高興的太早,這樣處理雖然是可行的但是又會引出另外一個問題,比如1100, 如果我用反序表示就是0011。Oh my god!如果最低位是0的數字進行反序后,這些0被自動丟棄了。那么如果不讓它們丟棄呢?解決方法就在於invert初始化為2,即反序的最高位我置為1,這樣的話,我在右移后判斷下invert是否為1就知道當前位是否為被除數的最后一位了。OK,啰嗦了這么多我自己已經徹底搞蒙了,不知道看的人是不是會更加糊塗哦。下面是代碼實現:
//求商
int divideEx(int a, int b)
{
  //對被除數和除數取絕對值
  int dividend = a < 0 ? add(~a, 1) : a;
  int divisor = b < 0 ? add(~b, 1) : b;
 
  //獲得被除數的反序 比如dividend=101011 invert=1110101,invert最高位會多一個1,
  //這是為了防止dividend=1010,則直接反轉為0101,這個時候原來的最低位的0就會丟失
  int invert = 2;
  while(dividend)
  {
    invert |= dividend & 0x1;
    invert = invert << 1;
    dividend = dividend >> 1;
  }
 
  int quotient = 0;
  int remainder = 0;
  while(invert > 1)//排除最高位的1
  {
    remainder = remainder << 1;
    remainder |= invert & 0x1;
    invert = invert >> 1;
    quotient = quotient << 1;
 
    if(remainder >= divisor)
    {
      quotient |= 0x1;
      remainder = subtract(remainder, divisor);
    }
  }
 
  //求商的符號
  if((a ^ b) < 0)
  {
    quotient = add(~quotient, 1);
  }
 
  return quotient;
}

//求余
int remainderEx(int a, int b)
{
  //對被除數和除數取絕對值
  int dividend = a < 0 ? add(~a, 1) : a;
  int divisor = b < 0 ? add(~b, 1) : b;
 
  //獲得被除數的反序 比如dividend=101011 invert=1110101,invert最高位會多一個1,
  //這是為了防止dividend=1010,則直接反轉為0101,這個時候原來的最低位的0就會丟失
  int invert = 2;
  while(dividend)
  {
    invert |= dividend & 0x1;
    invert = invert << 1;
    dividend = dividend >> 1;
  }
 
  int quotient = 0;
  int remainder = 0;
  while(invert > 1)//排除最高位的1
  {
    remainder = remainder << 1;
    remainder |= invert & 0x1;
    invert = invert >> 1;
    quotient = quotient << 1;
 
    if(remainder >= divisor)
    {
      quotient |= 0x1;
      remainder = subtract(remainder, divisor);
    }
  }
 
  //求商的符號
  if(a < 0)
  {
    remainder = add(~remainder, 1);
  }
 
  return remainder;
}
 
五、后記
上面寫的內容都是自己想到哪里就寫到哪里,所以肯定有很多錯誤。況且自己的文筆是爛到家了。就當時自己的讀書筆記吧。我實現的也都是最簡單的整數運算,浮點型以及更復雜類型的我沒有實現。雖然實現的比較簡單,但是我覺得原理已經都說清楚了,至少我自己認為說的比較詳細了。


免責聲明!

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



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