不同語言的負數取余問題


不同語言的負數取余問題

問題的出現

偶然在leetcode上看到一道這樣的題:

Given a 32-bit signed integer, reverse digits of an integer.  

翻譯成中文就是:

給定一個32位有符號整數,將整數由低位到高位反向輸出,例:
輸入:1230
返回:321

題目很簡單,有很多種實現方式,大概十分鍾左右就在腦海中想到了一個自認為最好的解法,之后用電腦實驗:

int reverse_num(int x,int res)
{
    if(!x) return res;
    return reverse_num(x/10,(res*10+x%10));
}
int main()
{
    int val=0;
    val=reverse_num(-9870,val);
    cout<<val<<endl;
}

輸出結果:

-789

解決!!其實用循環也可以高效地實現,為什么要用遞歸?因為遞歸總是能寫出簡潔優美的代碼(其實是為了裝X...)。

作為習慣,我再用python實現一遍,同樣的代碼結構:

def reverse(x,res):
    if x==0:
        return res
    return reverse(x/10,(res*10+x%10))


def main():
    num=-9870
    res=0
    val=reverse(num,res)
    print val

輸出結果:

RuntimeError: maximum recursion depth exceeded

What the ****!!??

我抬起我顫抖的小手移動着屏幕一行一行地檢查代碼,發現並沒有錯。

本以為大局已定,結果被極限反殺,當然不能就這么算了,於是開啟debug模式

程序畢竟簡單,很快就發現了問題:

>>> print -987/10
-99
>>> print -99/10
-10
>>> print -10/10
-1
>>> print -1/10
-1
>>> print -1/10
-1
>>> print -1/10
-1

到這里,各位觀眾老爺們應該也看出問題來了,從上述運行結果來看,-987/10的結果居然是-99而不是-98,-1/10的結果是-1,再次執行-1/10的結果當然又是是-1,而遞歸的退出條件為x=0,這導致遞歸無限執行,所以棧溢出,對於-1/10為什么等於-1這個結果,當然是既震驚又好奇,接下來便是一探究竟!!


問題的解決

根據資料顯示,目前使用的主流除法處理有兩種:Truncated Division和Floored Division,這兩種除法在對正數除法的處理上是一致的,而在對負數處理上則略有不同。

首先,需要了解的是,對於整數的除法,都滿足這個公式:

m=q*n+r
m為被除數
n為除數
r為余
q為商
m,n,q,r都為整數

即:

14=3*4+2
14除以4得3余2

這不是很標准了么,那為什么還會有分歧呢?

正整數的除法當然是沒有問題的,但是如果遇到負數的除法,比如

-8/5

結果可以是兩種,即:

-8 = 5*(-1)+(-3)

在這種情況下,商為-1,余數為-3

或者:

-8 = 5*(-2)+2  

而在這種情況下,商為-2,余數為2

這兩種除法的分歧導致了上述的不同語言的不同標准。


官方標准

根據官方資料顯示,在C89和C98的標准中,對此並沒有做規定,把實現留給了編譯器來決定,這會導致什么結果呢?就是我們常說的實踐出真知在這種情況下可能會得到一個錯誤的結果!

不管你的編譯器用的是C/C++類標准還是python類標准,你得出的結論就是單一標准,寫出來的代碼在另一個編譯器下並不具有移植性。


思考

由此引發博主的一個思考:有時候在研究這類計算機問題時,我們不能單單以某個平台上的實驗結果作為標准答案,這是有所偏頗的,編譯工具鏈(腳本解釋器)常常有多個版本,而單一平台卻無法覆蓋所有編譯器(腳本解釋器)版本,可能僅僅是選擇了最通用的版本或者是從多個分歧版本中選其一。


統一標准

在C99的標准中,明確規定了"truncation toward zero",即向0取整。在這個模式下,負整數除法中,商為負數時,小數部分是往靠近0的方向進行取整,即舍棄小數部分,C++和Java則沿用了C的方式,還是那個例子:

-8/5=-1.6
商為-1.6,但是因為是整數除法,小數部分向0取整,商為-1,所以余數為-3,即:
-8 = 5*(-1)+(-3)

而在python中,應用的則是小數部分向進1的方向取整,舉個例子:

-8/5=-1.6
商為-1.6,但是因為是整數除法,小數部分向進1取整,商為-2,所以余數為2,即:
-8 = 5*(-2)+2  

對於兩種不同語言的除法實現,我們已經有了基本的了解,但是事情就這樣結束了嗎?並沒有!!

上述討論的僅僅是

正整數/正整數
負整數/正整數

還有兩種情況怎么能漏呢?

負整數/負整數
正整數/負整數

對於負整數/負整數的除法,這兩種除法有沒有區別呢?

既然在C99之后對於C帶符號整數除法的標准中已經統一,我們還是可以選擇用上機運行代碼的方式來檢驗


正整數/負整數

C++代碼片段:

int div = 8/-5;
int mod = 8%-5;
int div1= 5/-8;
int mod1= 5%-8;
cout<<"8/-5="<<div<<endl<<"8%-5="<<mod<<endl;
cout<<"5/-8="<<div1<<endl<<"5%-8="<<mod1<<endl;

運行結果:

8/-5=-1
8%-5=3
5/-8=0
5%-8=5

python代碼片段:

print "%s %d" %("8/-5=",8/-5)
print "%s %d" %("8%-5=",8%-5)
print "%s %d" %("5/-8=",5/-8)
print "%s %d" %("5%-8=",5%-8)

運行結果:

8/-5=2
8%-5=-2
5/-8=-1
5%-8=-3

在正整數/負整數的示例中,C/C++和python的標准如上面所述,商為負數時,商的小數部分一個舍一個入,導致的結果也不一樣。


負整數/負整數

C/C++代碼片段:

int div = -8/-5;
int mod = -8%-5;
int div1= -5/-8;
int mod1=-5%-8;
cout<<"-8/-5="<<div<<endl<<"-8%-5="<<mod<<endl;
cout<<"-5/-8="<<div1<<endl<<"-5%-8="<<mod1<<endl;

運行結果:

-8/-5=1
-8%-5=-3
-5/-8=0
-5%-8=-5

python代碼片段:

print "%s %d" %("-8/-5=",-8/-5)
print "%s %d" %("-8%-5=",-8%-5)
print "%s %d" %("-5/-8=",-5/-8)
print "%s %d" %("-5%-8=",-5%-8)

運行結果:

-8/-5=1
-8%-5=-3
-5/-8=0
-5%-8=-5

兩種語言的輸出結果是一樣的,很多盆友就開始有點懵了,不是說標准不一樣,小數部分取整的方向不一樣嗎?

如果你仔細看上面的例子,就會發現一個前提條件,商為負數時,取整有差異,而這里商為正數,如-5/-8=0.625,則按照正整數的除法的統一規則(別忘了!正整數的處理兩種語言是一致的):

商取0,則:
-5 = 0*(-8)+(-5)
余數為-5

簡單總結

python和C/C++/JAVA在商為負數的除法處理上有兩種標准,在python中,商的小數部分進位,而在C/C++/JAVA中,商的小數部分被舍棄。(網上資料顯示Ruby的處理和python一個標准,博主未進行測試,有興趣的小伙伴可以嘗試一下)

好了,關於不同語言的有符號整數除法的討論就到此為止啦,如果朋友們對於這個有什么疑問或者發現有文章中有什么錯誤,歡迎留言

原創博客,轉載請注明出處!

祝各位早日實現項目叢中過,bug不沾身.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM