C語言-深入理解除法


不用*和/計算整數除法。請找出最快的方式。

解答:

  雖然初始化一個計數變量,每當被除數減去除數的一次就自增一直到被除數小於除數這個暴力解法可行,但顯然很慢。這是wiki answer答案,但它在很多情況下都不快,比如100/1。其執行的次數正好和相除的結果相同,用m表示除數,n表示被除數,時間復雜度是O(m/n)。

復制代碼
// Note: This only works for positive values!
int divide(int numerator, int denominator) { int quotient = 0; while(numerator >= denominator) { numerator -= denominator; quotient++; } return quotient; }
復制代碼

   下面看看另一種解法。

  一般限制使用*和/時,很容易考慮使用位移運算來替代,因為對於無符號數,左移一位(在不溢出時)相當於乘以2,右移一位相當於除以2。如果在紙上進行除法的筆算,是只用到了乘法和減法的。但是一般的十進制整數除法和位運算有什么關系呢?為了將兩者建立聯系,必須把十進制數轉化成二進制數,觀察除法的進行情況來找規律。比如100/7,寫成二進制來進行筆算,計算過程如下圖:

  這樣就簡單了,從這個式子可以看出,二進制除法筆算只涉及了減法和隱含的移位與大小比較,原先的乘法已經被移位所代替。因此,具體的編碼,就是把用筆算除法的過程轉化成代碼而已。

  不過,一般考慮使用除法的環境,必然要考慮除數是否為0。除數為0時這個除法是非法的,不能繼續進行,需要報錯。

  既然提到了編碼,如果使用C語言來完成,要注意的是:在C標准中,帶符號數右移的結果在C語言里是實現相關的,具體結果取決於實現,而不一定是用符號位補、用1補或者用0補最高位。為了避免這個陷阱,建議先確定結果——也就是商的符號,然后把被除數和除數都轉化為無符號數,這樣位移時就不會出錯。

  但是,這又涉及了有帶符號數與無符號數的轉換,它們二者的表示范圍的問題是不同的。好在被除數和除數從帶符號數轉化為無符號數時並不會丟失數據,而且商的絕對值必然小於被除數的絕對值(因為除數是整數,為0時報錯,大於等於1時才繼續進行),這時把商轉化回帶符號數時也不會丟失數據,可以放心的進行。不過這一點最好在面試時告訴面試官你已經注意到了這個問題,肯定會為你的印象加分。

 

復制代碼
int division(int m,int n) { //calculate m/n without * and /
    unsigned int rest,divisor, op,result = 0; int flag; int bits = 0; //bits用於記錄商的1在哪一位
    assert(n!=0); if((m<0 && n>0) || ( m>0 && n<0 )) flag = -1; else flag = 1; rest = m>0?m:-m; divisor = n>0?n:-n; if(rest < divisor) return 0; op = divisor; /* 2013.8.30 */
    /*經過博客園園友infinityu的提醒重寫 */
    while(op<=rest) { bits++; op=op<<1; } op=op>>1; bits--; while(op>=divisor) { if(rest>=op) { rest-=op; result += 1<<bits; } op = op>>1; bits--; } /* 重寫部分結束 */

    return flag * result; }
復制代碼

 

 

 

  由於需要把被除數轉化為二進制進行計算,最多做了其二進制表示位數次的減法,因此對於被除數m,算法復雜度為O(logm)。

  稍作修改,把最后的小於除數divisor的result取出就是余數,這樣就能把除法運算改寫為取模運算%了。如果把參數表修改為傳遞結果地址,同時獲得商和余數也是可以的。


免責聲明!

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



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