天天看點

python除法程式設計_Java和Python中的整數除法,取餘,舍入

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

1.整數除法

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

全選複制放進筆記>>>-1/10

-1

顯然如果按照Java的取整政策,-1/10應該得0,而Python給出的結果是-1。事實上Python的取整方式是向下取整,也就是向着數軸上負無窮的方向取整。

好吧,Java和Python的取整方式不同,奪大點事兒啊…那麼如果我們要在Python下采用向零取整的結果,咋整?一種比較直接的方式是:

全選複制放進筆記>>>int(float(-1)/10)

2.取餘

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

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

全選複制放進筆記(a/b)*b+c=a

也就是說:

全選複制放進筆記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正确地搞定這個問題嘞?可以先去絕對值和符号,當正數來處理,最後再在結果裡搭上符号。

3. Follow-ups

3.1 Python中的另一個除法操作

我們知道,在Python中,基本的除号“/”是被重載了的。當兩個操作數都是整數時,進行整數除法,得到整數結果,否則進行浮點數除法(真除法),得到浮點數結果。從Python 2.2開始,另一個除号被引入://,它隻執行整數除法。注意,//的結果類型依操作數而定。

全選複制放進筆記>>>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裡可以使用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異常。

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

轉載請注明:作者曾會玩