Java和Python中的整數除法,取餘,舍入

關於除法,你也許以爲沒什麼值得談論的,畢竟小學的時候體育老師就教過咱們了。然而對於編程中使用的除法,我以爲仍是有不少值得注意的細節的。爲何我想深究一下?由於我平常主要使用Java和Python編程,而它們的除法在細節上有不少不一樣之處,全是坑啊…因此接下來我也將着重於Java和Python,可是相信我,就算你不用Java和Python,也會有所收穫的。html

1.整數除法

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

>>>-1/10
-1

顯然若是按照Java的取整策略,-1/10應該得0,而Python給出的結果是-1。事實上Python的取整方式是向下取整,也就是向着數軸上負無窮的方向取整。
好吧,Java和Python的取整方式不一樣,奪大點事兒啊…那麼若是咱們要在Python下采用向零取整的結果,咋整?一種比較直接的方式是:python

>>>int(float(-1)/10)
0

2.取餘

誰說沒大事?( ̄▽ ̄)大事來了!編程

Java和Python整數除法都遵循下面這個公式:api

(a/b)*b+c=a

也就是說:oracle

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

這裏的/表示的是整數除法。既然它們的取整方式不同,那麼取餘也會受到影響:編程語言

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

在某些實際應用中,咱們可能會被要求獲得一個整數的各位數字。若是輸入的整數的正的,Java和Python均可以用相同的方法來解決: 函數

def func(a):
    pos, res=1, []
    while a/pos:
        res+=(a/pos)%10,
        pos*=10
    return res

Java代碼也差很少就是這樣了。但若是輸入的整數是一個負數,Java版本的代碼仍是能夠獲得正確的結果,而Python不能(曾經在這裏被坑的,舉手)。那怎樣用Python正確地搞定這個問題嘞?能夠先去絕對值和符號,當正數來處理,最後再在結果裏搭上符號。lua

3. Follow-ups

3.1 Python中的另外一個除法操做

咱們知道,在Python中,基本的除號「/」是被重載了的。當兩個操做數都是整數時,進行整數除法,獲得整數結果,不然進行浮點數除法(真除法),獲得浮點數結果。從Python 2.2開始,另外一個除號被引入://,它只執行整數除法。注意,//的結果類型依操做數而定。code

>>>1.0/2
0.0
>>>1.0//2.0
0.0
>>>1//2
>0

另外,若是想同時獲得商和餘數,可使用內建的函數divmod,結果是一個tuple。

>>>divmod(7, 2)
(3, 1)
>>>divmod(7.0, 2)
(3.0, 1.0)

3.2 Python中的舍入

除了缺省的舍入方式,Python還有多種舍入可供選擇。
Floor rounding:

>>>import math
>>>math.floor(1.2)
1.0
>>>math.floor(-1.2)
-2.0

Ceiling rounding:

>>>math.ceil(1.2)
2.0
>>>math.ceil(-1.2)
-1.0

Round-off:

>>>round(0.5)
1.0
>>>round(-0.4)
-0.0
>>>round(-0.5)
-1.0

內嵌的round函數也能夠一個指定保留小數位數的參數:

>>>round(0.21, 1)
0.2
>>>round(0.21, 2)
0.21

Caution !

>>>round(2.675, 2)
2.67

咦?bug啦?!固然不是。這裏要明確一件事:計算機只認識0,1(量子計算機?懵)。就是說,咱們輸入的十進制數,在計算機內部都是用二進制來表示的。有的十進制數能夠用二進制準確地表示出來,好比十進制的0.125能夠表示爲0b0.001;然而不少的小數是無法用二進制數精確表示的,計算機裏存儲的是它們的近似值,例如十進制的0.1,用二進制表示,能夠近似爲: 0b0.00011001100110011001100110011001100110011001100110011010,因此當咱們把它換回十進制數以輸出或者使用,獲得的值就是0.1000000000000000055511151231257827021181583404541015625。也就是說,0.1在計算機裏並非恰好等於1/10的。你看:

>>>0.1+0.2
0.30000000000000004

一樣,當咱們運行round()函數,也是對計算機中實際存儲的值近似取捨。2.67實際上近似爲2.67499999999999982236431605997495353221893310546875,第三位小數是4,那麼round(2.675, 2)就至關於round(2.674, 2),結果固然是2.67。值得注意的是,這種現象是普遍存在於各類計算機和各類編程語言的,不是bug,只是有的語言選擇了不讓你看到。

3.3 Java中的舍入

Java提供了floor和ceil方法來實現向下和向上取整。

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

這倆函數簡單方便,居家旅行必備。另外Java中也有個round函數,能夠實現各類複雜的取整。

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!
數學上有多種不一樣的策略來進行取整,好比咱們體育老師教的四捨五入。各類取整策略的共同點就是要作真值做近似,那就會引入誤差。四捨五入顯然並非一種公平的策略(想一想0~4的舍和5~9的得)。
有一個叫作銀行家舍入(Banker’s Rounding)的東西,不造你聽過沒,反正我是最近才知道的。事實上.NET和VB6都是默認採用這種方式,並且IEEE 754默認採用這種Rounding。Banker’s Rounding 也就是 round to even 策略。
假設當前考慮那位的數字是d(其實d就是將不被保留的第一位),若是d<5,則舍(round to zero);若是d>5,則入(round away from zero);而當d==5時,就要根據d先後的數位來肯定往哪邊取了。

1) 若是d以後存在非零的數位,則入;
2)若是d以後不存在非零的數位,則看d以前的一個數位,用c表示:
  a.若是c是奇數,則入;
  b.若是c是偶數,則舍。

再來一把栗子,對下列數保留0位小數,
第一位小數就是d,整數位就是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對正數和負數的處理是對稱的,所以不會引入符號帶來的誤差。另外它以均等的概率來舍入數位(考慮c, c有各一半的概率爲奇數和偶數),因此屢次舍入後與真值的差異會較小。

扯了這麼多,跟Java的Math.round( )有什麼關係呢?我也是寫到這才發現,好像沒什麼軟(luan)關係。由於它並無遵循Banker’s rounding。而是按照如下策略進行取整:
當考慮的數位d不是5,d<5就舍,d>5則入。
當d==5:

a.若是d的右邊有非零數位,則入;
b.若是d的右邊沒有非零數位,則 round to ceiling,即對負數舍,對正數入。

Java文檔裏是這麼表述的

還有還有, 在Java裏可使用 BigDecimal RoundingMode 實現更通用的取整方式。

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 的第一個參數是保留的小數位數,第二個參數是舍入模式。可選的舍入模式有:
HALF_EVEN, 也就是銀行家方式;
HALF_UP, 四捨五入;
HALF_DOWN, 五舍六入;
CEILING、FLOOR, 向正無窮、負無窮方向;
UP、DOWN*, 向零和遠離零;
UNNECESSARY, 斷言舍入後的值和原值相等,也就是不須要舍入。若是斷言錯了,拋出ArithmeticException異常。

先寫到這,比較粗糙,可是但願你有所收穫吧。歡迎討論,有話好好說( ̄▽ ̄)

轉載請註明:做者曾會玩

相關文章
相關標籤/搜索