關於除法,你也許以爲沒什麼值得談論的,畢竟小學的時候體育老師就教過咱們了。然而對於編程中使用的除法,我以爲仍是有不少值得注意的細節的。爲何我想深究一下?由於我平常主要使用Java和Python編程,而它們的除法在細節上有不少不一樣之處,全是坑啊…因此接下來我也將着重於Java和Python,可是相信我,就算你不用Java和Python,也會有所收穫的。html
對兩個不能整除的整數作除法,就要面對舍入的問題。和大多數編程語言同樣,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
誰說沒大事?( ̄▽ ̄)大事來了!編程
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
咱們知道,在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)
除了缺省的舍入方式,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,只是有的語言選擇了不讓你看到。
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異常。
先寫到這,比較粗糙,可是但願你有所收穫吧。歡迎討論,有話好好說( ̄▽ ̄)
轉載請註明:做者曾會玩