跳轉到

2.2 有理式與化簡#

本節目標#

  • 認識 SymPy 中的有理函數(分式)表示法
  • 熟練 cancel() 約分與 together() 通分
  • 學會 apart() 部分分式分解,為積分鋪路
  • 了解 simplify() 的定位:何時用通用化簡、何時用專用函數
  • 用 nsimplify() 把浮點數轉回漂亮的分數

從數學開始:有理函數#

有理函數是兩個多項式的比值:

\[ r(x) = \frac{p(x)}{q(x)} \]

處理有理式的三大基本操作:

  • 約分:把 \(\dfrac{x^2-1}{x+1}\) 化為 \(x-1\) (消去公因式)
  • 通分:把 \(\dfrac{1}{x} + \dfrac{1}{x+1}\) 合併為 \(\dfrac{2x+1}{x(x+1)}\)
  • 部分分式分解:把 \(\dfrac{1}{x(x+1)}\) 拆成 \(\dfrac{1}{x} - \dfrac{1}{x+1}\)

部分分式分解在微積分中是積分 \(\int \frac{1}{x(x+1)}\,dx\) 的標準前處理,SymPy 的 integrate() 內部就依賴這個技術。

在 SymPy 中建立有理式,記得用 Rational 或 S() 處理數字,避免 Python 的浮點除法:

from sympy import symbols, Rational, S

x = symbols('x')
r1 = (x**2 - 1) / (x + 1)
r2 = 1/x + 1/(x + 1)
print(r1)  # (x**2 - 1)/(x + 1)
print(r2)  # 1/x + 1/(x + 1)

cancel():約分#

cancel() 把有理式化成「分子分母互質」的標準型(p/q 形式):

from sympy import cancel

print(cancel((x**2 - 1) / (x + 1)))
# x - 1

print(cancel((x**3 + 2*x**2 + x) / (x**2 + x)))
# x + 1

print(cancel((x**2 + 1) / (x**2 - 1)))
# (x**2 + 1)/((x - 1)*(x + 1))   —— 無公因式,保持原樣

注意:直接做除法 p / q 時 SymPy 會自動做一些局部化簡,但複雜情況下公因式可能殘留,此時 cancel() 就派上用場:

expr = (x**10 - 1) / (x - 1)
print(cancel(expr))
# x**9 + x**8 + ... + x + 1

together():通分#

together() 把多個分式合併成單一分式:

from sympy import together

print(together(1/x + 1/(x + 1)))
# (2*x + 1)/(x*(x + 1))

print(together(1/x + 1/y + 1/(x*y)))
# (x + y + 1)/(x*y)

together() 與 apart() 大致互為逆操作(見後面實驗)。

apart():部分分式分解#

apart() 把有理式拆成部分分式:

from sympy import apart

print(apart(1 / (x*(x + 1))))
# 1/x - 1/(x + 1)

print(apart(x**2 / (x**2 - 1)))
# 1 + 1/(2*(x - 1)) - 1/(2*(x + 1))

print(apart(1 / (x**2 + 2*x + 1)))
# 1/(x + 1)**2   —— 重根也沒問題

為微積分鋪路#

部分分式是積分前處理的標準手法。先手動 apart,再積分:

from sympy import integrate, Symbol

t = Symbol('t')
f = 1 / (t*(t - 3))
print(apart(f))
# -1/(3*t) + 1/(3*(t - 3))

print(integrate(f, t))
# log(t - 3)/3 - log(t)/3

apart() 也能對其他變數分解:

y = symbols('y')
print(apart((y + 1) / (y**3 + y), y))
# (y + 1)/(y*(y**2 + 1)) —— 若在實域不可約則保持

simplify() 的定位:何時用它,何時用專用函數#

simplify() 是「萬用」化簡器:它會嘗試多種策略(約分、合併、三角化簡、指數律……)並回傳結果最短的版本。

from sympy import simplify

print(simplify((x**2 - 1) / (x + 1)))          # x - 1
print(simplify(sin(x)**2 + cos(x)**2))         # 1(下一節詳談)

但通用化的代價是:

  1. 結果不可預測:同一個輸入可能得到多種「等價但形式不同」的答案
  2. 速度慢:要嘗試多種演算法
  3. 可能過度化簡:把你想保留的結構(例如 \(\frac{1}{x} + \frac{1}{x+1}\) 的項)合併掉

實務準則:

需求 用什麼
有理式約分 cancel()
合併分式 together()
部分分式 apart()
展開 expand()
因式分解 factor()
不確定、要「看起來最乾淨」 simplify()
from sympy import expand

expr = (x + 1)**3 / (x + 1)
print(simplify(expr))   # x**2 + 2*x + 1
print(cancel(expr))     # x**2 + 2*x + 1(更快、更可控)

nsimplify():把小數變分數#

從數值計算或外部資料進來的浮點數,常需要轉回精確的有理數或常數表示:

from sympy import nsimplify, pi, exp

print(nsimplify(0.5))          # 1/2
print(nsimplify(0.333333))     # 1/3
print(nsimplify(3.1415926535)) # pi
print(nsimplify(2.718281828))  # E

nsimplify() 甚至能從數值「猜」出公式:

print(nsimplify(4.9348022005, [pi]))   # pi**2/2
print(nsimplify(1.0 + 1.7320508, [pi, exp(1)]))

在互動探索中,nsimplify 是連接「數值觀察」與「符號證明」的橋樑:先用浮點數看出規律,再用符號運算證明。

動手實驗:把 \(\frac{1}{x(x+1)}\) apart 回去驗證#

目標:驗證 apart() 與 together() 互為逆操作,並觀察 simplify() 在這個問題上的行為。

from sympy import symbols, apart, together, simplify, cancel, expand

x = symbols('x')
f = 1 / (x*(x + 1))

# 步驟 1:部分分式分解
pf = apart(f)
print(pf)         # 1/x - 1/(x + 1)

# 步驟 2:通分合併回去
g = together(pf)
print(g)          # (2*x + 1 ... )
print(g)          # 觀察合併結果

# 步驟 3:驗證恆等(兩種方法)
print(simplify(g - f))      # 0
print(expand((g - f).as_numer_denom()[0]))  # 0(分子為 0)

輸出:

1/x - 1/(x + 1)
(2*x + 1)/(x*(x + 1))
0
0

實驗延伸:

  1. 換成 \(\frac{1}{x(x+1)(x+2)}\) ,做 apart() 後 together() 回去,驗證恆等。
  2. 對 \(\frac{x^3}{x^2 - 1}\) 做 apart(),觀察多項式部分(假分式會拆出 \(x\) 項)。
  3. 試試 apart(1/(x**3 + 1))——在三階不可約因子上會發生什麼?加上 full=True 參數再試一次,比較結果。
  4. 用 nsimplify 猜猜 \(1.4676908375\) 與 \(\pi\) 的關係(提示: \(\frac{\pi}{2}\) 附近)。

小結與練習#

本節學會了:

  • cancel() 約分、together() 通分、apart() 部分分式分解
  • 驗證兩個有理式恆等:simplify(g - f) 或檢查分子為零
  • simplify() 的定位:通用但慢且不可控;優先用專用函數
  • nsimplify() 把浮點數還原為精確數學常數

練習:

  1. 對 \(\dfrac{2x+3}{(x-1)(x+2)}\) 做 apart(),並手算驗證係數。
  2. 驗證:together(apart(1/(x**2 - 1))) 經 cancel() 後回到原式。
  3. 用 apart() 與 integrate() 求 \(\int \frac{3}{x^2 + 3x + 2}\,dx\) 。
  4. 用 nsimplify() 找出 \(0.6931471805\) 、 \(1.4426950409\) 各對應什麼常數(提示: \(\ln 2\) 與 \(\log_2 e\) )。