PHPを使って√2を求める
概要
この解法として有名なのは二分法とニュートン(ラフソン)法がある。
私はPHPはほぼ書いたことがないがこれを練習問題にしてみようと思う。
設計のようなもの
方程式を解くということは解法によらないはずである。そのため解き方というアルゴリズムの実装をインターフェイスとして分離する。
また、例は
解法について
二分法もニュートン法も繰り返し法の一種である誤差に対してそれよりも予測される誤差が小さくなったときにそれを解とする方法である。(なので真の
紹介した2つの方法は簡単には以下のようであり、二分法は解がある限り安定的にその解を探すことができる。一方でニュートン法は収束が速い一方、解から遠く関数系が複雑なときには解には向かわずに振動する場合がある。
二分法
いわゆる挟み撃ちである、解が間にある2つの点を取ると
ニュートン法
微分を用いた方法である初期値から一次式で近似して次の解を求めて、その差(+関数の値)から収束しているかを判断して解を求める方法である。
実装
Solver
方程式を解くモノのインターフェイスとしてSolverを用意する、引数は第一引数を解かせたい方程式を関数オブジェクトとして第二引数に初期値にする。
また、打ち切り誤差は定数としてSolverに持たせておく。
その実装として二分法(BySelection)とニュートン法(Newton)クラスを用意する。
内部実装は以下のとおりである。
<?php
interface Solver{
const threshold=1.0e-8;
public function solve($func, $x);
}
class BySelection implements Solver{
public function solve($func, $x){
$x1=0.5*$x;
$x2=1.5*$x;
$y =$func($x);
$y1=$func($x1);
$y2=$func($x2);
$count=0;
if( $y1*$y2>0 ) throw new Exception('二分法、初期値エラー');
while( true ){
$count++;
if( $y*$y1>0 ){
$x1=$x;
$y1=$y;
}
else{
$x2=$x;
$y2=$y;
}
$x=0.5*($x1+$x2);
$y=$func($x);
if( abs($y1)<Solver::threshold && abs($y2)<Solver::threshold && abs($x1-$x2)<Solver::threshold ) return $x;
if( $count==1000 ) throw new Exception('二分法で収束しませんでした');
}
}
}
class Newton implements Solver{
public function solve($func, $x){
$count=0;
while( true ){
$count++;
$y=$func($x);
$dy=($func($x+Solver::threshold)-$func($x-Solver::threshold))/(2.0*Solver::threshold);
if( abs($y)<Solver::threshold && abs($y/$dy)<Solver::threshold ) return $x;
$x=$x-$y/$dy;
if( $count==1000 ) throw new Exception('ニュートン法で収束しませんでした');
}
}
}
?>
二分法はアルゴリズムとしては単純なので読めると思う。
ニュートン法では直線近似のために微分を使うがそれは数値微分を用いた。
main関数
実際に使う側では以下のようになる。
$solver
の実体をBySelectionかNewtonかで内部のアルゴリズムは変更できるが結果は変わらない(はずである)。
<?php
require_once('Solver.php');
// $func=function($x){ return cos($x); };
$func=function($x){ return x*x-2; };
// $sovler=new BySelection();
$solver=new Newton();
var_dump(2.0*$solver->solve($func, 2.0));
?>
不備など
一応、これで想定したケースはいけるが一般の関数系となると初期値などにより解が求まらないことがある。(二分法の初期値の雑な処理等が最たる例)
数値計算では収束条件(threshold)やイテレーション回数等はアルゴリズムや解きたい問題に大きく依存するのでここまでのブラックボック化が適切ではないとおもう。
あとがきと感想
PHPをほとんど使ったことがない筆者が単体で動かせるサンプルとして数値計算をさせてみた、PHPがその用途に向いてるとは思わないがインターフェイスやラムダ式を使うことでかなりの対応ができると思った。
また、PHPの定数の使い方や例外処理の飛ばし方がわかった(このレベル)。
Discussion