負數的取模運算
我們知道,在不同的語言中,對負數執行取模運算,結果有可能會是不同的。例如,(-11)%5在python中計算的結果是4,而在C(C99)中計算的結果則是-1。
truncate除法 && floor除法
在大多數程式語言中,如果整數a不是整數b的整數倍數的話,那麼a、b做除法產生的實際結果的小數部分將會被截除,這個過程稱為截尾(truncation)。如果除法的結果是正數的話,那麼一般的程式語言都會把結果趨零截尾,也就是說,直接把商的小數部分去除。但是如果除法的結果是負數的話,不同的語言通常採用了兩種不同的截尾方法:一種是趨零截尾(truncate toward zero),另一種是趨負無窮截尾(truncate toward negative infinity);相應的,兩種除法分別被稱為truncate除法和floor除法。
事實上,可以認為不管除法的結果是正是負,truncate除法都是趨零結尾;而floor除法都是趨負無窮結尾。
取模運算
取模運算實際上是計算兩數相除以後的餘數。假設q是a、b相除產生的商(quotient),r是相應的餘數(remainder),那麼在幾乎所有的計算系統中,都滿足a=b*q+r,其中|r|<|a|。因此r有兩個選擇,一個為正,一個為負;相應的,q也有兩個選擇。如果a、b都是正數的話,那麼一般的程式語言中,r為正數;或者如果a、b都是負數的話,一般r為負數。但是如果a、b一正一負的話,不同的語言則會根據除法的不同結果而使得r的結果也不同,並且一般r的計算方法都會滿足r=a-(a/b)*b
。
常見語言
(1)C/Java語言
C/Java語言除法採用的是趨零截尾(事實上,C89對於除數或被除數之一為負數情況的結果是未定義的;C99才正式確定了趨零截尾),即truncate除法。它們的取模運算子是%,並且此運算子只接受整型運算元。一個規律是,取模運算的結果的符號與第一個運算元的符號相同(或為0)。因此(-11)%5=-11-[(-11)/5]*5=-11-(-2)*5=-1
(2)C++語言
C++語言的截尾方式取決於特定的機器。如果兩個運算元均為正,那麼取模運算的結果也為正數(或為0);如果兩個運算元均為負數,那麼取模運算的結果為負數(或為0);如果只有一個運算元為負數,那麼取模運算的結果是取決於特定實現的。
(3)Python語言
Python語言除法採用的是趨負無窮截尾,即floor除法。它的取模運算子也是%,並且此運算子可以接受浮點運算元。一個類似的規律是,取模運算的結果的符號與第二個運算元的符號相同。因此(-11)%5=-11-[(-11)/5]*5=-11-(-3)*5=4
。
這裡需要注意的是,Python 3.x中"/"運算子的意義發生了變化,"/"產生的結果將不會再進行截尾;相應的"//"運算子的結果才會進行截尾。
(4)Common Lisp
Common Lisp的特殊操作符(special operator)"/"的結果是分數,因此不會存在截尾的問題。但是Common Lisp提供了TRUNCATE函式和FLOOR函式分別對應上述的兩種除法。相應的,Common Lisp的REM函式類似於C/Java語言中的取模運算;而MOD函式類似於Python語言中的取模運算。
//**********************************
以下引入c++ primer 關於整型賦值的定義
整型的賦值
物件的型別決定物件的取值。這會引起一個疑問:當我們試著把一個超出其取值範圍的值賦給一個指定型別的物件時,結果會怎樣呢?答案取決於這種型別是 signed 還是 unsigned 的。
對於 unsigned 型別來說,編譯器必須調整越界值使其滿足要求。編譯器會將該值對 unsigned 型別的可能取值數目求模,然後取所得值。比如 8 位的unsigned char,其取值範圍從 0 到 255(包括 255)。如果賦給超出這個範圍的值,那麼編譯器將會取該值對 256 求模後的值。例如,如果試圖將 336 儲存到 8 位的 unsigned char 中,則實際賦值為 80,因為 80 是 336 對 256 求模後的值。
對於 unsigned 型別來說,負數總是超出其取值範圍。unsigned 型別的物件可能永遠不會儲存負數。有些語言中將負數賦給 unsigned 型別是非法的,但在 C++ 中這是合法的。
C++ 中,把負值賦給 unsigned 物件是完全合法的,其結果是該負數對該型別的取值個數求模後的值。所以,如果把 -1 賦給 8 位的 unsigned char,那麼結果是 255,因為 255 是 -1 對 256 求模後的值。
當將超過取值範圍的值賦給 signed 型別時,由編譯器決定實際賦的值。在實際操作中,很多的編譯器處理 signed 型別的方式和 unsigned 型別類似。也就是說,賦值時是取該值對該型別取值數目求模後的值。然而我們不能保證編譯器都會這樣處理 signed 型別。