跳轉到

3.1 一元方程#

本節目標#

  • 學會用 solve() 解一元方程,理解「精確解」與「數值解」的差別。
  • 掌握二次方程的求根公式,並用 SymPy 驗證手算過程(判別式、根的個數)。
  • 觀察三次方程 Cardano 公式根的「醜陋」——理解為什麼需要數值方法。
  • 認識 roots() 與 Poly 物件在解方程時的用途。
  • 能處理無理方程與絕對值方程,並避開增根陷阱。

數學概念:什麼是解方程#

解方程 \(f(x) = 0\) ,就是找出所有使等式成立的 \(x\) 值,稱為方程的根(root)或解(solution)。

對一次方程 \(ax + b = 0\) ,解就是 \(x = -b/a\) 。對二次方程

\[ ax^2 + bx + c = 0, \quad a \neq 0 \]

有著名的求根公式:

\[ x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} \]

其中 \(b^2 - 4ac\) 稱為判別式 \(\Delta\) :

  • \(\Delta > 0\) :兩個相異實根
  • \(\Delta = 0\) :一個重根
  • \(\Delta < 0\) :兩個共軛複根

三次與四次方程雖然也有公式解(Cardano 公式、Ferrari 方法),但根的形式往往極為冗長複雜;五次以上一般就沒有根式解了(Abel–Ruffini 定理)。這正是後面 3.4 節要談數值解的動機。

solve() 的基本用法#

from sympy import symbols, solve, Eq

x = symbols('x')

# 直接給運算式(預設 = 0)
print(solve(x**2 - 4, x))          # [-2, 2]

# 用 Eq 表示等式:2x + 3 = 7
print(solve(Eq(2*x + 3, 7), x))    # [2]

# 一次方程
print(solve(3*x - 5, x))           # [5/3]

solve() 回傳的是精確解:分數是分數、根號是根號,不會有浮點誤差。這是符號計算最大的賣點。

二次方程與求根公式#

先用手算流程走一遍,再讓 SymPy 驗證:

解 \(x^2 - 5x + 6 = 0\) 。判別式 \(\Delta = 25 - 24 = 1 > 0\) ,故有兩相異實根:

\[ x = \frac{5 \pm 1}{2} \quad\Rightarrow\quad x = 3 \text{ 或 } x = 2 \]

用 SymPy 重現整個過程:

from sympy import symbols, solve, sqrt, Rational, discriminant

a, b, c, x = symbols('a b c x')

# 1. 一般求根公式
roots_formula = solve(a*x**2 + b*x + c, x)
print(roots_formula)
# [(-b + sqrt(-4*a*c + b**2))/(2*a), (-b - sqrt(-4*a*c + b**2))/(2*a)]

# 2. 判別式
D = b**2 - 4*a*c
print(discriminant(a*x**2 + b*x + c, x))  # b**2 - 4*a*c,一致!

# 3. 具體例子 x^2 - 5x + 6 = 0
print(solve(x**2 - 5*x + 6, x))   # [2, 3]

# 4. 沒有實根的例子:x^2 + x + 1 = 0
print(solve(x**2 + x + 1, x))
# [-1/2 - sqrt(3)*I/2, -1/2 + sqrt(3)*I/2]  → 複根,精確表示

# 5. 含無理根的例子:x^2 - 2 = 0
print(solve(x**2 - 2, x))         # [-sqrt(2), sqrt(2)]

注意第 4 例:SymPy 預設在複數域解方程。若只要實根,可加 dict=True 後自行篩選,或用 solveset 搭配域的限制(第 3.3 節會再談集合)。

高次方程:Cardano 根有多醜#

三次方程 \(x^3 - 15x - 4 = 0\) 其實有一個很簡單的根 \(x = 4\) (因為 \(64 - 60 - 4 = 0\) )。但若用 Cardano 公式,會出現著名的 casus irreducibilis(不可約情形):三個實根都存在,公式卻非得寫成複數的三次方根不可。

from sympy import symbols, solve, roots, Poly, nsimplify, N

x = symbols('x')

# 三次方程:根其實是 4, -2±sqrt(3)
sol = solve(x**3 - 15*x - 4, x)
print(sol)
# [4, -sqrt(3) - 2, sqrt(3) - 2]   ← 這裡 SymPy 已化簡得不錯

# 換一個更狡猾的例子:x^3 - 3x - 1 = 0(三個實根,但全是醜的無理數)
sol2 = solve(x**3 - 3*x - 1, x)
print(sol2)
# 2*cos(...) 形式或冗長根式,視版本而定
for r in sol2:
    print(r, '≈', N(r, 6))

觀察重點:

  1. 有的三次方程根很漂亮(有理根、簡單根式),solve() 能化簡出來。
  2. 有的三次方程三個實根卻無法用簡單根式表示(casus irreducibilis),輸出會是 \(2\cos\frac{\theta}{3}\) 之類的形式或很長的巢狀根式——這就是「Cardano 根的醜」。
  3. 此時 N(r) 取數值近似反而最直觀。數值方法見 3.4 節。

roots() 與 Poly#

solve() 給出「解的清單」,而 roots() 會把根與重數(multiplicity)一起回傳成字典;Poly 則把多項式物件化,提供結構化操作:

from sympy import symbols, roots, Poly, solve

x = symbols('x')

# roots() 回傳 {根: 重數}
print(roots((x - 1)**2 * (x + 3), x))
# {1: 2, -3: 1}   ← 1 是二重根

# Poly 物件:明確宣告變數與域
p = Poly(x**4 - 5*x**2 + 4, x)
print(p)                # Poly(x**4 - 5*x**2 + 4, x, domain='ZZ')
print(p.degree())       # 4
print(p.all_roots())    # [-2, -1, 1, 2](按代數數排好)
print(p.factor_list())  # 因式分解

# 高次方程:4 次還有公式,5 次以上一般沒有
print(roots(x**5 - x - 1, x))   # {} ← 根式解不存在,只能靠數值方法

規則:多項式方程優先考慮 roots()(有重數資訊)或 Poly;一般方程用 solve();解不出來就用 3.4 節的 nsolve()。

無理方程與絕對值方程#

from sympy import symbols, solve, Abs, sqrt, checksol

x = symbols('x')

# 無理方程:sqrt(x+3) = x - 3
# 手算:兩邊平方 → x + 3 = x^2 - 6x + 9 → x^2 - 7x + 6 = 0 → x = 1 或 6
# 但平方會產生「增根」:x=1 代回原式 sqrt(4)=2 ≠ -2,捨去!
print(solve(sqrt(x + 3) - (x - 3), x))   # [6] ← SymPy 自動檢查,只留 6

# 保險起見可手動驗根
for cand in [1, 6]:
    print(cand, checksol(sqrt(x + 3) - (x - 3), x, cand))  # None / 0

# 絕對值方程:|2x - 1| = 5
# 手算:2x - 1 = 5 或 2x - 1 = -5 → x = 3 或 x = -2
print(solve(Abs(2*x - 1) - 5, x))        # [-2, 3]

# 含兩個絕對值:|x| + |x - 1| = 3(分段討論手算很繁瑣)
print(solve(Abs(x) + Abs(x - 1) - 3, x)) # [-1, 2]

教學要點:sqrt(x+3) = x-3 這類方程,平方前後不等價,手算必須驗根;SymPy 的 solve() 內建了檢查,但仍建議用 checksol() 養成驗證習慣。

動手實驗:判別式 \(\Delta\) 與根的個數#

實驗目標:固定 \(a = 1,\ b = 2\) ,掃描 \(c\) 的值,觀察 \(\Delta = 4 - 4c\) 的正負與根的形態。

from sympy import symbols, solve, discriminant, N, Rational

x, c = symbols('x c')
a, b = 1, 2

print(f"{'c':>6} | {'Δ':>8} | 根")
print("-" * 50)
for cv in [Rational(3), Rational(1), Rational(0), Rational(-1), Rational(-2)]:
    poly = a*x**2 + b*x + cv - cv*0 + (cv - cv)  # 展開寫法避免混淆
    poly = a*x**2 + b*x + cv
    D = discriminant(poly, x)
    rts = solve(poly, x)
    print(f"{str(cv):>6} | {str(D):>8} | {rts}")

# 結論:
# c = 1    → Δ = 0     → 重根 [-1]
# c = 0    → Δ = 4 > 0 → 兩實根 [0, -2]
# c = -1   → Δ = 8 > 0 → 兩實根(無理)
# c = 3    → Δ = -8 < 0→ 兩複根

# 實驗延伸:讓 c 保持符號,寫出「有實根」的條件
from sympy import simplify
D_general = b**2 - 4*a*c
# 對 a=1, b=2:Δ = 4 - 4c ≥ 0 ⟺ c ≤ 1
print(solve(D_general.subs({a: 1, b: 2}) >= 0, c))  # c ≤ 1(見 3.3 節不等式)

進一步挑戰:

  1. 把 b 也換成不同值,驗證「 \(\Delta\) 相同則根的個數型態相同」。
  2. 用 N() 把複根的虛部大小與 \(\sqrt{-\Delta}/(2a)\) 比較,你會發現它們相等。
  3. 對三次方程 \(x^3 + px + q = 0\) ,查閱「判別式 \(\Delta = -4p^3 - 27q^2\) 」,實驗 \(\Delta\) 的正負與三根是「三實根」還是「一實二複」的關係。

小結與練習#

小結

  • solve() 給精確解,預設在複數域;roots() 附帶重數;Poly 適合結構化處理多項式。
  • 二次方程用判別式 \(\Delta\) 判斷根的個數型態;SymPy 的 discriminant() 可直接計算。
  • 三次以上方程的根式解可能極醜甚至不存在(casus irreducibilis、五次無根式解),這是數值解存在的理由。
  • 無理方程平方會增根,必須驗根;Abs 方程 solve() 可直接處理。

練習

  1. 用 solve() 解 \(2x^2 - 4x - 3 = 0\) ,並用 discriminant() 先預測根的型態。
  2. 解 \(\sqrt{x + 5} = x - 1\) ,手算指出增根是哪一個,再用 checksol() 驗證。
  3. 解 \(|x - 2| = |2x + 1|\) (提示:兩邊絕對值相等代表距離相等)。
  4. 用 roots() 求 \((x^2 - 1)^3\) 的所有根與重數。
  5. 嘗試 solve(x**6 - x - 1, x),觀察結果;再用 N() 對 Poly(...).all_roots() 取近似值。