ニュートン・ラフソン法による逆数の求め方メモ

そもそもニュートン・ラフソン法ってなんだっけ?

ニュートン・ラフソン法(単にニュートン法ともいう)とは、方程式の解を近似値として反復的に求める手法です。

ニュートン・ラフソン法で逆数や平方根などを求める場合、普通はf(x)=0の解がその求めたい数になるようにします。

ということで最終目標は、f(x)=0を満たすときのxを求めることです。

大雑把な流れを書いていきます。


decide X0
x_0を決める

まずxとして有効な値を決め、初期値x_0とします。

左図の赤線をこのx_0とします。



tangent line of f(x) at X0
x=x_0におけるf(x)の接線

次にx=x_0で接するf(x)の接線を考えます。

左図の青い線をこの接線とします。



X1 decided
接線とx軸との交点のx座標がx_1

この接線とx軸との交点におけるx座標をx_1とします。



tangent line of f(x) at X1
x=x_1におけるf(x)の接線

今度は、x=x_1で接するf(x)の接線を考えます。



X2 decided
接線とx軸との交点のx座標がx_2

先ほどと同じように、接線とx軸との交点におけるx座標を、今度はx_2とします。


tangent line of f(x) at X2
x=x_2におけるf(x)の接線

以下、同様にx_3,x_4,x_5,\cdots

と、x_nを求めていきます。


X3 decided and compare with X0
接線とx軸との交点のx座標がx_3x_0との比較

すると、やがてx_nf(x)x軸との交点に近づいていきます。

左図でx_0x_3を比較すると、解であるf(x)x軸との交点に近づいていることがわかります。

これは、x_nf(x)=0の解に収束することを意味します。

以上のようにして、ニュートン・ラフソン法でf(x)=0の解を近似値として求めることができます。

逆数を求める

ニュートン・ラフソン法で逆数を求める際の式、f(x)

逆数を求めたい数を、SourceよりSrcとすることとします。f(x)=0を満たすときのxの値が解となるようにするので、

f(x)=\frac{1}{x}-Src
Srcは逆数を求めたい数)

となります。

x=x_nにおけるf(x)の接線

まず接線の傾きは、

f(x)

~=\frac{1}{x}-Src

~=x^{-1}-Src

f'(x)=-x^{-2}=-\frac{1}{x^2}

となります。

接線のy切片のy座標をYintとすると、x=x_nにおける接線を表す式(tangent lineよりt(x)とします)は、

t(x)=f'(x_n)x+Yint=-\frac{1}{x_n^2}x+Yint
t(x)x=x_nにおける接線)
Yintは接線のy切片のy座標)

となります。


cross
t(x)f(x)の交点

Yintを求めます。接点座標は、f(x_n)=\frac{1}{x_n}-Srcより、(x_n,\frac{1}{x_n}-Src)となります。これをt(x)に当てはめると、


\frac{1}{x_n}-Src=-\frac{1}{x_n^2}x_n+Yint

Yint=\frac{2}{x_n}-Src
Yintは接線のy切片のy座標)
Srcは逆数を求めたい数)

と、Yintが求まりました。

以上より、最終的にx=x_nにおける接線t(x)は、

t(x)=-\frac{1}{x_n^2}x+\frac{2}{x_n}-Src
t(x)x=x_nにおける接線)
Srcは逆数を求めたい数)

となります。

x_nからx_{n+1}を求める漸化式

x_nからx_{n+1}を求めるには、x_nにおける接線のx切片のx座標を求めます。

Xn to Xn+1
t(x)=0の解がx_{n+1}

要するに、先程求めた接線が0になる解を求めればいいのです。

t(x_{n+1})=0

-\frac{1}{x_n^2}x_{n+1}+\frac{2}{x_n}-Src=0

-x_{n+1}+2x_n-Src\,x_n^2=0

x_{n+1}=2x_n-Src\,x_n^2

x_{n+1}=x_n(2-Src\,x_n)

これが漸化式となります。

あとは、この漸化式を使ってx_nからx_{n+1}への変化が極僅かとなるまで収束させ続けます。

ニュートン・ラフソン法で逆数を求めるプログラムを書いてみる

Golang(Go言語、正式には単に「Go」らしいです)の入門も兼ねてGoで書いてみました。

A Tour of Go(日本語版)Packages - The Go Programming Languageに入門者向けの読み物と、パッケージ(API?)一覧がありますので、これを読みながら書きました。

x_0をどうするか

ニュートン・ラフソン法では、x_0が解に近いほど早く収束します。故にx_0はなるべく解に近づけます。

逆数を求める場合は、Src(逆数を求めたい数)がSrc>0のときx_0が解より大きすぎると、x_1<0となってしまい、収束しないはずです。故に0 < x_0 < \frac{1}{Src}を満たすようにします。(Src<0のときは不等号を逆にして考えてください。)

最善策がよくわからないので、まずx_0=Srcとし、Src \times x_01未満になるまでx_00.1をかけ続けるようにしました。

終了条件はどうするか

Src \times x_{n+1}1に近づき続ける間は、続けるようにしました。


ニュートン・ラフソン法で逆数を求める関数

func reciprocal(Src float64) float64 {
	if Src == 0.0 {
		return math.NaN()
	}

	var newdist /*終了判定用変数*/, newX /*Xn+1*/ float64
	var olddist /*終了判定用変数*/, oldX /*Xn*/ float64 = math.Inf(0), Src * 0.1

	//X0を決定。Src*X0が1より小さくなるまでX0を0.1倍し続ける。
	for {
		if Src*oldX <= 1.0 {
			break
		}
		oldX *= 0.1
	}

	for {
		//漸化式でXn+1を求める
		newX = oldX * (2.0 - oldX*Src)

		//前回よりもSrc*Xn+1が1に近づいたら続行
		newdist = math.Abs(1.0 - newX*Src)
		if newdist >= olddist {
			break
		}
		olddist = newdist

		//Xnを更新
		oldX = newX
	}

	return oldX
}

ニュートン・ラフソン法で逆数を求め除算をするプログラム

上記関数を使って除算をするプログラムです。

package main

import (
	"fmt"
	"math"
	"os"
	"strconv"
)

func main() {
	var v1, v2 float64
	var stat bool

	fmt.Printf("入力1>> ")
	stat, v1 = readValidValue()
	if !stat {
		fmt.Printf("数値をいれよ\n")
		return
	}

	fmt.Printf("入力は%Fであるな\n", v1)

	fmt.Printf("入力2>> ")
	stat, v2 = readValidValue()
	if !stat {
		fmt.Printf("数値をいれよ\n")
		return
	}

	fmt.Printf("入力は%Fであるな\n", v2)

	fmt.Printf("%F/%Fは%Fである\n", v1, v2, divide(v1, v2))
}

//数値入力(ふざけた入力は受け付けない)
func readValidValue() (bool, float64) {
	var buf /*直接入力するバッファ*/, num /*入力を溜め込み、後に[]byte→string→float64と変換*/ []byte
	var dotAppeared, invalid = false, false
	var ret float64

	buf = make([]byte, 1)
	num = make([]byte, 0, 127)

	for {
		//一文字(1byte)入力
		os.Stdin.Read(buf)
		if buf[0] == byte('\n') {
			//改行コードなら入力終了
			if len(num) == 0 {
				//このとき、入力が改行コードのみだった場合、不正な入力とする
				invalid = true
			}
			break
		} else if buf[0] == byte('-') && len(num) == 0 {
			//負の数を入力するため、先頭のみ「-」を許容
			num = append(num, buf[0])

		} else if buf[0] == byte('-') && len(num) != 0 {
			//先頭以外で-を入力したら不正な入力とする
			invalid = true
			readForNewLine()
			break

		} else if buf[0] == byte('.') && len(num) >= 1 && !dotAppeared {
			//少数入力のため、2文字目以降の初めての「.」のみ許容する
			dotAppeared = true
			num = append(num, buf[0])

		} else if buf[0] == byte('.') && (len(num) == 0 || dotAppeared) {
			//先頭または二度目の.なら不正な入力とする
			invalid = true
			readForNewLine()
			break

		} else if buf[0] >= byte('0') && buf[0] <= byte('9') {
			//0〜9は常に許容
			num = append(num, buf[0])

		} else {
			//あとはすべて不正な入力とする
			invalid = true
			readForNewLine()
			break

		}
	}

	ret, _ = strconv.ParseFloat(string(num), 64)

	return !invalid, ret
}

//読み捨て用の関数
func readForNewLine() {
	buf := make([]byte, 1)

	for {
		os.Stdin.Read(buf)
		if buf[0] == byte('\n') {
			break
		}
	}
}

//除算を逆数との掛け算として求める関数
func divide(a, b float64) float64 {
	if b == 0.0 {
		return math.NaN()
	}

	return a * reciprocal(b)
}

//ニュートン・ラフソン法で逆数を求める関数
func reciprocal(Src float64) float64 {
	if Src == 0.0 {
		return math.NaN()
	}

	var newdist /*終了判定用変数*/, newX /*Xn+1*/ float64
	var olddist /*終了判定用変数*/, oldX /*Xn*/ float64 = math.Inf(0), Src * 0.1

	//X0を決定。Src*X0が1より小さくなるまでX0を0.1倍し続ける。
	for {
		if Src*oldX < 1.0 {
			break
		}
		oldX *= 0.1
	}

	for {
		//漸化式でXn+1を求める
		newX = oldX * (2.0 - oldX*Src)

		//前回よりもSrc*Xn+1が1に近づいたら続行
		newdist = math.Abs(1.0 - newX*Src)
		if newdist >= olddist {
			break
		}
		olddist = newdist

		//Xnを更新
		oldX = newX
	}

	return oldX
}

実行結果

[アホのPC]$go run main.go
入力1>> 8
入力は8.000000であるな
入力2>> 32
入力は32.000000であるな
8.000000/32.000000は0.250000である

無事に計算できてますが、特にGolangでなくてもいいような出来になりました。