Math&Julia #08|二分法とニュートン・ラフソン法 » 平方根
はじめに
方程式を数値的に解くための代表的な手法として「二分法(bisection)」 と 「ニュートン・ラフソン法(Newton-Raphson)」があります。本稿ではこの 2つの手法を使って平方根を求める方程式を解きます。
平方根を求める方程式
ある数
知りたいのは
実例
中間値の定理
関数
実例
この結果より、区間
二分法(bisection)
二分法の基本的なアイデアは、中間値の定理に基づいて解が存在する区間を半分ずつ狭めていくというものです。

図 1 二分法で解(√2)を求める
初期区間の設定
初期区間
中点の計算
区間の中点を求める方法は直感的に分かる通りです。この中点
区間の更新
収束判定
次の収束判定が真になったら
-
は許容誤差ε
実例
図 1では式
ニュートン・ラフソン法(Newton–Raphson)
ニュートン・ラフソン法の基本的なアイデアは、関数の接線と

図 2 ニュートン・ラフソン法で解(√2)を求める
ニュートン・ラフソン法の漸化式を導出
点
この接線が
これを
平方根の漸化式を導出
今回はニュートン・ラフソン法を使って平方根を求めたいのでした。まずは、平方根の関数とその導関数を用意します。
式
これをさらに変形すると平方根の漸化式が導出されます。
これは結果的にはバビロニア法(古代バビロニアにおける平方根の計算方法)として知られている式です。
平方根の漸化式に初期値を設定
平方根の漸化式の初期値
図 2では
収束判定
次の収束判定が真になったら
-
は許容誤差ε
実例
実例として図 2の
実装
二分法とニュートン・ラフソン法の 2つの手法について、平方根の数値計算を実装します。両者とも反復計算を行いますが、その収束速度は大きく異なるので、これを視覚的に確認できるようにします。また、計算結果(近似解)については、高精度解と比較することにより精度を把握できるようにします。
using CairoMakie
using Printf
function sqrt_bisect(a, ε; maxiter=60) # 平方根(二分法)
f(x) = x ^ 2 - a # 平方根の関数(区間の設定で使用)
lo, hi = 0.0, max(1.0, a) # 区間の初期値
xs = Float64[] # 途中経過(中点)の記録用
for i in 1:maxiter
x = (lo + hi) / 2 # 区間の中点を求める
push!(xs, x) # 中点を記録
hi - lo < ε && break # 許容誤差内であれば早期打ち切りへ
f(x) > 0 ? hi = x : lo = x # 区間を狭めて再試行へ
end
xs
end
function sqrt_newton(a, ε; maxiter=20) # 平方根(ニュートン・ラフソン法)
f(x) = 0.5 * (x + a / x) # 平方根の漸化式
x = a > 1 ? a/2 : 1.0 # 漸化式の初期値
xs = Float64[x] # 途中経過(反復値)の記録用
for i in 1:maxiter
x_new = f(x) # 平方根の反復計算
push!(xs, x_new) # 反復値を記録
abs(x_new - x) < ε && break # 許容誤差内であれば早期打ち切りへ
x = x_new # 次の反復計算へ
end
xs
end
function raw_convergence(vals, val_true, a, title) # 平方根の近似解
val = vals[end] # 近似解を取得
err = abs(val - val_true) # 誤差を計算
acc = (1 - err/val_true) * 100 # 精度を計算
println("$title\n", "-"^40)
@printf "被開平数 : %s\n" a
@printf "反復回数 : %d\n" length(vals)
@printf "平方根‐近似解 : %.17f\n" val
@printf "平方根‐高精度解 : %.77f\n" val_true
@printf "誤差 : %.17f\n" err
@printf "精度 : %.17f%%\n\n" acc
end
function vis_convergence(vals_bisect, vals_newton, val_true) # 収束速度の比較
errs_bisect = abs.(vals_bisect .- val_true) # 誤差(二分法)
errs_newton = abs.(vals_newton .- val_true) # 誤差(ニュートン・ラフソン法)
fig = Figure()
ax = Axis(fig[1,1], yscale=log10, xlabel="Iteration", ylabel="Error")
scatterlines!(ax, errs_bisect, color=:blue, marker=:circle, label="Bisection")
scatterlines!(ax, errs_newton, color=:red, marker=:diamond, label="Newton-Raphson")
axislegend(ax)
display(fig)
end
ハンズオン
a = 2 # 被開平数
ε = 1e-16 # 許容誤差
val_true = sqrt(big(a)) # 平方根(高精度解)
vals_bisect = sqrt_bisect(a, ε) # 平方根(二分法)
vals_newton = sqrt_newton(a, ε) # 平方根(ニュートン・ラフソン法)
vis_convergence(vals_bisect, vals_newton, val_true) # 収束速度の比較
raw_convergence(vals_bisect, val_true, a, "二分法")
raw_convergence(vals_newton, val_true, a, "ニュートン・ラフソン法")

図 3 収束分析(√2)
二分法
----------------------------------------
被開平数 : 2
反復回数 : 60
平方根‐近似解 : 1.41421356237309492
平方根‐高精度解 : 1.41421356237309504880168872420969807856967187537694807317667973799073247846210
誤差 : 0.00000000000000013
精度 : 99.99999999999999113%
ニュートン・ラフソン法
----------------------------------------
被開平数 : 2
反復回数 : 7
平方根‐近似解 : 1.41421356237309492
平方根‐高精度解 : 1.41421356237309504880168872420969807856967187537694807317667973799073247846210
誤差 : 0.00000000000000013
精度 : 99.99999999999999113%
ニュートン・ラフソン法は二分法と比べて少ない反復回数で急速に収束しているのが分かります。また、両者の近似解を比較すると同等の精度を確保していることが分かります(表示範囲の限りでは同じです)。
本稿の内容だけだとニュートン・ラフソン法の方が一方的に優れているように見えますが、こちらは条件によっては収束しないことがあります。一方の二分法は中間値の定理を満たせば必ず収束するという性質があります。それぞれの性質についての詳細は省きますが、実用面では両者の長所を組み合わせたハイブリッド戦略も有効とされています。
Discussion