這是一個關於數據溢出的問題,如果你對這個知識點感興趣請繼續往下看。引入問題之前我們先溫習一下溢出的概念:一個算數運算溢出,是指完整的整數結果不能放到數據類型的字長限制中去。
問題:寫出一個具有如下原型的函數,如果參數x 和 y 相加不會產生溢出,這個函數就返回 1.
int tadd_ok(int x,int y);
錯誤的解決辦法:
1 /* WARNING: This code is buggy. */ 2 boolean tadd_ok(int x,int y){ 3 int sum=x+y; 4 return (sum-x == y) && (sum-y == x); 5 }
這段代碼的思路是,如果發生了溢出 (x+y)-y 的結果就不會等於x,且 (x+y)-x 的結果也不會等於y.但實際測試結果是,函數 tadd_ok() 的返回值總是 true。為什么會產生這樣的結果呢?下面先來看另一段代碼:
public class demo01 { public static void main(String[] args) { int sum=2147483647+3; //IntMax:2147483647 System.out.println("sum="+sum); System.out.println("sum-3="+(sum-3)); } } //output: sum = -2147483646 sum-3 = 2147483647
從上面代碼中看到,2147483647+3 后發生了溢出,但是溢出后的值減3后又得到了原來的值2147483647。這看起來似乎不合常理,因為發生溢出后,超過數據類型字長限制的部分會被丟棄,這樣運算的結果就不會是我們預期的值,而且逆運算之后也更加不會得到原來的初值,但實際情況卻不是這樣的。
為了更直觀地解釋這種“詭異”的現象,我們假設機器字長為4位,x=9 和 x=12 的位表示分別為 [1001] 和 [1100],它們的和是21 (5位數字表示為 [10101]),但是機器位只有4位,於是最高位會被丟棄,我們就得到 [0101],也就是十進制的5.我們看到,在 x+y 的運算過程中發生了溢出,導致運算結果5與預期值21不符,但是模擬上面的程序,再進行逆運算會發生什么呢?即運算結果5減去9,如果5-9的結果等於12就說明與上面的程序運行結果相匹配,我們不妨仔細看看運算過程:
5-9 = 5 + (-9) = [0101] + [0111] = [1100] =12.
上面的計算過程可能還不夠清晰,'-9'的補碼為什么是 [0111],[0111] 不是 7 的補碼嗎?難道說 -9 =7 嗎?在這里給出兩種解釋方式:(這僅僅是我認為正確的思路,標准答案是什么還望高手不吝賜教)
第一種解釋:把 [0111] 並不是 '-9' 的補碼,而是表達式 (-9) 的4位二進制表示。我們知道,對於任意整數值 x ,計算表達式 -x 和 ~x+1 得到的結果完全一樣。利用這個結論,我們可以得到 '-9' 的位表示推理過程 [1001] 取反得到 [0110], 加1得 [0111].
第二種解釋:按照計算機中有符號數的存儲規則, '-9' 的補碼按照不同位數表示是 [10111] 、[110111]、[1110111]...但是我們已經假設了機器字長為4位,所以必須丟棄超過限定字長的部分,丟棄后得到的補碼就是 [0111].這與第一種解釋相吻合。
這樣我們就能得出一個結論:即使加法產生了溢出,進行逆運算后的值依然等於參與運算的初值,即無論加法是否溢出,而(x+y)-y 總是會求值得到 x。
好了,我們已經利用這個簡單的例子說明這個運算時可逆的,也就證明了上面紅體字部分的正確性,也就說明給出的解決辦法是錯誤的。
正確的解決辦法:
1 int tadd_ok(int x,int y){ 2 int sum = x+y; 3 int neg_over = x < 0 && y < 0 && sum >= 0; 4 int pos_over = x >=0 && y >= 0 && sum < 0; 5 return !neg_over && !pos_over; 6 }
后記:數據的溢出往往會造成程序的不可靠性,所以在程序中應該考慮到這個問題所可能產生的后果,並采取必要的措施來處理這種情況。當然,這樣做的前提是你已經對溢出有了比較深入的理解。
注:本文整理自《深入理解計算機系統》.