Java和Python中的整數(shù)除法,取余,舍入

關于除法,你也許覺得沒什么值得談論的,畢竟小學的時候體育老師就教過我們了。然而對于編程中使用的除法,我覺得還是有很多值得注意的細節(jié)的。為什么我想深究一下?因為我日常主要使用Java和Python編程,而它們的除法在細節(jié)上有很多不同之處,全是坑啊…所以接下來我也將著重于Java和Python,但是相信我,就算你不用Java和Python,也會有所收獲的。

1.整數(shù)除法

對兩個不能整除的整數(shù)做除法,就要面對舍入的問題。和大多數(shù)編程語言一樣,Java的基本策略是向零取整(round to zero),也就是向絕對值變小的方向取整。舉幾個香甜的小栗子:3/2=1, -3/2=-1。而對于Python而言,情況就有所不同了。

>>>-1/10
-1```
顯然如果按照Java的取整策略,-1/10應該得0,而Python給出的結果是-1。事實上Python的取整方式是向下取整,也就是向著數(shù)軸上負無窮的方向取整。
好吧,Java和Python的取整方式不同,奪大點事兒啊…那么如果我們要在Python下采用向零取整的結果,咋整?一種比較直接的方式是:
```python
>>>int(float(-1)/10)
0```
     
##2.取余

誰說沒大事?(╰( ̄▽ ̄)╭) 大事來了!

Java和Python整數(shù)除法都遵循下面這個公式:

>(a/b)*b+c=a

也就是說:

>a mod b=c=a-(a/b)*b

這里的/表示的是整數(shù)除法。既然它們的取整方式不一樣,那么取余也會受到影響:

>For Java: -2 % 3==-2
For Python: -2 % 3==1

在某些實際應用中,我們可能會被要求得到一個整數(shù)的各位數(shù)字。如果輸入的整數(shù)的正的,Java和Python都可以用相同的方法來解決: 
```python
def func(a):
    pos, res=1, []
    while a/pos:
        res+=(a/pos)%10,
        pos*=10
    return res```

Java代碼也差不多就是這樣了。但如果輸入的整數(shù)是一個負數(shù),Java版本的代碼還是可以得到正確的結果,而Python不能(曾經(jīng)在這里被坑的,舉手)。那怎樣用Python正確地搞定這個問題嘞?可以先去絕對值和符號,當正數(shù)來處理,最后再在結果里搭上符號。 
##3. Follow-ups
###3.1 Python中的另一個除法操作
我們知道,在Python中,基本的除號“/”是被重載了的。當兩個操作數(shù)都是整數(shù)時,進行整數(shù)除法,得到整數(shù)結果,否則進行浮點數(shù)除法(真除法),得到浮點數(shù)結果。從Python 2.2開始,另一個除號被引入://,它只執(zhí)行整數(shù)除法。注意,//的結果類型依操作數(shù)而定。
```python
>>>1.0/2
0.0
>>>1.0//2.0
0.0
>>>1//2
>0```

另外,如果想同時得到商和余數(shù),可以使用內建的函數(shù)divmod,結果是一個tuple。
```python
>>>divmod(7, 2)
(3, 1)
>>>divmod(7.0, 2)
(3.0, 1.0)```

###3.2 Python中的舍入
除了缺省的舍入方式,Python還有多種舍入可供選擇。
**Floor rounding:**
```python
>>>import math
>>>math.floor(1.2)
1.0
>>>math.floor(-1.2)
-2.0```

**Ceiling rounding:**
```python
>>>math.ceil(1.2)
2.0
>>>math.ceil(-1.2)
-1.0```

**Round-off:**
```python
>>>round(0.5)
1.0
>>>round(-0.4)
-0.0
>>>round(-0.5)
-1.0```

內嵌的round函數(shù)也可以一個指定保留小數(shù)位數(shù)的參數(shù):
```python
>>>round(0.21, 1)
0.2
>>>round(0.21, 2)
0.21```

**Caution !**
```python
>>>round(2.675, 2)
2.67

咦?bug啦?!當然不是。這里要明確一件事:計算機只認識0,1(量子計算機?懵)。就是說,我們輸入的十進制數(shù),在計算機內部都是用二進制來表示的。有的十進制數(shù)可以用二進制準確地表示出來,比如十進制的0.125可以表示為0b0.001;然而很多的小數(shù)是沒法用二進制數(shù)精確表示的,計算機里存儲的是它們的近似值,例如十進制的0.1,用二進制表示,可以近似為: 0b0.00011001100110011001100110011001100110011001100110011010,所以當我們把它換回十進制數(shù)以輸出或者使用,得到的值就是0.1000000000000000055511151231257827021181583404541015625。也就是說,0.1在計算機里并不是剛好等于1/10的。

>>>0.1+0.2
0.30000000000000004

同樣,當我們運行round()函數(shù),也是對計算機中實際存儲的值近似取舍。2.67實際上近似為2.67499999999999982236431605997495353221893310546875,你看第三位小數(shù)是4,那么round(2.675, 2)就相當于round(2.674, 2),結果當然是2.67。值得注意的是,這種現(xiàn)象是廣泛存在于各種計算機和各種編程語言的,不是bug,只是有的語言選擇了不讓你看到。

3.3 Java中的舍入

Java提供了floor和ceil方法來實現(xiàn)向下和向上取整。

Math.floor(2.9)
Math.ceil(2.1)

這倆函數(shù)簡單方便,居家旅行必備。另外Java中也有個round函數(shù),可以實現(xiàn)各種復雜的取整。

System.out.println(Math.round(0.5));
//輸出 1
System.out.println(Math.round(-0.5));
//輸出 0
System.out.println(Math.round(-0.51));
//輸出 -1```

這什么鬼!Keep Calm and Carry On!
數(shù)學上有多種不同的策略來進行取整,比如我們體育老師教的四舍五入。各種取整策略的共同點就是要做真值作近似,那就會引入偏差。四舍五入顯然并不是一種公平的策略(想想0~4的舍和5~9的得)。
有一個叫做銀行家舍入(Banker’s Rounding)的東西,不造你聽過沒,反正我是最近才知道的。事實上.NET和VB6都是默認采用這種方式,而且IEEE 754默認采用這種Rounding。Banker’s Rounding 也就是** round to even **策略。
假設當前考慮那位的數(shù)字是d(其實d就是將不被保留的第一位),如果d<5,則舍(round to zero);如果d>5,則入(round away from zero);而當d==5時,就要根據(jù)d前后的數(shù)位來確定往哪邊取了。
>1) 如果d之后存在非零的數(shù)位,則入;
 2)如果d之后不存在非零的數(shù)位,則看d之前的一個數(shù)位,用c表示:
    a.如果c是奇數(shù),則入;
  b.如果c是偶數(shù),則舍。

再來一把栗子,對下列數(shù)保留0位小數(shù),
第一位小數(shù)就是d,整數(shù)位就是c:
>BankRound(0.4)==0,  BankRound(0.6)==1,  BankRound(-0.4)==0,  BankRound(-0.6)==-1
BankRound(1.5)==2.0,  BankRound(-1.5)==-2.0,  BankRound(2.5)==2.0,  BankRound(-2.5)==-2.0
BankRound(1.51)==2.0,  BankRound(-1.51)==-2.0,  BankRound(2.51)==3.0,  BankRound(-2.51)==-3.0

可以看出,Banker’s Rounding對正數(shù)和負數(shù)的處理是對稱的,因此不會引入符號帶來的偏差。另外它以均等的幾率來舍入數(shù)位(考慮c, c有各一半的幾率為奇數(shù)和偶數(shù)),所以多次舍入后與真值的差別會較小。

扯了這么多,跟Java的**Math.round( )**有什么關系呢?我也是寫到這才發(fā)現(xiàn),好像沒什么軟(luan)關系。因為它并沒有遵循Banker’s rounding。而是按照以下策略進行取整:
當考慮的數(shù)位d不是5,d<5就舍,d>5則入。
當d==5:
>a.如果d的右邊有非零數(shù)位,則入;
>b.如果d的右邊沒有非零數(shù)位,則** round to ceiling**,即對負數(shù)舍,對正數(shù)入。

[Java文檔里是這么表述的](http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html)

還有還有, 在Java里可以使用** BigDecimal **和** RoundingMode **實現(xiàn)更通用的取整方式。
```java
double d=-2.5;
BigDecimal bd=new BigDecimal(d);
double nd=bd.setScale(0,
RoundingMode.HALF_EVEN).doubleValue();
System.out.println(nd);
//輸出 -2.0```

** setScale **的第一個參數(shù)是保留的小數(shù)位數(shù),第二個參數(shù)是舍入模式。可選的舍入模式有:
**HALF_EVEN**, 也就是銀行家方式;
**HALF_UP**, 四舍五入;
**HALF_DOWN**, 五舍六入;
**CEILING、FLOOR**, 向正無窮、負無窮方向;
**UP、DOWN***, 向零和遠離零;
**UNNECESSARY**, 斷言舍入后的值和原值相等,也就是不需要舍入。如果斷言錯了,拋出**ArithmeticException**異常。

先寫到這,比較粗糙,但是希望你有所收獲吧。歡迎討論,有話好好說(╰( ̄▽ ̄)╭)

本文遵守[知識共享協(xié)議:署名-非商業(yè)性使用-相同方式共享 (BY-NC-SA)](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/)及[簡書協(xié)議](http://www.lxweimin.com/p/c44d171298ce)
轉載請注明:**作者[曾會玩](http://www.lxweimin.com/users/5e81a35c8586/latest_articles)**
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • 背景 在java中float賦值給double,會產生精度問題。 輸出為2.0999999046325684。 小...
    我叫小小強閱讀 19,255評論 2 23
  • 個人筆記,方便自己查閱使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik閱讀 67,741評論 0 5
  • 1. 背景 解一個bug的時候,發(fā)現(xiàn)計算比賽的讓桿時(高爾夫打球時為了平衡水平,采用高手給菜鳥讓一定的桿數(shù)),同樣...
    pingpong_龘閱讀 1,779評論 1 1
  • http://python.jobbole.com/85231/ 關于專業(yè)技能寫完項目接著寫寫一名3年工作經(jīng)驗的J...
    燕京博士閱讀 7,606評論 1 118