Python技法:浮點數取整、格式化和NaN處理
1. 取整的三種方法
1.1 強轉int型別
這種方法會直接對浮點數的小數部分進行截斷(無論是正還是負)。
print(int(2.7)) # 2 print(int(-2.7)) # -2
1.2 採用math.ceil和math.floor
這種方法的取整規則如下圖所示:

可以看到無論是正數還是負數,都遵循: ceil
往數軸正方向取整, floor
往數軸負方向取整。例項如下:
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)
,可以將一個浮點數取整到固定的小數位。該函式對正數和負數都採取就近取整原則,而當某個值恰好等於兩個整數間一半時,取整操作會取到離該值最近的那個偶數。像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()
引數 ndigits
可以是負數,這種情況下回相應取整到十位、百位、千位:
a = 1627731 print(round(a, -1)) # 1627730 print(round(a, -2)) # 1627700 print(round(a, -3)) # 1628000
2. 格式化浮點數輸出
注意對值輸出時別把取整和格式化操作混為一談。如果只是將數值以固定位數輸出,一般是用不著 round()
的,只要在用 format
格式化時指定所需要的精度即可( format()
格式化操作會根據 round()
的規則進行取整,最終返回一個字串型別)。
x = 1234.56789 s = format(x, "0.2f") print(type(s), format(x, "0.2f")) # <class 'str'> 1234.57
除了取整到固定小數位, format()
還具有許多格式化功能,如格式化輸出對齊,增加千分位分隔符等。實際上面的 0.2f
就表示至少對齊到0個字元(相當於沒有對齊操作),並保留兩位小數。
小提示: .2f
也表示至少對齊到0個字元(預設是0),並保留兩位小數,
和 0.2f
二者是等效的。
更多示例如下:
# 往右調整以對齊到10個字元 print(format(x, ">10.1f")) # 1234.6 # 往右調整以對齊到10個字元 print(format(x, "<10.1f")) # 1234.6 # 居中以對齊到10個字元 print(format(x, "^10.1f")) # 1234.6 # 增加千位分隔符 print(format(x, ",")) # 1,234.56789 # 增加千位分隔符並儲存到1位小數 print(format(x, "0,.1f")) # 1,234.6
如果想使用科學計數法,只要把 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
最後,我們這裡提一下,呼叫字串的 .format()
函式和單獨呼叫 format()
函式可以達到相同的效果,如:
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
當然我們也可以使用 %
操作符來對數值做格式化處理,如:
print("%.2f" % x) print("%10.1f" % x) print("%-10.1f" % x)
這種格式化操作雖然可行,但是比起更加現代化的 format()
方法,這種方法就顯得不是那麼強大了。如用 %
操作符來格式化數值時,有些功能就沒法得到支援了(如新增千位分隔符)。
3. 執行精確的小數計算
我們在第一部分介紹了 round()
函式,我們有可能會企圖用浮點取整的方式來“修正”精度上的問題,如:
a = 2.1 b = 4.2 c = a + b print(c) # 6.300000000000001 print(c==6.3) # False print(round(c, 2)) # 6.3 企圖這樣修正精度(???)
對大部分浮點數應用程式(包括科學計算與機器學習)來說,一般都不必(或者所不推薦)這麼做。雖然Python的浮點運算會引入一些小誤差,但這些誤差實際上是底層CPU的浮點運算單元和IEEE 754浮點算數標準的一種“特性”。由於Python的浮點數型別儲存的資料採用的是原始儲存形式,因此只要程式碼中用到了 float
例項,那就無法避免這樣的誤差。
如果避免出現誤差的行為非常重要(比如在金融應用中),那麼可以考慮使用 decimal
模組。事實上在用Python做資料庫庫介面時經常碰到 Decimal
物件——當訪問金融資料時尤其如此。我們通過使用 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
這麼做看起來似乎有點怪異(將數字以字串的形式來指定)。但是 Decimal
物件能夠以任何期望的方式來工作(支援所有常見的數學操作)。如果要將它們打印出來或者在字串格式化函式中使用,它們看起來就和普通數字一樣。它們也可以和普通 int
、 float
型別混合操作(最後會統一強轉為 Decimal
型別):
print(type(a + 1), a + 1) # <class 'decimal.Decimal'> 5.2
但是需要注意的是不要將其與普通 float
型別直接進行比較:
print((a + b) == 6.3) # False
decimal
模組的強大之處在於在計算過程中靈活地控制數字的位數和四捨五入,如我們可以建立一個本地的上下文環境然後修改精度的設定,如:
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
不過還是我們上面所說的,如果我們處理的是科學或工程型別的問題,那麼更常見的做法是直接使用普通的 float
浮點型別。首先,在真實世界中極少有東西需要計算到小數點後17位( float
提供17位的精度),因此在計算中引入的微小誤差不足掛齒;其次,原生的 float
浮點數運算效能要快許多——如果要執行大量計算,效能問題就顯得很重要了。
在使用 float
型別時,我們同樣還需要對類似相減抵消(substraction cancellation)以及把大數和小數載入一起的情況多加小心:
nums = [1.23e+18, 1, -1.23e+18] print(sum(nums)) # 0.0
使用 Decimal
物件當然可以解決此問題。不過在不動用 Decimal
物件的情況下,我們可以使用 math.fsum()
以更精確的實現來解決:
import math print(math.fsum(nums)) # 1.0
但對於其它複雜的數值演算法,我們就需要研究演算法本身,理解其誤差傳播(error propagation)了,這屬於數值分析的研究範疇。在數值分析中數學家研究了大量數值演算法,其中一些演算法的誤差處理能力優於其它演算法,詳情可以參見我的數值計算專欄 《orion-orion:數值計算》 ,此處不再詳述。
4. 無窮大、負無窮大和NaN的判斷測試
在實際專案中我們需要對浮點數的無窮大、負無窮大或NaN(not a number)進行判斷測試。在Python中沒有特殊的語法來表示這些特殊的浮點值,但是它們可以通過 float
來建立:
a = float("inf") b = float("-inf") c = float("nan") print(a, b, c) # inf -inf nan
要檢查是否出現了這些值,可以使用 math.isinf()
和 math.isnan()
函式:
print(math.isinf(a)) # True print(math.isnan(c)) # True
這些特殊浮點數的詳細資訊可以參考IEEE 754規範。但是我們這裡有幾個棘手的問題需要搞清楚,尤其是設計比較操作和操作符時可能出現的問題。
無窮大值在數學計算中會進行傳播,如:
a = float("inf") print(a + 45) # inf print(a * 10) # inf print(10/a) # 0.0
但是,某些關於無窮大值特定的操作會導致未定義的行為併產生 NaN
的結果,例如:
a = float("inf") print(a/a) # nan b = float("-inf") print(a + b) # nan
NaN會通過所有的操作進行傳播,且不會引發任何異常,如:
c = float("nan") print(c + 23) # nan print(c / 2) # nan print(c + 2) # nan
有關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.
- [2] https://stackoverflow.com/questions/15765289/what-is-the-difference-between-0-2lf-and-2lf-as-printf-placeholders
- [3] https://docs.python.org/3/
- 分享自己平時使用的socket多客戶端通訊的程式碼技術點和軟體使用
- iNeuOS工業網際網路作業系統,增加2154個檢視建模(WEB組態)行業向量圖元、大屏背景及相關圖元
- 多臺雲伺服器的 Kubernetes 叢集搭建
- Elasticsearch學習系列四(聚合搜尋)
- 關於swiper外掛在vue2的使用
- 使用 Abp.Zero 搭建第三方登入模組(一):原理篇
- LVGL庫入門教程 - 顏色和影象
- 物聯網?快來看 Arduino 上雲啦
- SpringBoot JWT Redis 開源知識社群系統
- CVPR2022 | 可精簡域適應
- Spring框架系列(3) - 深入淺出Spring核心之控制反轉(IOC)
- 面試突擊59:一個表中可以有多個自增列嗎?
- CVPR2022 | 弱監督多標籤分類中的損失問題
- JDBC、ORM、JPA、Spring Data JPA,傻傻分不清楚?一文帶你釐清箇中曲直,給你個選擇SpringDataJPA的...
- Spring Security:使用者和Spring應用之間的安全屏障
- Mybatisi和Spring整合原始碼分析
- 前端學習 linux —— 第一篇
- call apply bind的作用及區別? 應用場景?
- Bika LIMS 開源LIMS集——實驗室檢驗流程概述及主頁、面板
- 軟體專案管理 7.5.專案進度模型(SPSP)