本文 AI 產出,尚未審核

Python 課程 – 變數與資料型別

主題:小數精度(decimalfractions 模組)


簡介

在日常開發中,我們常會碰到 金額計算、科學計算或比例運算 等需要高精度的小數處理情境。
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
    • 可接受 intfloat(會先轉為最簡分數)或字串。
  • 自動化簡化
    • 內建的 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 = Nlocalcontext()
Fraction 直接接受長小數 產生非常大的分子/分母,效能下降 使用 limit_denominator(max_den) 進行約化
混用 Decimalfloat 會拋出 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
  • 型別註記局部設定精度明確捨入,這樣的程式碼在多人協作時最不易出錯。

實際應用場景

  1. 電子商務結帳系統

    • 使用 Decimal 處理商品價格、稅金、折扣,確保每筆交易的金額精確到分(或更小的貨幣單位)。
  2. 科學計算與統計分析

    • 需要大量累加小數時,Decimal 能避免誤差累積;若要保留分數形式(如機率),Fraction 可直接呈現最簡分數。
  3. 配方或材料比例管理

    • 料理、化工或製造業常以「3:5」的比例混合原料,Fraction 讓比例運算保持整數結果,避免因四捨五入產生浪費。
  4. 金融衍生商品定價

    • 期權、債券的利率計算要求高精度,Decimal 的自訂捨入規則(如銀行家捨入)正好符合業界標準。
  5. 教育與演算法教學

    • 在講授有理數運算或歐幾里得算法時,用 Fraction 能直接展示「最簡分數」的概念,讓學生更易理解。

總結

  • float 雖快但不適合 高精度金額或比例 的運算。
  • decimal 提供 十進位浮點,可自行設定精度與捨入方式,是金融、會計等領域的首選。
  • fractions分子/分母 形式保存有理數,保證運算完全精確,適合比例、機率等需求。
  • 使用時要 避免混用型別、從字串建立物件、明確設定捨入與精度,即可寫出「不會跑偏」的程式。

掌握這兩個模組後,你就能在 Python 中自信地處理任何需要精確小數的情境,從日常的金額計算到高階的科學模擬,都能保持正確與可靠。祝你寫程式時「算」得安心、寫得順手!