Python浮點數(shù)取整、格式化和NaN處理的操作方法
強轉(zhuǎn)int類型會直接對浮點數(shù)的小數(shù)部分進行截斷(無論是正還是負)。還有一種方法是math.ceil和math.floor。無論是正數(shù)還是負數(shù),都遵循:ceil往數(shù)軸正方向取整,floor往數(shù)軸負方向取整。round原型為round(value, ndigits),可以將一個浮點數(shù)取整到固定的小數(shù)位。該函數(shù)對正數(shù)和負數(shù)都采取就近取整原則,而當(dāng)某個值恰好等于兩個整數(shù)間一半時,取整操作會取到離該值最近的那個偶數(shù)。
1. 取整的三種方法
1.1 強轉(zhuǎn)int類型
這種方法會直接對浮點數(shù)的小數(shù)部分進行截斷(無論是正還是負)。
print(int(2.7)) # 2 print(int(-2.7)) # -2
1.2 采用math.ceil和math.floor
這種方法的取整規(guī)則如下圖所示:

可以看到無論是正數(shù)還是負數(shù),都遵循:ceil往數(shù)軸正方向取整,floor往數(shù)軸負方向取整。實例如下:
print(math.ceil(-1.27)) # -1 print(math.floor(-1.27)) # -2 print(math.ceil(1.27)) # 2 print(math.floor(1.27)) # 1
1.3 采用round
round原型為round(value, ndigits),可以將一個浮點數(shù)取整到固定的小數(shù)位。該函數(shù)對正數(shù)和負數(shù)都采取就近取整原則,而當(dāng)某個值恰好等于兩個整數(shù)間一半時,取整操作會取到離該值最近的那個偶數(shù)。像1.5和2.5這樣的值都會取整到2。示例如下:
print(round(1.23, 0)) # 1.0 print(round(1.23, 1)) # 1.2 print(round(1.27, 1)) # 1.3 print(round(-1.27, 1)) # -1.3 print(round(1.25361, 3)) # 1.254 print(round(1.5, 0)) # 2.0 print(round(2.5, 0)) # 2.0
傳遞給round()參數(shù)ndigits可以是負數(shù),這種情況下回相應(yīng)取整到十位、百位、千位:
a = 1627731 print(round(a, -1)) # 1627730 print(round(a, -2)) # 1627700 print(round(a, -3)) # 1628000
2. 格式化浮點數(shù)輸出
注意對值輸出時別把取整和格式化操作混為一談。如果只是將數(shù)值以固定位數(shù)輸出,一般是用不著round()的,只要在用format格式化時指定所需要的精度即可(format()格式化操作會根據(jù)round()的規(guī)則進行取整,最終返回一個字符串類型)。
x = 1234.56789 s = format(x, "0.2f") print(type(s), format(x, "0.2f")) # <class 'str'> 1234.57
除了取整到固定小數(shù)位,format()還具有許多格式化功能,如格式化輸出對齊,增加千分位分隔符等。實際上面的0.2f就表示至少對齊到0個字符(相當(dāng)于沒有對齊操作),并保留兩位小數(shù)。
小提示:.2f也表示至少對齊到0個字符(默認(rèn)是0),并保留兩位小數(shù),
和0.2f二者是等效的。
更多示例如下:
# 往右調(diào)整以對齊到10個字符 print(format(x, ">10.1f")) # 1234.6 # 往右調(diào)整以對齊到10個字符 print(format(x, "<10.1f")) # 1234.6 # 居中以對齊到10個字符 print(format(x, "^10.1f")) # 1234.6 # 增加千位分隔符 print(format(x, ",")) # 1,234.56789 # 增加千位分隔符并保存到1位小數(shù) print(format(x, "0,.1f")) # 1,234.6
如果想使用科學(xué)計數(shù)法,只要把f改成e或E即可:
print(format(x, "e")) # 1.234568e+03 print(format(x, "0.2E")) # 1.23E+03
此外,我們還可以利用字符串的translate()方法交換不同的分隔符:
swap_separators = {ord("."):",", ord(","):"."}
print(format(x, ",").translate(swap_separators)) # 1.234,56789
最后,我們這里提一下,調(diào)用字符串的.format()函數(shù)和單獨調(diào)用format()函數(shù)可以達到相同的效果,如:
print("value is {:0.3f}".format(x)) # value is 1.235
print("The value is {:0,.2f}".format(x)) # The value is 1,234.57
當(dāng)然我們也可以使用%操作符來對數(shù)值做格式化處理,如:
print("%.2f" % x)
print("%10.1f" % x)
print("%-10.1f" % x)這種格式化操作雖然可行,但是比起更加現(xiàn)代化的format()方法,這種方法就顯得不是那么強大了。如用%操作符來格式化數(shù)值時,有些功能就沒法得到支持了(如添加千位分隔符)。
3. 執(zhí)行精確的小數(shù)計算
我們在第一部分介紹了round()函數(shù),我們有可能會企圖用浮點取整的方式來“修正”精度上的問題,如:
a = 2.1 b = 4.2 c = a + b print(c) # 6.300000000000001 print(c==6.3) # False print(round(c, 2)) # 6.3 企圖這樣修正精度(???)
對大部分浮點數(shù)應(yīng)用程序(包括科學(xué)計算與機器學(xué)習(xí))來說,一般都不必(或者所不推薦)這么做。雖然Python的浮點運算會引入一些小誤差,但這些誤差實際上是底層CPU的浮點運算單元和IEEE 754浮點算數(shù)標(biāo)準(zhǔn)的一種“特性”。由于Python的浮點數(shù)類型保存的數(shù)據(jù)采用的是原始保存形式,因此只要代碼中用到了float實例,那就無法避免這樣的誤差。
如果避免出現(xiàn)誤差的行為非常重要(比如在金融應(yīng)用中),那么可以考慮使用decimal模塊。事實上在用Python做數(shù)據(jù)庫庫接口時經(jīng)常碰到Decimal對象——當(dāng)訪問金融數(shù)據(jù)時尤其如此。我們通過使用Decimal對象解決上述問題:
from decimal import Decimal
a = Decimal('4.2')
b = Decimal('2.1')
print(type(a + b), a + b) # <class 'decimal.Decimal'> 6.3
print((a + b) == Decimal('6.3')) # True這么做看起來似乎有點怪異(將數(shù)字以字符串的形式來指定)。但是Decimal對象能夠以任何期望的方式來工作(支持所有常見的數(shù)學(xué)操作)。如果要將它們打印出來或者在字符串格式化函數(shù)中使用,它們看起來就和普通數(shù)字一樣。它們也可以和普通int、float類型混合操作(最后會統(tǒng)一強轉(zhuǎn)為Decimal類型):
print(type(a + 1), a + 1) # <class 'decimal.Decimal'> 5.2
但是需要注意的是不要將其與普通float類型直接進行比較:
print((a + b) == 6.3) # False
decimal模塊的強大之處在于在計算過程中靈活地控制數(shù)字的位數(shù)和四舍五入,如我們可以創(chuàng)建一個本地的上下文環(huán)境然后修改精度的設(shè)定,如:
from decimal import localcontext
a = Decimal("1.3")
b = Decimal("1.7")
print(a/b) # 0.7647058823529411764705882353
with localcontext() as ctx:
ctx.prec = 3
print(a/b) # 0.765
with localcontext() as ctx:
ctx.prec = 50
print(a/b) # 0.764705882352941176470588235294117647058823529
不過還是我們上面所說的,如果我們處理的是科學(xué)或工程類型的問題,那么更常見的做法是直接使用普通的float浮點類型。首先,在真實世界中極少有東西需要計算到小數(shù)點后17位(float提供17位的精度),因此在計算中引入的微小誤差不足掛齒;其次,原生的float浮點數(shù)運算性能要快許多——如果要執(zhí)行大量計算,性能問題就顯得很重要了。
在使用float類型時,我們同樣還需要對類似相減抵消(substraction cancellation)以及把大數(shù)和小數(shù)加載一起的情況多加小心:
nums = [1.23e+18, 1, -1.23e+18] print(sum(nums)) # 0.0
使用Decimal對象當(dāng)然可以解決此問題。不過在不動用Decimal對象的情況下,我們可以使用math.fsum()以更精確的實現(xiàn)來解決:
import math print(math.fsum(nums)) # 1.0
但對于其它復(fù)雜的數(shù)值算法,我們就需要研究算法本身,理解其誤差傳播(error propagation)了,這屬于數(shù)值分析的研究范疇。在數(shù)值分析中數(shù)學(xué)家研究了大量數(shù)值算法,其中一些算法的誤差處理能力優(yōu)于其它算法,詳情可以參見我的數(shù)值計算專欄《orion-orion:數(shù)值計算》,此處不再詳述。
4. 無窮大、負無窮大和NaN的判斷測試
在實際項目中我們需要對浮點數(shù)的無窮大、負無窮大或NaN(not a number)進行判斷測試。在Python中沒有特殊的語法來表示這些特殊的浮點值,但是它們可以通過float來創(chuàng)建:
a = float("inf")
b = float("-inf")
c = float("nan")
print(a, b, c) # inf -inf nan
要檢查是否出現(xiàn)了這些值,可以使用math.isinf()和math.isnan()函數(shù):
print(math.isinf(a)) # True print(math.isnan(c)) # True
這些特殊浮點數(shù)的詳細信息可以參考IEEE 754規(guī)范。但是我們這里有幾個棘手的問題需要搞清楚,尤其是設(shè)計比較操作和操作符時可能出現(xiàn)的問題。
無窮大值在數(shù)學(xué)計算中會進行傳播,如:
a = float("inf")
print(a + 45) # inf
print(a * 10) # inf
print(10/a) # 0.0
但是,某些關(guān)于無窮大值特定的操作會導(dǎo)致未定義的行為并產(chǎn)生NaN的結(jié)果,例如:
a = float("inf")
print(a/a) # nan
b = float("-inf")
print(a + b) # nan
NaN會通過所有的操作進行傳播,且不會引發(fā)任何異常,如:
c = float("nan")
print(c + 23) # nan
print(c / 2) # nan
print(c + 2) # nan
有關(guān)NaN,一個微妙的特性是他們在做比較時從不會被判定為相等,如:
c = float("nan")
d = float("nan")
print(c == d) # False
print(c is d) # False
正因為如此,唯一安全檢測NaN的方法是使用math.isnan()。
參考
[1] Martelli A, Ravenscroft A, Ascher D. Python cookbook[M]. " O'Reilly Media, Inc.", 2015.
到此這篇關(guān)于Python浮點數(shù)取整、格式化和NaN處理的操作方法的文章就介紹到這了,更多相關(guān)Python浮點數(shù)取整內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python3 cvs將數(shù)據(jù)讀取為字典的方法
今天小編就為大家分享一篇python3 cvs將數(shù)據(jù)讀取為字典的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-12-12
python 找出list中最大或者最小幾個數(shù)的索引方法
今天小編就為大家分享一篇python 找出list中最大或者最小幾個數(shù)的索引方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-10-10

