本文 AI 產出,尚未審核
Python 課程 – 變數與資料型別
主題:小數精度(decimal、fractions 模組)
簡介
在日常開發中,我們常會碰到 金額計算、科學計算或比例運算 等需要高精度的小數處理情境。
Python 的內建 float 使用二進位浮點數表示,雖然速度快卻會產生 捨入誤差(例如 0.1 + 0.2 != 0.3),這在金融、會計或統計分析上是不可接受的。
為了解決這個問題,Python 提供了兩個專門處理精確小數的標準庫:
decimal– 基於十進位的浮點運算,適合金額、稅率等需要 固定小數位 的情境。fractions– 以 分子/分母 形式儲存數值,適合比例、機率或需要 完全精確的有理數 計算。
本篇將從概念說明、實作範例、常見陷阱與最佳實踐,一路帶你掌握這兩個模組的使用方式,並提供實務應用案例,讓你在程式中寫出「不會跑偏」的數學運算。
核心概念
1. 為什麼 float 會出錯?
float 採用 IEEE 754 二進位浮點表示法,某些十進位小數(如 0.1)在二進位中無法精確表示,結果會留下微小的誤差。
>>> 0.1 + 0.2
0.30000000000000004
這類誤差在迴圈累加或比較時會放大,導致 條件判斷失敗 或 金額對帳不一致。
2. decimal 模組
decimal 提供 十進位浮點,使用者可以自行設定 精度(有效位數)與 捨入方式。
- 建立 Decimal 物件
- 必須從字串或
int建立,直接使用float會把誤差帶進去。
- 必須從字串或
- 設定全域精度
- 透過
getcontext().prec調整運算時的有效位數。
- 透過
程式碼範例 1:基本使用
from decimal import Decimal, getcontext
# 設定全域精度為 28 位(預設值)
getcontext().prec = 28
a = Decimal('0.1')
b = Decimal('0.2')
c = a + b
print(c) # 0.3
程式碼範例 2:自訂捨入方式(四捨五入、向上、向下)
from decimal import Decimal, ROUND_HALF_UP, ROUND_DOWN
x = Decimal('2.345')
print(x.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)) # 2.35
print(x.quantize(Decimal('0.01'), rounding=ROUND_DOWN)) # 2.34
程式碼範例 3:金融計算 – 計算稅後金額
from decimal import Decimal, getcontext, ROUND_HALF_EVEN
getcontext().prec = 10
price = Decimal('1234.56')
tax_rate = Decimal('0.05') # 5% 稅率
tax = (price * tax_rate).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN)
total = price + tax
print(f"稅額:{tax},含稅總價:{total}")
3. fractions 模組
fractions.Fraction 用 分子 / 分母 的形式表示有理數,所有運算都保持 完全精確(除非除以 0)。
- 建立 Fraction
- 可接受
int、float(會先轉為最簡分數)或字串。
- 可接受
- 自動化簡化
- 內建的
Fraction會自動把分子與分母約分到最簡形式。
- 內建的
程式碼範例 4:基本使用
from fractions import Fraction
f1 = Fraction(1, 3) # 1/3
f2 = Fraction('2/5') # 2/5
result = f1 + f2
print(result) # 11/15
程式碼範例 5:與 float 互換(注意精度)
from fractions import Fraction
# 從 float 轉換會產生近似分數
approx = Fraction(0.1) # 3602879701896397/36028797018963968
print(approx.limit_denominator(100)) # 1/10(限制分母上限)
程式碼範例 6:比例計算 – 配方比例
from fractions import Fraction
# 兩種材料的比例 3:5
ratio = Fraction(3, 5)
total = 800 # 總重量 (克)
part_a = total * ratio # 材料 A
part_b = total - part_a # 材料 B
print(f"A: {part_a}g, B: {part_b}g") # A: 300g, B: 500g
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方式 |
|---|---|---|
直接使用 float 產生 Decimal |
誤差會被帶入 Decimal,失去精度 |
永遠從字串或 int 建立 Decimal('0.1') |
| 忘記設定全域精度 | 計算結果被自動截斷,可能出現 InvalidOperation |
使用 getcontext().prec = N 或 localcontext() |
Fraction 直接接受長小數 |
產生非常大的分子/分母,效能下降 | 使用 limit_denominator(max_den) 進行約化 |
混用 Decimal 與 float |
會拋出 TypeError,或隱藏誤差 |
統一使用同一種型別,必要時先轉換 |
| 忘記指定捨入模式 | 金額四捨五入不符合會計規則 | 明確使用 quantize(..., rounding=ROUND_HALF_EVEN) 等 |
推薦的寫法
from decimal import Decimal, getcontext, ROUND_HALF_EVEN
def calculate_total(price: str, tax_rate: str) -> Decimal:
"""回傳四捨五入到分位的含稅金額"""
getcontext().prec = 12
p = Decimal(price)
r = Decimal(tax_rate)
tax = (p * r).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN)
return p + tax
- 型別註記、局部設定精度、明確捨入,這樣的程式碼在多人協作時最不易出錯。
實際應用場景
電子商務結帳系統
- 使用
Decimal處理商品價格、稅金、折扣,確保每筆交易的金額精確到分(或更小的貨幣單位)。
- 使用
科學計算與統計分析
- 需要大量累加小數時,
Decimal能避免誤差累積;若要保留分數形式(如機率),Fraction可直接呈現最簡分數。
- 需要大量累加小數時,
配方或材料比例管理
- 料理、化工或製造業常以「3:5」的比例混合原料,
Fraction讓比例運算保持整數結果,避免因四捨五入產生浪費。
- 料理、化工或製造業常以「3:5」的比例混合原料,
金融衍生商品定價
- 期權、債券的利率計算要求高精度,
Decimal的自訂捨入規則(如銀行家捨入)正好符合業界標準。
- 期權、債券的利率計算要求高精度,
教育與演算法教學
- 在講授有理數運算或歐幾里得算法時,用
Fraction能直接展示「最簡分數」的概念,讓學生更易理解。
- 在講授有理數運算或歐幾里得算法時,用
總結
float雖快但不適合 高精度金額或比例 的運算。decimal提供 十進位浮點,可自行設定精度與捨入方式,是金融、會計等領域的首選。fractions以 分子/分母 形式保存有理數,保證運算完全精確,適合比例、機率等需求。- 使用時要 避免混用型別、從字串建立物件、明確設定捨入與精度,即可寫出「不會跑偏」的程式。
掌握這兩個模組後,你就能在 Python 中自信地處理任何需要精確小數的情境,從日常的金額計算到高階的科學模擬,都能保持正確與可靠。祝你寫程式時「算」得安心、寫得順手!