跳轉到

3.4 數值解與根的定位#

本節目標#

  • 學會用 nsolve() 求方程的數值解,理解其背後是牛頓法(第 X 章預告)。
  • 認識「無解析解」的方程(如 \(e^x = 3x\) ),理解為什麼必須走向數值方法。
  • 會用 plot 目視判斷根的個數與位置,作為 nsolve 的初始值依據。
  • 能在 solve 與 nsolve 之間做正確選擇。
  • 實驗:解 \(\cos x = x\) 、探討根的個數與函數形狀的關係。

數學概念:精確解與數值解#

前面兩節的 solve() 給的是精確解(符號解)。但許多方程根本沒有根式或初等函數的封閉形式解:

  • \(e^x = 3x\) :涉及超越函數與多項式混合,一般無法用初等運算表示根。
  • \(\cos x = x\) :同理,根只能近似。
  • \(x^5 - x - 1 = 0\) :五次多項式無根式解(Abel–Ruffini 定理)。

數值解(numerical solution)不追求「漂亮的式子」,而是求一個足夠接近真根的小數,例如 \(x \approx 0.567143\) 。常用的方法之一是牛頓法(Newton's method):

\[ x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} \]

從一個猜測值 \(x_0\) 出發,反覆用「切線與 \(x\) 軸的交點」更新猜測,通常收斂得非常快(二次收斂)。詳細推導與誤差分析見第 X 章;本節先學會把 nsolve 當工具用。

nsolve 基本用法#

from sympy import symbols, nsolve, cos, exp, pi

x = symbols('x')

# 例 1:cos(x) = x(著名方程,根稱為 Dottie number)
print(nsolve(cos(x) - x, x, 0))        # 0.739085133215161
print(nsolve(cos(x) - x, x, 1))        # 0.739085133215161(不同初始值,同一根)

# 例 2:指定精度(第 3 個參數是位數)
print(nsolve(cos(x) - x, x, 0, prec=30))
# 0.739085133215160641655312087674

# 例 3:多個根時,初始值決定收斂到哪一個
# x^2 - 5 = 0 的根是 ±sqrt(5) ≈ ±2.236
print(nsolve(x**2 - 5, x, 1))          # 2.23606797749979  → +√5
print(nsolve(x**2 - 5, x, -1))         # -2.23606797749979 → -√5

# 例 4:也可用 Eq
print(nsolve(cos(x) - x, 0))           # 不寫變數也可(單變數時)

要點:nsolve(expr, x, x0) 的 x0 是初始猜測值,它決定了牛頓法迭代走向哪個根。給錯初始值可能收斂到別的根、甚至不收斂——所以先「定位」根很重要,這正是 plot 的用武之地。

無解析解的方程: \(e^x = 3x\)#

from sympy import symbols, solve, solveset, nsolve, plot, exp, S, N, Poly

x = symbols('x', real=True)

# 1. 先試符號解:solve 舉白旗或給 LambertW 形式
print(solve(exp(x) - 3*x, x))
# [3*LambertW(-1/3)]  ← LambertW 是特殊函數,不算「初等」解
# 一般教學情境會說「此方程無初等解析解」

# 2. 圖形法:f(x) = e^x - 3x 的零點
p = plot(exp(x) - 3*x, (x, -1, 2), line_color='blue', show=False)
p.show()    # 觀察:曲線與 x 軸交於兩處?一處在 x≈0.6,一處在 x≈1.5

# 3. 用 nsolve 分別求出兩個根
print(nsolve(exp(x) - 3*x, x, 0.5))    # 0.619061286735945
print(nsolve(exp(x) - 3*x, x, 1.5))    # 1.51213455165784

# 4. 驗證:代回原方程,殘差接近 0
r = nsolve(exp(x) - 3*x, x, 1.5)
print(exp(r) - 3*r)                    # 約 0(浮點殘差 ~1e-16 級)

觀察重點:solve 給的 LambertW(-1/3) 是一個精確解,但圖形顯示有兩個實根——符號方法找到了一個,另一個(較小的根)沒有以初等形式現身。這說明:「有符號解」與「找到全部解」是兩回事;圖形是找根個數最可靠的工具。

用 plot 目視找根個數#

找根的標準流程:

  1. 定義 \(f(x)\) ,把方程化成 \(f(x) = 0\) 。
  2. 畫 \(y = f(x)\) ,看曲線與 \(x\) 軸交幾次、大約在哪。
  3. 對每個交點,取附近的初始值呼叫 nsolve。
from sympy import symbols, plot, nsolve, sin, cos, exp

x = symbols('x', real=True)

# 另一個例子:x^3 - 6x^2 + 11x - 6 = (x-1)(x-2)(x-3)
p = plot(x**3 - 6*x**2 + 11*x - 6, (x, 0, 4), show=False)
p.show()   # 三個交點 x ≈ 1, 2, 3
for guess in [0.9, 2.1, 3.1]:
    print(nsolve(x**3 - 6*x**2 + 11*x - 6, x, guess))
    # 1.0, 2.0, 3.0

# 技巧:也可以把方程改寫成「兩邊各畫一條曲線看交點」
# cos(x) = x  ⟺  y = cos(x) 與 y = x 的交點
p2 = plot(cos(x), x, (x, -2, 2), show=False)
p2.show()   # 只有一個交點 → 只有一個根

也可以兩條線一起畫:plot(f1, f2, (x, a, b)),交點的 \(x\) 座標就是原方程的根。這招對「一邊是震盪函數」的方程特別好用。

solve vs nsolve 的選擇#

面向 solve()(符號解) nsolve()(數值解)
結果形式 精確式子( \(\sqrt{2}\) 、分數) 小數近似
能否得到全部根 多項式通常可以 一次只能給一個(靠初始值)
超越方程 常失敗或給特殊函數 幾乎總能給出近似根
適合場景 需要後續符號運算、證明、化簡 只要數值、驗證、畫圖資料
速度 可能極慢甚至停不下來 快

選擇原則:

  1. 多項式、簡單代數方程:先 solve(),拿不到再 nsolve()(或對 Poly.all_roots() 取 N())。
  2. 超越方程、只要數值:直接 plot 定位 + nsolve。
  3. 需要全部實根:plot 看個數,再逐個 nsolve;或 solveset / Poly.all_roots()。
from sympy import symbols, solve, nsolve, N, Poly, plot

x = symbols('x')

# 五次方程:solve 給不出根式解,但 Poly.all_roots() 可以(以代數數表示)
print(solve(x**5 - x - 1, x))          # CRootOf(...) 形式或空
roots = Poly(x**5 - x - 1, x).all_roots()
print(N(roots[0], 8))                  # 1.1673040  ← 唯一實根

動手實驗 1: \(\cos x = x\) 的解#

實驗目標:完整走一遍「畫圖定位 → nsolve 求根 → 驗證」流程。

from sympy import symbols, cos, nsolve, plot, N

x = symbols('x', real=True)

# 步驟 1:畫 y = cos(x) 與 y = x,確認交點個數與大致位置
p = plot(cos(x), x, (x, -3, 3), show=False)
p.show()    # 唯一交點,在 x ≈ 0.7 附近

# 步驟 2:nsolve 求根(試幾個不同的初始值)
for x0 in [-1, 0, 0.5, 1, 2]:
    print(x0, '→', N(nsolve(cos(x) - x, x, x0), 15))
# 全部收斂到 0.739085133215161(Dottie number)

# 步驟 3:驗證殘差
r = nsolve(cos(x) - x, x, 0.5, prec=50)
print(cos(r) - r)      # ≈ 0(高精度下幾乎為零)

# 步驟 4:牛頓法手動重現(預告第 X 章)——迭代 5 次看看收斂速度
f = cos(x) - x
fp = f.diff(x)
xn = 1.0                        # 初始值
for i in range(6):
    fxn = float(f.subs(x, xn))
    fpxn = float(fp.subs(x, xn))
    xn = xn - fxn / fpxn        # x_{n+1} = x_n - f/f'
    print(f"第 {i+1} 次迭代: x = {xn:.15f}")
# 觀察:有效位數幾乎每次翻倍 → 這就是「二次收斂」

動手實驗 2:根的個數與函數形狀的關係#

實驗目標:系統性掃描幾個函數,歸納「形狀 ⟹ 根個數」的規律。

from sympy import symbols, plot, nsolve, exp, sin, cos, Poly, N

x = symbols('x', real=True)

cases = {
    'x^2 - 2            (開口向上的拋物線)': x**2 - 2,          # 2 根
    'x^2 + 2            (拋物線整體在軸上方)': x**2 + 2,        # 0 根
    '(x-1)^2            (與軸相切)': (x - 1)**2,               # 1 個重根
    'x^3 - 3x           (S 形,穿越三段)': x**3 - 3*x,          # 3 根
    'exp(x) - 3*x       (先降後升的 U 形)': exp(x) - 3*x,       # 2 根
    'sin(x) - x/5       (震盪對上緩斜直線)': sin(x) - x/5,      # 多根
}

for name, f in cases.items():
    plot(f, (x, -6, 6), show=False).show()
    print(name)

# 以 sin(x) - x/5 為例:找出 [-6, 6] 內全部的根
f = sin(x) - x/5
guesses = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
found = []
for g in guesses:
    r = nsolve(f, x, g)
    if all(abs(float(r) - fr) > 1e-6 for fr in found):
        found.append(float(r))
print(sorted(found))
# [-2.876…, -2.536…, 0.0, 2.536…, 2.876…] 附近 → 5 個根
# (準確個數請以圖形為準:|sin x| 的坡度被 x/5 追上幾次,就有幾個根)

歸納出的規律:

  1. 變號才可能有根:若 \(f\) 在區間上連續且 \(f(a) f(b) < 0\) ,則 \((a,b)\) 內必有根(勘根定理 / 中間值定理)。
  2. 極值個數限制根個數: \(n\) 次多項式至多 \(n-1\) 個極值、至多 \(n\) 個實根;單峰(U 形)函數至多 2 個根。
  3. 切於 \(x\) 軸 = 重根: \((x-1)^2\) 只「碰」不「穿」,是偶數重根的幾何特徵。
  4. 震盪函數對緩慢變化的項:震盪幾次就可能交幾次——根的個數由「振幅被直線追上的次數」決定。

進一步挑戰:

  1. 把實驗 2 的係數 \(1/5\) 改成 \(1/2\) 、 \(1\) 、 \(2\) ,觀察 \(\sin x = kx\) 根個數的變化,找出「從多根變成單根」的臨界 \(k\) 。
  2. 對 \(e^x = 3x\) ,證明(用符號微分求極值)當 \(e^x = 3x\) 的判別條件 \(x = \ln 3\) 處函數值正負不同時,根的個數會從 2 變 0;把 3 換成參數 \(c\) ,求出臨界 \(c\) 值。
  3. 用 nsolve 解 \(\tan x = x\) 在 \((0, \pi/2)\) 內的根;再用圖形解釋為什麼這個區間內只有一個根( \(\tan x > x\) 恆成立?驗證看看)。

小結與練習#

小結

  • nsolve(expr, x, x0) 用牛頓法求數值根,初始值 \(x_0\) 決定收斂到哪個根;prec 可控制精度。
  • 許多方程( \(e^x = 3x\) 、 \(\cos x = x\) 、五次多項式)沒有初等解析解,數值方法是唯一務實選擇。
  • 找根流程:plot 目視定位 → 每個交點附近給 nsolve 初始值 → 代回驗證殘差。
  • 根的個數由函數形狀決定:勘根定理、極值個數、重根相切、震盪對緩變項,是四個核心直覺。

練習

  1. 用 nsolve 解 \(e^{-x} = x\) (答案即 \(\Omega\) 常數 \(\approx 0.5671\) ),並用牛頓法手動迭代 5 次重現收斂過程。
  2. 先用 plot 判斷 \(x^4 - 4x^3 + 2x^2 + 4x + 1 = 0\) 在 \([-1, 4]\) 內有幾個實根,再用 nsolve 全部求出,最後用 Poly(...).all_roots() 驗證個數。
  3. 解 \(\sin x = 0.1\) 在 \([0, 2\pi]\) 內的所有根:先用圖形數出個數,再逐個 nsolve。
  4. 方程 \(x^3 - x = c\) 有三個相異實根的條件是什麼?(提示:研究 \(f(x) = x^3 - x\) 的極大極小值,令 \(c\) 介於其間;用 nsolve 對臨界的 \(c\) 驗證。)
  5. 比較 solve(exp(x) - 3*x, x) 與兩個 nsolve 根的數值:LambertW 那個精確解對應哪一個數值根?另一個根為什麼 solve 沒給?