除法啰嗦,不僅是 Python。
進(jìn)入 Python 交互模式之后(以后在本教程中,可能不再重復(fù)這類的敘述,只要看到>>>,就說明是在交互模式下),練習(xí)下面的運(yùn)算:
>>> 2 / 5
0
>>> 2.0 / 5
0.4
>>> 2 / 5.0
0.4
>>> 2.0 / 5.0
0.4
看到?jīng)]有?麻煩出來了(這是在 Python2.x 中),按照數(shù)學(xué)運(yùn)算,以上四個(gè)運(yùn)算結(jié)果都應(yīng)該是 0.4。但我們看到的后三個(gè)符合,第一個(gè)居然結(jié)果是 0。why?
因?yàn)?,?Python(嚴(yán)格說是 Python2.x 中,Python3 會有所變化)里面有一個(gè)規(guī)定,像 2/5 中的除法這樣,是要取整(就是去掉小數(shù),但不是四舍五入)。2 除以 5,商是 0(整數(shù)),余數(shù)是 2(整數(shù))。那么如果用這種形式:2/5,計(jì)算結(jié)果就是商那個(gè)整數(shù)?;蛘呖梢岳斫鉃椋?strong>整數(shù)除以整數(shù),結(jié)果是整數(shù)(商)。
比如:
>>> 5 / 2
2
>>> 7 / 2
3
>>> 8 / 2
4
注意:得到是商(整數(shù)),而不是得到含有小數(shù)位的結(jié)果再通過“四舍五入”取整。例如:5/2,得到的是商 2,余數(shù) 1,最終5 / 2 = 2
。并不是對 2.5 進(jìn)行四舍五入。
這個(gè)標(biāo)題和上面的標(biāo)題格式不一樣,上面的標(biāo)題是“整數(shù)除以整數(shù)”,如果按照風(fēng)格一貫制的要求,本節(jié)標(biāo)題應(yīng)該是“浮點(diǎn)數(shù)除以整數(shù)”,但沒有,現(xiàn)在是“浮點(diǎn)數(shù)與整數(shù)相除”,其含義是:
假設(shè):x 除以 y。其中 x 可能是整數(shù),也可能是浮點(diǎn)數(shù);y 可能是整數(shù),也可能是浮點(diǎn)數(shù)。
出結(jié)論之前,還是先做實(shí)驗(yàn):
>>> 9.0 / 2
4.5
>>> 9 / 2.0
4.5
>>> 9.0 / 2.0
4.5
>>> 8.0 / 2
4.0
>>> 8 / 2.0
4.0
>>> 8.0 / 2.0
4.0
歸納,得到規(guī)律:不管是被除數(shù)還是除數(shù),只要有一個(gè)數(shù)是浮點(diǎn)數(shù),結(jié)果就是浮點(diǎn)數(shù)。所以,如果相除的結(jié)果有余數(shù),也不會像前面一樣了,而是要返回一個(gè)浮點(diǎn)數(shù),這就跟在數(shù)學(xué)上學(xué)習(xí)的結(jié)果一樣了。
>>> 10.0 / 3
3.3333333333333335
這個(gè)是不是就有點(diǎn)搞怪了,按照數(shù)學(xué)知識,應(yīng)該是 3.33333...,后面是 3 的循環(huán)了。那么你的計(jì)算機(jī)就停不下來了,滿屏都是 3。為了避免這個(gè),Python 武斷終結(jié)了循環(huán),但是,可悲的是沒有按照“四舍五入”的原則終止。當(dāng)然,還會有更奇葩的出現(xiàn):
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.1 - 0.2
0.0
>>> 0.1 + 0.1 + 0.1 - 0.3
5.551115123125783e-17
>>> 0.1 + 0.1 + 0.1 - 0.2
0.10000000000000003
越來越糊涂了,為什么 computer 姑娘在計(jì)算這么簡單的問題上,如此糊涂了呢?不是 computer 姑娘糊涂,她依然冰雪聰明。原因在于十進(jìn)制和二進(jìn)制的轉(zhuǎn)換上,computer 姑娘用的是二進(jìn)制進(jìn)行計(jì)算,上面的例子中,我們輸入的是十進(jìn)制,她就要把十進(jìn)制的數(shù)轉(zhuǎn)化為二進(jìn)制,然后再計(jì)算。但是,在轉(zhuǎn)化中,浮點(diǎn)數(shù)轉(zhuǎn)化為二進(jìn)制,就出問題了。
例如十進(jìn)制的 0.1,轉(zhuǎn)化為二進(jìn)制是:0.0001100110011001100110011001100110011001100110011...
也就是說,轉(zhuǎn)化為二進(jìn)制后,不會精確等于十進(jìn)制的 0.1。同時(shí),計(jì)算機(jī)存儲的位數(shù)是有限制的,所以,就出現(xiàn)上述現(xiàn)象了。
這種問題不僅僅是 Python 中有,所有支持浮點(diǎn)數(shù)運(yùn)算的編程語言都會遇到,它不是 Python 的 bug。
明白了問題原因,怎么解決呢?就 Python 的浮點(diǎn)數(shù)運(yùn)算而言,大多數(shù)機(jī)器上每次計(jì)算誤差不超過 2**53 分之一。對于大多數(shù)任務(wù)這已經(jīng)足夠了,但是要在心中記住這不是十進(jìn)制算法,每個(gè)浮點(diǎn)數(shù)計(jì)算可能會帶來一個(gè)新的舍入錯(cuò)誤。
一般情況下,只要簡單地將最終顯示的結(jié)果用“四舍五入”到所期望的十進(jìn)制位數(shù),就會得到期望的最終結(jié)果。
對于需要非常精確的情況,可以使用 decimal 模塊,它實(shí)現(xiàn)的十進(jìn)制運(yùn)算適合會計(jì)方面的應(yīng)用和高精度要求的應(yīng)用。另外 fractions 模塊支持另外一種形式的運(yùn)算,它實(shí)現(xiàn)的運(yùn)算基于有理數(shù)(因此像 1/3 這樣的數(shù)字可以精確地表示)。最高要求則可是使用由 SciPy 提供的 Numerical Python 包和其它用于數(shù)學(xué)和統(tǒng)計(jì)學(xué)的包。列出這些東西,僅僅是讓看官能明白,解決問題的方式很多,后面會用這些中的某些方式解決上述問題。
關(guān)于無限循環(huán)小數(shù)問題,我有一個(gè)鏈接推薦給諸位,它不是想象的那么簡單呀。請閱讀:維基百科的詞條:0.999...,會不會有深入體會呢?
補(bǔ)充一個(gè)資料,供有興趣的朋友閱讀:浮點(diǎn)數(shù)算法:爭議和限制
Python 總會要提供多種解決問題的方案的,這是她的風(fēng)格。
Python 之所以受人歡迎,一個(gè)很重重要的原因,就是輪子多。這是比喻啦。就好比你要跑的快,怎么辦?光天天練習(xí)跑步是不行滴,要用輪子。找輛自行車,就快了很多。還嫌不夠快,再換電瓶車,再換汽車,再換高鐵...反正你可以選擇的很多。但是,這些讓你跑的快的東西,多數(shù)不是你自己造的,是別人造好了,你來用。甚至兩條腿也是感謝父母恩賜。正是因?yàn)檩喿佣?,可以選擇的多,就可以以各種不同速度享受了。
輪子是人類偉大的發(fā)明。
Python 就是這樣,有各種輪子,我們只需要用。只不過那些輪子在 Python 里面的名字不叫自行車、汽車,叫做“模塊”,有人承接別的語言的名稱,叫做“類庫”、“類”。不管叫什么名字吧。就是別人造好的東西我們拿過來使用。
怎么用?可以通過兩種形式用:
不啰嗦了,實(shí)驗(yàn)一個(gè):
>>> from __future__ import division
>>> 5 / 2
2.5
>>> 9 / 2
4.5
>>> 9.0 / 2
4.5
>>> 9 / 2.0
4.5
注意了,引用了一個(gè)模塊之后,再做除法,就不管什么情況,都是得到浮點(diǎn)數(shù)的結(jié)果了。
這就是輪子的力量。
前面計(jì)算 5/2 的時(shí)候,商是 2,余數(shù)是 1
余數(shù)怎么得到?在 Python 中(其實(shí)大多數(shù)語言也都是),用%
符號來取得兩個(gè)數(shù)相除的余數(shù).
實(shí)驗(yàn)下面的操作:
>>> 5 % 2
1
>>> 6%4
2
>>> 5.0%2
1.0
符號:%,就是要得到兩個(gè)數(shù)(可以是整數(shù),也可以是浮點(diǎn)數(shù))相除的余數(shù)。
前面說 Python 有很多人見人愛的輪子(模塊),她還有豐富的內(nèi)建函數(shù),也會幫我們做不少事情。例如函數(shù) divmod()
>>> divmod(5,2) # 表示 5 除以 2,返回了商和余數(shù)
(2, 1)
>>> divmod(9,2)
(4, 1)
>>> divmod(5.0,2)
(2.0, 1.0)
最后一個(gè)了,一定要堅(jiān)持,今天的確有點(diǎn)啰嗦了。要實(shí)現(xiàn)四舍五入,很簡單,就是內(nèi)建函數(shù):round()
動手試試:
>>> round(1.234567,2)
1.23
>>> round(1.234567,3)
1.235
>>> round(10.0/3,4)
3.3333
簡單吧。越簡單的時(shí)候,越要小心,當(dāng)你遇到下面的情況,就有點(diǎn)懷疑了:
>>> round(1.2345,3)
1.234 # 應(yīng)該是:1.235
>>> round(2.235,2)
2.23 # 應(yīng)該是:2.24
哈哈,我發(fā)現(xiàn)了 Python 的一個(gè) bug,太激動了。
別那么激動,如果真的是 bug,這么明顯,是輪不到我的。為什么?具體解釋看這里,下面摘錄官方文檔中的一段話:
Note: The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information.
原來真的輪不到我。歸根到底還是浮點(diǎn)數(shù)中的十進(jìn)制轉(zhuǎn)化為二進(jìn)制惹的禍。
似乎除法的問題到此要結(jié)束了,其實(shí)遠(yuǎn)遠(yuǎn)沒有,不過,做為初學(xué)者,至此即可。還留下了很多話題,比如如何處理循環(huán)小數(shù)問題,我肯定不會讓有探索精神的朋友失望的,在我的 github 中有這樣一個(gè)輪子,如果要深入研究,可以來這里嘗試。
總目錄 | 上節(jié):數(shù)和四則運(yùn)算 | 下節(jié):Math 模塊和運(yùn)算優(yōu)先級
如果你認(rèn)為有必要打賞我,請通過支付寶:qiwsir@126.com,不勝感激。