標籤:
今天看《程式設計語言概念》(Concepts of Programming Language),第七章“結合性”一節中有這麼一段: 某些電腦中的整數加法不具有結合性。例如,假設一個程式要計算“A + B + C + D”,其中A、C是很大的正數,B、D是絕對值很大的負數。在這種情況下,將B加到A並不會導致溢出,但將C加到A就會溢出。B和D與此類似。 這段話很好理解,因為只要是程式員,整數計算可能會溢出是基本的常識。但這段話只談到計算的中間結果發生溢出的情況。如果不考慮中間結果而將重點放在最終結果上,計算順序是否依然會對結果產生影響呢?也就是說,電腦中的加法滿足結合律嗎?即: (a + b) + c 是否一定等於 (a + c) + b 呢? 首先,如果每個中間結果以及最終結果都沒有溢出,可以肯定必然是滿足結合律的,否則就是電腦自身有錯誤。 如果中間結果發生了溢出會怎樣?我們不妨編寫一個簡單的程式驗證一下。這裡我們選擇四個整數,a和c是兩個是很大的正數,b和d是兩個很小的負數(即絕對值很大的負數)。
int a = 2147483392; //0x7fffff00;int b = -2147479553; //0x80000fff;int c = 2146500592; //0x7ff0fff0;int d = -2147421968; //0x8000f0f0;int sum1 = ((a + b) + c) + d;int sum2 = (a + b) + (c + d);int sum3 = (a + c) + (b + d);System.out.println("((a + b) + c) + d=" + sum1);System.out.println("(a + b) + (c + d)=" + sum2);System.out.println("(a + c) + (b + d)=" + sum3);
sum1為從左至右依次計算a+b+c+d的和;sum2先計算a+b和c+d,然後再計算二者的和;sum3則先計算a+c和b+d,然後再求和。通過代碼可以看出,sum1和sum2的中間結果沒有發生溢出,但sum3在計算a+c和b+d時都發生了溢出。下面來看看實際運行結果:
((a + b) + c) + d=-917537(a + b) + (c + d)=-917537(a + c) + (b + d)=-917537
可以看到無論順序如何結果都是正確的。所以我們可以得出結論:
電腦中的整數加法運算滿足結合律 這裡還有一個問題,在上面的例子中,雖然中間結果發生了溢出,但最終結果是沒溢出的。那如果最終結果也溢出了會怎麼樣?答案是不同計算順序得到的結果仍然一樣,只不過結果都是錯的(都溢出了)。這是由電腦本身有限的精度導致的,和結合律無關,所以這種情況仍然認為是符合結合律的。你可以自己寫個程式來驗證這一點。 不過要注意,這個結論是有限定條件的,對現代大多數電腦系統來說該結論都成立,因為這些系統通常都採用“二進位補碼”的方式來儲存整數,而二進位補碼的加法運算是符合結合律的。不滿足結合律的例子也是有的,比如BCD碼的加法運算。 ------------------------------------------------------------------寫到這裡,我想到了一個老題目:如何在不引入臨時變數的情況下交換2個整數的值?一般來說,有2種方法可以做到,一種是使用加法,另一種是使用異或:
a = a + b;b = a - b;a = a - b;
a = a ^ b;b = a ^ b;a = a ^ b;
有人說第一種方法有問題,原因是將a和b相加時可能會溢出。如果你看了這篇文章,就會知道這種說法是錯誤的了——雖然a+b可能會溢出,但最後仍能得到正確的結果。要說缺點,只是它的效率比第二種要低一些。但話說回來,它的可讀性卻要優於第二種。------------------------------------------------------------------
補碼簡介 下面簡單介紹一下補碼,如果對此不感興趣或已比較熟悉請略過。二進位補碼(Two‘s complement)採用“2^N的補”的方式儲存的整數編碼(其中N為整數的位長)。相比而言,另一種儲存方式“反碼”採用的是“1的補”,即逐位計算各個位的補(1的補為0,0的補為1,在二進位中這和取反是一樣的),因此反碼的英文名稱為“One‘s complement”。 補碼可以認為是對反碼的改進,這不但因為補碼中統一了“正零和負零”,還因為其計算也要比反碼容易。最主要的一點是補碼不用考慮進位(即溢出位),而反碼則必須考慮。補碼的另一個優點是其符號位同時也是計算位,因此計算時無需對正數和負數區別對待,這一點和反碼一樣。與補碼和反碼不同,原碼則必須同時考慮數的正負和進位,因此很少有系統採用原碼的方式來儲存整數。 下面分別用補碼和反碼的方式來計算“10 - 1”,以此加深理解。 由於大多數電腦只實現了加法而沒有減法,因此“10 - 1”實際上是轉換為“10 + (-1)”來計算的。為了簡單,這裡假設整數只有8位。 補碼的計算過程如下(-1的補碼為“1111 1111”): 0000 1010+ 1111 1111 —————— 1 0000 1001 結果發生了溢出,產生了一個進位,對補碼來說簡單忽略即可,因此最後的結果為“9”。 注意這裡的溢出屬於正常溢出。相比之下,如果正數+正數結果為負數,或負數+負數結果為正數時,則說明發生了不正常的溢出。正常的溢出結果仍然是正確的(這正是補碼的特性),而不正常的溢出得到的是錯誤的結果。 反碼的計算過程為(-1的反碼為“1111 1110”): 0000 1010+ 1111 1110 —————— 1 0000 1000 同樣發生了溢出,但此時不能忽略進位,否則將得到錯誤結果“8”,因此還需要把進位加到結果上: 0000 1000+ 1 —————— 0000 1001 得到最終結果“9”。
總結 最後再來簡單總結一下。在大多數電腦系統中,整數的加法運算滿足結合律。具體來說,如果最終結果沒有溢出,即使計算過程的中間結果出現了溢出也不會影響最終結果。而如果最終結果本身就是溢出的,改變計算順序仍然會得到一致的結果,這時候仍然認為是滿足結合律的。 雖然這篇文章對實際編程可能用處不大,因為我們通常只需注意最終結果不要溢出即可,對中間過程無需在意。但這篇文章為這個結論提供了一定的理論支援,以協助我們加深對電腦整數加法運算的理解。 參考資料: 有符號數的表示:http://en.wikipedia.org/wiki/Signed_number_representations反碼:http://en.wikipedia.org/wiki/Ones%27_complement補碼:http://en.wikipedia.org/wiki/Two%27s_complementBCD碼:http://en.wikipedia.org/wiki/BCD_code
電腦中整數加法滿足結合律嗎