參考自:http://csapp.cs.cmu.edu/express
開篇說明一下,本文不是介紹四則運算的具體執行過程,想了解具體過程的孩子們本身去看看計算機組成。app
好了,話很少說。ide
加法和減法沒有區別,如下內容專一於加法。函數
無符號數加法會出現溢出問題,當發生溢出的時候直接扔掉溢出進位。好比1111 + 0001 = 10000 直接扔掉進位1,結果爲0000。考慮到溢出以後,咱們能夠總結出這樣的計算式:優化
那麼咱們怎麼來判斷溢出呢?ui
對於s= x+ y; 當s <x 或者 s < y的時候就是溢出了。緣由在於溢出的時候s=x+y-2w。而x,y都是小於2w的因此s<x ,s<y。this
因而,咱們能夠寫出函數來提早判斷兩個無符號數想加是否會溢出:lua
/* Determine whether arguments can be added without overflow */ int uadd_ok(unsigned x, unsigned y){ unsigned temp= x+y; return temp>=x; }
下面給出一張圖,全面展現四位無符號二進制樹之間的想加狀況:spa
好了,以前提到加法和減法沒什麼區別,那麼咱們怎麼計算無符號數減法呢?要知道無符號數但是沒有「相反數」這個東西的。code
爲了解決這個問題,咱們引入一個 加法逆元(additive inverse) 的概念。假設x的加法逆元是y,當且僅當(x+y)%2w=0,其中x,y爲w位的二進制數。
加法逆元的計算公式:
之後計算減法的時候,就將被減數轉換爲其加法逆元,這樣再相加就行了。
有符號加法和無符號加法產生的二進制是同樣的,它也會產生溢出,因爲符號的緣由它有兩種溢出 正溢出和負溢出。正溢出是指兩個兩個正數想家結果爲負數,好比0111 + 0001 =1000。負溢出就是兩個負數相加爲正數1001 + 1001 =0010。計算公式以下:
給出四位二進制有符號數相加的所有狀況圖:
原理就是 負負得正,正正得負 就是溢出啦。
/* Determine whether arguments can be added without overflow */ int tadd_ok(int x, int y) { int sum = x+y; int neg_over = x < 0 && y < 0 && sum >= 0; int pos_over = x >= 0 && y >= 0 && sum < 0; return !neg_over && !pos_over; }
固然了,若是有人神經叨叨的給你看他的實現方法:
/* Determine whether arguments can be added without overflow */ /* WARNING: This code is buggy. */ int tadd_ok(int x, int y) { int sum = x+y; return (sum-x == y) && (sum-y == x); }
這段代碼毫無疑問是錯誤的,爲何呢?由於咱們知道加法是符合交換律的,那麼(x+y)-x = (x-x)+y,也就是說上述判斷條件始終成立。不信你能夠驗證。
說完加法,咱們來看看怎麼將減法轉換爲加法。固然是取相反數了,關鍵是,怎麼取相反數?這是一個頗有學問的地方,技巧妙的一談糊塗。
x相反數是y,當且僅當x+y=0。那麼咱們顯然獲得y=-x這個公式了,可是要是有人問你最小負數的相反數是多少,你怎麼辦?舉個例子,對於8位二進制而言,最小負數-128,-(-128)=128>127(8位二進制最大正數)。
哈哈,怎麼辦,怎麼辦。
結論是最小負數的相反數就是它本身!!你能夠算一算1000 0000 + 1000 0000 =0000 0000 符合定義!
因而就有咱們的求負數的公式了:
哈哈,看完這個咱們再來看看怎麼驗證兩個數相減會不會溢出,先看看下述代碼:
/* Determine whether arguments can be subtracted without overflow */ /* WARNING: This code is buggy. */ int tsub_ok(int x, int y) { return tadd_ok(x, -y); }
這個對不對呢?
應該是不對的,由於Tmin的相反數仍是他本身,當x爲正數,y爲Tmin的時候,這個函數是會報錯的,可是其實是不會溢出的。
下面接着來講怎麼實際操做來取相反數:一種方法是 各位取反後加一
第二種方法:咱們仔細看看這個咱們能看出一個規律,就是說咱們沿着二進制從右往左走,知道遇到第一個1,而後將第一個1以後的每一個位取反就行了。
關於具體怎麼作乘法本身去查閱計算機組成原理課本。咱們先來看具體的二進制乘法的例子:
咱們發現一個現象,就是說有符號二進制乘法和無符號二進制乘法,他們得到的最終結果的二進制表示是同樣的!!、
好吧,處於嚴謹考慮,咱們來證實一下下:
那麼咱們怎麼來判斷乘法會不會發生溢出呢?咱們注意到,上面那個圖中的乘法都是溢出的。
先給出代碼,你們能夠看看這個代碼對不對?
/* Determine whether arguments can be multiplied without overflow */ int tmult_ok(int x, int y) { int p = x*y; /* Either x is zero, or dividing p by x gives y */ return !x || p/x == y; }
好吧,我認可我頑皮了,由於大部分同窗(包括曾經的我)根本看不懂這個是什麼東東!腦子裏就倆個字:尼瑪!
咱們從理論上證實一下下:
首先咱們知道對於w位的x和y,x*y是能夠用2w位二進制來表示的,咱們將這2w位二進制拆爲兩部分,高w位(用v來表示)和低w位(用u來表示),因而咱們獲得x*y=v*2w + u. u表明了代碼中p的二進制表示,根據無符號數轉換爲有符號數的公式咱們獲得:u = p + p w−1 2w,因而咱們能夠將x * y = p + t * 2w. 根據溢出的定義,該乘法發生溢出當且僅當 t != 0。
其次,當x!=0的時候咱們能夠將p表示爲p = x * q + r, |r| < |x|。因而咱們有x * y = x * q + r + t * 2w。咱們須要推導出t == 0的等價條件。當t = 0的時候,咱們有x * y = x * q + r,而|r| < |x|,因此必有q=y;反過來,q=y的時候咱們也能夠推出t=0。也便是說t=0這個條件等價於q=y這個條件(前提是x不爲0)。
q=p/x;
因而咱們就獲得了咱們代碼裏面的判斷條件: !x || p/x == y
那麼若是咱們不容許使用除法,可是容許你使用long long這個數據類型,你怎麼來作乘法的溢出檢查呢?
哈哈,其實這個就簡單了。
/* Determine whether arguments can be multiplied without overflow */ int tmult_ok(int x, int y) { /* Compute product without overflow */ long long pll = (long long) x*y; /* See if casting to int preserves value */ return pll == (int) pll; }
哈哈,說完這些以後咱們來看看怎麼將乘法轉換爲 移位和加法操做, 由於這兩種操做時很是快速的。
咱們來看,怎麼將經過移位的方法一個數擴大兩倍?很顯然是左移一位。擴大四倍呢?左移兩位。
其實咱們就類比十進制乘法 列豎的計算式子就能夠了。
這裏面有個能夠被優化的技巧,咱們來看。加入被乘數是[(0 . . . 0)(1 . . . 1)(0 . . . 0) ].那麼咱們還要一個個的移位相加麼?其中連續的1,起於從右到左的第m位,終於第n位。
咱們能夠這樣,(x<<n+1) - (x<<m)。
好了,乘法也就這麼多了,下面是除法。
除法這一塊咱們只涉及除以一個2k次方。對於正數以及無符號數而言,這意味着右移k位,高位補0。下圖中斜體的0都是後來補的0。
而對於負數而言,咱們須要採用邏輯右移,也就是說高位補1。例子見下圖:
可是咱們在圖中發現一個問題,就是結果可能跟真實的值相差一,好比-12340/8應該爲-48而不是-49,-7/2=-3而不是-4。怎麼處理這個問題呢?
咱們能夠對負數作一點預處理,使得x=x+y-1。其中y就是咱們的除數。
咱們先來看看這麼作對不對,(-7+2-1)/2 = (-6/2) =-3 沒有問題啊。
證實以下:假設x = qy + r,其中0 ≤r <y。那麼,(x + y − 1)/y = q + (r + y − 1)/y。當r=0的時候x/y=q,不然x/y=q+1。符合要求!!
代碼實現是這樣的:(x<0 ? x+(1<<k)-1 : x) >> k。
哈哈,妙吧!
那麼若是僅僅容許使用移位和&操做以及加法,怎麼代碼實現除法呢?(注意除數爲2k)
int div16(int x) { /* Compute bias to be either 0 (x >= 0) or 15 (x < 0) */ int bias = (x >> 31) & 0xF; return (x + bias) >> 4; }
看看這個代碼寫的!!亮瞎了吧。
當x爲正數的時候,bias=0, 這沒錯。
當x爲負數的時候,bias爲15=16-1。又沒問題!
好了,除法也就這些了!
Assume we are running code on a 32-bit machine using two’s-complement arithmetic
for signed values. Right shifts are performed arithmetically for signed values
and logically for unsigned values. The variables are declared and initialized as
follows:
int x = foo(); /* Arbitrary value */
int y = bar(); /* Arbitrary value */
unsigned ux = x;
unsigned uy = y;
For each of the following C expressions, either (1) argue that it is true (evaluates
to 1) for all values of x and y, or (2) give values of x and y for which it is false
(evaluates to 0):
A. (x > 0) || (x-1 < 0)
B. (x & 7) != 7 || (x<<29 < 0)
C. (x * x) >= 0
D. x < 0 || -x <= 0
E. x > 0 || -x >= 0
F. x+y == uy+ux
G. x*~y + uy*ux == –x
解答以下:
A. (x > 0) || (x-1 < 0)
False. Let x be −2,147,483,648 (TMin32). We will then have x-1 equal to
2147483647 (TMax32).
B. (x & 7) != 7 || (x<<29 < 0)
True. If (x & 7) != 7 evaluates to 0, then we must have bit x2 equal to 1.
When shifted left by 29, this will become the sign bit.
C. (x * x) >= 0
False. When x is 65,535 (0xFFFF), x*x is −131,071 (0xFFFE0001).
D. x < 0 || -x <= 0
True. If x is nonnegative, then -x is nonpositive.
E. x > 0 || -x >= 0
False. Let x be −2,147,483,648 (TMin32). Then both x and -x are negative.
F. x+y == uy+ux
True. Two’s-complement and unsigned addition have the same bit-level behavior,
and they are commutative.
G. x*~y + uy*ux == -x True. ~y equals -y-1. uy*ux equals x*y. Thus, the left hand side is equivalent to x*-y-x+x*y.