摘要:整數(shù)除法對兩個不能整除的整數(shù)做除法,就要面對舍入的問題。中的舍入除了缺省的舍入方式,還有多種舍入可供選擇。就是說,我們輸入的十進制數(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下采用向零取整的結果,咋整?一種比較直接的方式是:
>>>int(float(-1)/10) 02.取余
誰說沒大事?( ̄▽ ̄)大事來了!
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都可以用相同的方法來解決:?
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ù)而定。
>>>1.0/2 0.0 >>>1.0//2.0 0.0 >>>1//2 >0
另外,如果想同時得到商和余數(shù),可以使用內建的函數(shù)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函數(shù)也可以一個指定保留小數(shù)位數(shù)的參數(shù):
>>>round(0.21, 1) 0.2 >>>round(0.21, 2) 0.21
Caution !
>>>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文檔里是這么表述的
還有還有, 在Java里可以使用 BigDecimal 和 RoundingMode 實現(xiàn)更通用的取整方式。
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ù)是舍入模式??蛇x的舍入模式有:
HALF_EVEN, 也就是銀行家方式;
HALF_UP, 四舍五入;
HALF_DOWN, 五舍六入;
CEILING、FLOOR, 向正無窮、負無窮方向;
UP、DOWN*, 向零和遠離零;
UNNECESSARY, 斷言舍入后的值和原值相等,也就是不需要舍入。如果斷言錯了,拋出ArithmeticException異常。
先寫到這,比較粗糙,但是希望你有所收獲吧。歡迎討論,有話好好說( ̄▽ ̄)
轉載請注明:作者曾會玩
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/64718.html
摘要:整數(shù)除法對兩個不能整除的整數(shù)做除法,就要面對舍入的問題。中的舍入除了缺省的舍入方式,還有多種舍入可供選擇。就是說,我們輸入的十進制數(shù),在計算機內部都是用二進制來表示的。 關于除法,你也許覺得沒什么值得談論的,畢竟小學的時候體育老師就教過我們了。然而對于編程中使用的除法,我覺得還是有很多值得注意的細節(jié)的。為什么我想深究一下?因為我日常主要使用Java和Python編程,而它們的除法在細節(jié)...
摘要:例如返回的結果并不是,而是冪指數(shù)取余賦值給變量使用函數(shù)來查看變量類型整型數(shù)字的最大最小值在位系統(tǒng)中,一個整型個字節(jié),最小值,最大值。事實上,中儲存的值為,因為這是最接近的浮點數(shù)。 整型 Integers 整型運算,加減乘: 2 + 2 4 3 - 4 -1 4 * 5 20 在Python 2.7中,整型的運算結果只能返回整型,除法的結果也不例外。 例如12 / ...
摘要:上一篇文章標準庫內置類型邏輯值檢測布爾運算比較下一篇文章標準庫內置類型迭代器類型序列類型數(shù)字類型存在三種不同的數(shù)字類型整數(shù)浮點數(shù)和復數(shù)。標準庫包含附加的數(shù)字類型,如表示有理數(shù)的以及以用戶定制精度表示浮點數(shù)的。 上一篇文章:Python標準庫---9、內置類型:邏輯值檢測、布爾運算、比較下一篇文章:Python標準庫---11、內置類型:迭代器類型、序列類型 數(shù)字類型 --- int,...
摘要:區(qū)分取余與取模取余與與取模的本質區(qū)別取余盡可能讓商,進行向取整。理解鏈對任何一個大于的數(shù),對其進行向取整和負無窮取整,取整方向是一致的。故取模等價于取余。 目錄 1. 取整問題 1.0向取整(C語言默認的取整方案) 2.地板取整(向負無窮的方向取整) 3.天花板取整(向+無窮的方向取整) ...
摘要:但是在轉化中,浮點數(shù)轉化為二進制后,不會精確等于十進制的。一般情況下,只要簡單地將最終顯示的結果用四舍五入到所期望的十進制位數(shù),就會得到期望的最終結果。四舍五入內建函數(shù)。在中的第二個數(shù),表示要保留的小數(shù)位數(shù),返回值是一個四舍五入之后的數(shù)值。 數(shù)字 基本類型 首先,進入Python交互模式中: //整數(shù) >>> 3 3 //長整數(shù) >>> 3333333333333333333333...
閱讀 786·2023-04-25 17:33
閱讀 3641·2021-07-29 14:49
閱讀 2488·2019-08-30 15:53
閱讀 3442·2019-08-29 16:27
閱讀 2011·2019-08-29 16:11
閱讀 1038·2019-08-29 14:17
閱讀 2447·2019-08-29 13:47
閱讀 2024·2019-08-29 13:28