1. 程式人生 > >如何檢測整型相加溢位(overflow)

如何檢測整型相加溢位(overflow)

前言:

本文主要討論如何判斷整型相加溢位(overflow)的問題. 我們知道計算機裡面整型一般是有限個位元組(4 bytes for int)表示, 正是因為只能用有限個位元組表示一個整型變數, 由此帶來一個可能的問題: 溢位(overflow). 所謂整型溢位(overflow), 是說一個整數的值太大或者太小導致沒有用給定的有限個(比如四個位元組沒法存超過2^31 – 1的有符號正整數)位元組儲存表示. 這個整型溢位(overflow)問題一般的時候不會注意到也並不危險, 但是在做整型加法或者乘法的時候就有可能出現並且給程式帶來未定義的行為. 這裡我們主要討論如何判斷整型相加溢位(overflow)的兩種方法以及各自優缺點.

整型相加溢位(overflow)的原因:

前言裡面也已經提到了, 計算機中的的整數是用有限個位元組表示的, 假設用k個位元組表示一個整型變數, 那麼這個變數可以表示的有符號整數的範圍是-2^(8k-1) ~ 2^(8k-1) – 1  ,  那麼兩個正整數或者兩個負整數相加就有可能超過這個整型變數所能表示的範圍, 向上超出>2^(8k-1) – 1我們稱之為向上溢位, 向下超出<-2^(8k-1), 我們稱之為向下溢位. 注意這裡兩個整數符號相同是整型相加溢位(overflow)的必要條件, 也就是說只有符號相同的兩個整數相加才有可能產生溢位(overflow)問題. 這個可以這麼理解: 你想要是兩個符號不同的兩個整數, 他們相加, 那麼這個和的值的絕對值一定是比單個相加數和被相加數都小, 既然相加數和被相加數都能用現有整型變量表示, 那麼兩個不同整數的相加結果怎麼樣都可以用現有的整型範圍的變數儲存下來而不溢位(overflow). 所以結論: 只有符號相同的整數相加才有可能才生溢位(overflow).

整型相加溢位(overflow)的檢測:

那麼接下來的問題就是如何檢測到溢位(overflow)的產生的, 更具體的, 給定兩個整型, 比如int a, int b, 我們做加法a+b, 如何去判斷這個相加的結果是正確結果還是說是溢位(overflow)的結果. 下面我們給出兩種方法(後面我們會討論方法二是更好的方法), 並且做出解釋或者說不太嚴格的證明方法的正確性, 也就是為什麼這麼做就能保證溢位(overflow)的檢測的正確性.

方法一, 計算相加的結果, 判斷結果的符號, 兩個正整數相加結果為負數, 或者兩個負整數相加結果為正數, 那麼就是溢位(overflow)了. 實現程式碼如下:

1 int addInt(int a, int b) {
2 int res = a + b;
3 if(a > 0 && b > 0 && res < 0) throw overflow_exception;
4 if(a < 0 && b < 0 && res > 0) throw overflow_exception;
5 return res;
6 }

這個方法的原理是這樣, 計算機裡面有符號整數是利用補碼的形式表示的, 第一位是符號位, 0表示整數, 1表示負數. 我們拿一個位元組的整型來舉例, 一個位元組的有符號數可以表示的範圍就是-128 ~ 127, 那麼兩個一個位元組的正整數相加的最大範圍就是254, 那麼其中128 ~ 254就是溢位(overflow)的值, 是不能用一個位元組儲存下的值, 這個值用一個位元組表示的時候最高位是1, 在有符號整數系統裡面這個值其實被當成了負數. 同理, 負數相加的時候最小可以到達-256, 根據補碼的表示對應的正整數取反加1就是對應的補碼, 那麼對應的正整數的最高位是1, 現在取反以後就變成0, 也就是說兩個比較大的負數相加的結果其實變成了正數. 這就是上述方法的理論基礎.

方法二, 使用減法, 利用現有整型的最大或者最小極值減去某個加數(減法相當於變號, 從而保證沒有溢位(overflow)的發生), 和另一個加數比較大小進行判斷. 實現程式碼如下:

1 int addInt(int a, int b) {
2 if(a > 0 && b > 0 && a > INT_MAX - b) throw overflow_exception;
3 if(a < 0 && b < 0 && a < INT_MIN - b) throw overflow_exception;
4 return a + b;
5 }

這個方法其實不用太多的解釋, 簡單的數學知識就能解釋其中的原理, 由於減法保證了不會溢位(overflow), 又前面我們保證了兩個數都是正整數, 所以形如 a > INT_MAX – b的判斷是安全並且總是正確的. 而且這個檢測方法的正確性可以通過移位就看得懂了, 不像方法一, 需要一定的計算機底層的知識才能解釋說通.

方法一和方法二比較: 我自己一開始的時候思考利用方法一這樣的結論去判斷溢位(overflow), 但是我心裡其實不放心, 因為方法一的前提是”兩個正整數相加溢位的充要條件是符號位變成1, 也就是結果變成了負數”, 這樣的結論或者事實對於我或者一般人來講其實並不是那麼的直觀或者理所當然, 當然了我自己又用那個一個byte的例子試著去解釋, 結論還是正確的,  所以方法一相比較方法二而言並不直觀. 另一方面有說法說是溢位的時候結果其實不確定, 上面在方法一里面我們的分析只是理論上的分析, 編譯器有可能做出相關的優化或者對溢位結果做出調整, 那麼可能就出現未定義的行為了, 所以綜上所述, 方法二應該是比較更為安全和合理並且更為直觀的首選檢測整數相加溢位(overflow)的方法.

更新: 感謝網友Stanley的留言, 提供了第三種方法的檢測, 其實也就是方法一的bit operation版本, 通過位操作, 我們可以判斷求和結果x是否與a和b還同號, 如果同時不同號(也就是sign bit不相同了), 那我們就相當於檢測到了溢位. Stanley的版本稍微反了反, 我認為應該是下面這種情況才是溢位, 如有錯誤, 敬請指正. Thanks!

1 x = a + b;
2 if ((x^a) < 0 && (x^b) < 0)
3 {
4 //overflow, do something
5 }

結束語:

本文主要討論瞭如何判斷整型相加溢位(overflow)的問題, 主要總結了整型相加溢位(overflow)的原因, 並給出了兩種檢測整型相加溢位(overflow)的方法, 方法一基於計算結果的正負, 方法二基於把加法轉化為減法. 本文同時給出了兩種方法的比較, 並且指出方法二應該是首選的檢測方法.