謎題01: 奇數性
下面方法的目的是確定其唯一的參數是否為奇數。這個方法可行嗎?
public static bool IsOdd(int i)
{
return i % 2 == 1;
}
解惑01: 奇數性
奇數可定義為被2整除餘數為1的整數。運算式i%2計算的是i除以2時所產生的餘數,因此看起來這個程式應該可行。遺憾的是,它不行;在四分之一的時間裡它返回的都是錯誤的答案。
為什麼是四分之一?因為在所有的int數值中,有一半是負數,而IsOdd方法對所有負奇數的判斷都會失敗。在任何負整數上調用該方法都會返回false,無論該整數是偶數還是奇數。
這是C#對取餘操作符(%)的定義所產生的後果。該操作符被定義為對所有的int數值a和所有的非零int數值b,都滿足下面的恒等式:
(a / b) * b + (a % b) == a
換句話說,如果用b整除a,將商乘以b,然後加上餘數,那麼就得到了最初的值a[C#語言規範 7.7.3]。該恒等式具有正確的含義,但是當與C#的截尾整數整除操作符[C#語言規範 7.7.2]相結合時,它就意味著:
當取餘操作返回一個非零的結果時,它與左運算元具有相同的正負符號。
IsOdd方法以及它所基於的對術語“奇數”的定義都假設所有的餘數都是正數。雖然該假設對某些種類的整除是有意義的,但是C#的取餘操作是與捨棄整除結果小數部分的整數整除操作完全符合的。
當i是一個負奇數時,i % 2等於-1而不是1,因此IsOdd方法錯誤地返回false。為了防止這種意外,
請測試你的方法在為每一個數值型參數傳遞負數、零和正數數值時,其行為是否正確。
這個問題很容易改正。只需將i % 2與0而不是與1比較,並且使用相反的比較含義即可:
public static bool IsOdd(int i)
{
return i % 2 != 0;
}
如果正在一個強調效能的環境中使用IsOdd方法,那麼用位操作符AND(&)替代取餘操作符會顯得更好:
public static bool IsOdd(int i)
{
return (i & 1) != 0;
}
第二個版本運行起來可能比第一個版本要快得多,這取決於你使用的是什麼樣的平台和虛擬機器,並且不太可能出現運行得更慢的情況。按常規來說,整除和取餘操作與其他的算術和邏輯操作相比要慢一些。
倉促地最佳化是不好的,但是在上述情況下,更快的版本與最初的版本一樣清晰明白,所以沒有任何理由偏愛最初的版本。
總之,無論何時使用了取餘操作符,都要考慮運算元和結果的符號。該操作符的行為在其運算元非負時是一目瞭然的,但是當一個或兩個運算元是負數時,它的行為就不那麼顯而易見了。
C#解惑總目錄