非線形方程式の数値解法 続き

非線形方程式y=f(x)で、指定されたxの範囲の最小値(最下点)あるいは最大値(最上点)を求める計算です。

 微分が出来れば、変化がゼロになる点を求めれば簡単に計算が出来ますが、微分が容易でない場合二分法に近い方法で計算しますが、f(a)f(b)<0になる事は有りません。
この場合は、指定範囲の片方から指定のピッチで計算し、前の計算値と、新しい値の計算値の差分の符号が変化する場所を検出し、最小値(最下点)あるいは最大値(最高点)を求めます。
{f(a)-f(b)}{f(b)-f(c)] < 0 となる点です。
最初は、ある程度大きなピッチで計算し、符号がが反転したら、ピッチを二つ戻し、ピッチを小さくし再度計算することを繰り替えし、ピッチが指定値epsilon以下になったら、計算終了とします。
ピッチを二つ戻すのは、最小値(最下点)あるいは最大値(最高点)をピッチが過ぎても、差分の符号が反転しない場合があるからです、この場合は二つ過ぎたところで符号が反転します。
この手法は、ねじ歯車の軸間距離の計算に使用しています。


プログラム
 プログラム、簡単に微分可能な計算式となっていますが、あくまでもプログラム例です。

// 非線形方程式の最大値(頂点)又は最小値(谷点)検出
// 最大値(頂点)のx軸方向の値は、誤差が大きくなります、最大値(頂点)は水平に成るためです。
// グラフを最初に作図して、範囲の確認をします。
unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VclTee.TeeGDIPlus, VCLTee.Series,
  Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls, VCLTee.TeEngine, VCLTee.TeeProcs,
  VCLTee.Chart;

type
  TForm1 = class(TForm)
    Chart1: TChart;
    Series1: TLineSeries;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    BitBtn1: TBitBtn;
    Memo1: TMemo;
    Series3: TPointSeries;
    Series4: TPointSeries;
    LabeledEdit3: TLabeledEdit;
    LabeledEdit4: TLabeledEdit;
    BitBtn2: TBitBtn;
    CheckBox1: TCheckBox;
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure LabeledEdit1Change(Sender: TObject);
    procedure LabeledEdit2Change(Sender: TObject);
    procedure LabeledEdit3Change(Sender: TObject);
    procedure LabeledEdit4Change(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure chart_graph(gl, gr: double);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// 関数f(x)の計算式 非線形方程式
function f(x: double): double;
begin
  result := (x - 3) * (x - 3) - 2;
//  result := x * x;
  if form1.CheckBox1.Checked = true then result := -result;
end;

// 区間[gl,gr]の関数f(x)のグラフを作図
procedure TForm1.chart_graph(gl, gr: double);
const
  n = 100;
var
  x, xs, xe, dx: double;
  y : double;
  i : integer;
begin
  series1.clear;
  series3.clear;
  series4.clear;
  if gl = gr then exit;                     // 範囲がゼロだったら終了
  xs := gl;
  xe := gr;
  dx := (xe - xs) / n;
  for i := 0 to n do begin
    x := xs + i * dx;
    y := f(x);
    series1.AddXY(x, y);
  end;
end;

// 頂点の近似値計算
function bisection_method(a, p, epsilon: double; var loop: integer): double;
var
  fn, fa: double;
  chf : boolean;
begin
  // 初期指定点とピッチ確認
  fn := f(a);                            // 初期点計算
  fa := f(a + p);
  chf := false;
  if form1.CheckBox1.Checked = true then begin
    if fn > fa then chf := true;         // 上向き頂点
  end
  else begin
    if fn < fa then chf := true;         // 下向き頂点 最下点
  end;
  if chf then begin
    result := 0;
    exit;
  end;
  // 頂点の計算
  repeat
    chf := false;
    fa := fn;                            // 前の値
    a := a + p;                          // 次の点
    fn := f(a);                          // 次の点計算
    if form1.CheckBox1.Checked = true then begin
      if fn < fa then chf := true;       // 上向き頂点
    end
    else begin
      if fn > fa then chf := true;       // 下向き頂点 最下点
    end;
    if chf then begin
      a := a - p - p;                    // 2ピッチ戻し
      p := p / 4;
      fn := f(a);                        // 元の値再計算
    end;
    inc(loop);
  until (p < epsilon * (abs(a) + 1)) or (loop > 500);
  result := a;
end;

// 計算実行
procedure TForm1.BitBtn1Click(Sender: TObject);
var
  a, p, alpha, y : double;
  loop : integer;
  epsilon, tmp : double;
begin
  a := strTofloat(labelededit1.Text);
  p := strTofloat(labelededit2.Text);
  memo1.Clear;
  // 収束判定値計算
  epsilon := 1;
  repeat
    epsilon := epsilon / 2;
    tmp := 1 + epsilon;
  until tmp = 1;
  epsilon := epsilon * 2;
  // 非線形方程式の計算
  loop := 0;
  alpha := bisection_method(a, p, epsilon, loop);
  if loop = 0 then begin
    application.MessageBox('初期値の位置に間違いがある、又はピッチが大きすぎます。','注意',0);
    exit;
  end;
  // 検算
  y := f(alpha);
  // 計算結果表示
  series4.AddXY(alpha, y);
  memo1.Lines.Append('f(x) = (x - 3)^2 - 2');
  memo1.Lines.Append('ループ数 ' + intTostr(loop));
  memo1.Lines.Append('収束判定値');
  memo1.Lines.Append(floatTostr(epsilon));
  memo1.Lines.Append('x= ' +floatTostr(alpha));
  memo1.Lines.Append('検算 y = f(x)');
  memo1.Lines.Append('y= ' +floatTostr(y));
end;

procedure TForm1.BitBtn2Click(Sender: TObject);
var
  gl, gr : double;
  ch : integer;
  a, p, y : double;
begin
  val(labelededit3.Text, gl, ch);
  if ch <> 0 then begin
    application.MessageBox('グラフ範囲左に間違いが有ります。','注意',0);
    exit;
  end;
  val(labelededit4.Text, gr, ch);
  if ch <> 0 then begin
    application.MessageBox('グラフ範囲右に間違いが有ります。','注意',0);
    exit;
  end;
  if gr <= gl then begin
    application.MessageBox('グラフ範囲右には左より大きくして下さい。','注意',0);
    exit;
  end;

  chart_graph(gl, gr);
  application.ProcessMessages;

  val(labelededit1.Text, a, ch);
  if ch <> 0 then begin
    application.MessageBox('初期値aに間違いが有ります。','注意',0);
    exit;
  end;
  val(labelededit2.Text, p, ch);
  if ch <> 0 then begin
    application.MessageBox('初期間隔pに間違いが有ります。','注意',0);
    exit;
  end;
  if (a < gl) or (a > gr) then begin
    application.MessageBox('初期値aがグラフの範囲外です。','注意',0);
    exit;
  end;
  if a > (gr - gl) / 2 + gl then begin
    application.MessageBox('初期値aは左寄りにして下さい。','注意',0);
    exit;
  end;
  y := f(a);
  series3.AddXY(a, y);
  application.ProcessMessages;
  if p > (gr - gl) / 2 then begin
    application.MessageBox('初期間隔pが大きすぎます。','注意',0);
    exit;
  end;
  if p <= 0 then begin
    application.MessageBox('初期間隔pはゼロより大きくして下さい。','注意',0);
    exit;
  end;
  y := f(a + p);
  series4.AddXY(a + p, y);
  application.ProcessMessages;
  bitbtn1.Enabled := True;
end;

procedure TForm1.LabeledEdit1Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit2Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit3Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit4Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;


end.

 ギヤ(歯車)の計算の場合、計算の精度は千分の一以下であれば精度で実用上問題ないのでDoubleの精度でOKですが、大きな精度が必要な場合は、多倍長bigdecimalの計算が必要です。
bigdecimalの組み込みは、ベルヌーイ数その4big_mathについては、bigdecimalによるmathを参照してください。
Delphiの多倍長計算にはMPArithもあります、これを使用するのであれば、組み込み方は、ベルヌーイ数その2を参照してください。
MPArithははfunction形式が使用出来ない為、工夫が必要です。

bigdecimalによるプログラム

// 非線形方程式の最大値最小値検出
// 最大値最小値のx軸方向の値は、誤差が大きくなります、最大値最小値の近辺は水平に成るためです。
// グラフを最初に作図して、範囲の確認をします。
unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VclTee.TeeGDIPlus, VCLTee.Series,
  Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls, VCLTee.TeEngine, VCLTee.TeeProcs,
  VCLTee.Chart;

type
  TForm1 = class(TForm)
    Chart1: TChart;
    Series1: TLineSeries;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    BitBtn1: TBitBtn;
    Memo1: TMemo;
    Series3: TPointSeries;
    Series4: TPointSeries;
    LabeledEdit3: TLabeledEdit;
    LabeledEdit4: TLabeledEdit;
    BitBtn2: TBitBtn;
    CheckBox1: TCheckBox;
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure LabeledEdit1Change(Sender: TObject);
    procedure LabeledEdit2Change(Sender: TObject);
    procedure LabeledEdit3Change(Sender: TObject);
    procedure LabeledEdit4Change(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure chart_graph(gl, gr: double);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses Velthuis.Bigdecimals, Velthuis.bigintegers, big_Math;

{$R *.dfm}

{
function BigdecimalToDouble(b: bigdecimal): double;
var
  bigstr : string;
  absb : bigdecimal;
begin
  absb := bigdecimal.Abs(b);
  if absb = bigdecimal.Zero then begin
    result := 0;
    exit;
  end;
  if absb >= maxdouble then begin
    result := maxdouble;
    if b < bigdecimal.Zero then result := -result;
    exit;
  end;
  if absb <= mindouble then begin
    result := mindouble;
    if b < bigdecimal.Zero then result := -result;
    exit;
  end;
  b := b.RoundToPrecision(30);
  bigstr := b.ToString;
  result := strtofloat(bigstr);
end;
}

// 関数f(x)の計算式 非線形方程式 bigdecimal
function f(x: bigdecimal): bigdecimal;
begin
  result := (x - 3) * (x - 3) - 2;
//  result := x * x;
  if form1.CheckBox1.Checked = true then result := -result;
end;

// 区間[gl,gr]の関数f(x)のグラフを作図
procedure TForm1.chart_graph(gl, gr: double);
const
  n = 100;
var
  x, xs, xe, dx: double;
  y : double;
  i : integer;
begin
  series1.clear;
  series3.clear;
  series4.clear;
  if gl = gr then exit;                     // 範囲がゼロだったら終了
  xs := gl;
  xe := gr;
  dx := (xe - xs) / n;
  for i := 0 to n do begin
    x := xs + i * dx;
    y := bigdecimaltodouble(f(x));
    series1.AddXY(x, y);
  end;
end;

// 頂点の近似値計算
function bisection_method(a, p, epsilon: bigdecimal; var loop: integer): bigdecimal;
var
  fn, fa: bigdecimal;
  chf : boolean;
begin
  // 初期指定点とピッチ確認
  fn := f(a);                                // 初期点計算
  fa := f(a + p);
  chf := false;
  if form1.CheckBox1.Checked = true then begin
    if fn > fa then chf := true;             // 上向き頂点
  end
  else begin
    if fn < fa then chf := true;             // 下向き頂点 最下点
  end;
  if chf then begin
    result := 0;
    exit;
  end;
  // 頂点検出
  repeat
    chf := false;
    fa := fn;                                // 前の値
    a := a + p;                              // 次の点
    fn := f(a);                              // 次の点計算
    if form1.CheckBox1.Checked = true then begin
      if fn < fa then chf := true;           // 上向き頂点
    end
    else begin
      if fn > fa then chf := true;           // 下向き頂点 最下点
    end;
    if chf then begin
      a := a - p - p;                        // 2ピッチ戻し
      p := p / 4;
      fn := f(a);                            // 元の値再計算
    end;
    inc(loop);
  until (p < epsilon * (bigdecimal.abs(a) + 1)) or (loop > 5000);
  result := a;
end;

// 計算実行
procedure TForm1.BitBtn1Click(Sender: TObject);
var
  a, p, alpha, y : bigdecimal;
  loop, prec : integer;
  epsilon : bigdecimal;
  estr : string;

  yd, ad : double;
begin
  a := labelededit1.Text;
  p := labelededit2.Text;
  memo1.Clear;
  // 収束判定値計算
  // 非線形方程式の計算
  prec := bigdecimal.DefaultPrecision;
  prec := prec - 1;
  estr := '1e-' + intTostr(prec);
  epsilon := estr;
  loop := 0;
  alpha := bisection_method(a, p, epsilon, loop);
  if loop = 0 then begin
    application.MessageBox('初期値の位置に間違いがある、又はピッチが大きすぎます。','注意',0);
    exit;
  end;
  // 検算
  y := f(alpha);
  yd := bigdecimaltodouble(y);
  ad := bigdecimaltodouble(alpha);
  // 計算結果表示
  series4.AddXY(ad, yd);
  if checkbox1.Checked = true then
    memo1.Lines.Append('f(x) = - ((x - 3)^2 - 2)')
  else
    memo1.Lines.Append('f(x) = (x - 3)^2 - 2');
  memo1.Lines.Append('ループ数 ' + intTostr(loop));
  memo1.Lines.Append('収束判定値');
  memo1.Lines.Append(epsilon.ToString);
  memo1.Lines.Append('x= ' + floatTostr(ad));
  memo1.Lines.Append('検算 y = f(x)');
  memo1.Lines.Append('y= ' + floatTostr(yd));
end;

procedure TForm1.BitBtn2Click(Sender: TObject);
var
  gl, gr : double;
  ch : integer;
  a, p, y : double;
begin
  val(labelededit3.Text, gl, ch);
  if ch <> 0 then begin
    application.MessageBox('グラフ範囲左に間違いが有ります。','注意',0);
    exit;
  end;
  val(labelededit4.Text, gr, ch);
  if ch <> 0 then begin
    application.MessageBox('グラフ範囲右に間違いが有ります。','注意',0);
    exit;
  end;
  if gr <= gl then begin
    application.MessageBox('グラフ範囲右には左より大きくして下さい。','注意',0);
    exit;
  end;

  chart_graph(gl, gr);
  application.ProcessMessages;

  val(labelededit1.Text, a, ch);
  if ch <> 0 then begin
    application.MessageBox('初期値aに間違いが有ります。','注意',0);
    exit;
  end;
  val(labelededit2.Text, p, ch);
  if ch <> 0 then begin
    application.MessageBox('初期間隔pに間違いが有ります。','注意',0);
    exit;
  end;
  if (a < gl) or (a > gr) then begin
    application.MessageBox('初期値aがグラフの範囲外です。','注意',0);
    exit;
  end;
  if a > (gr - gl) / 2 + gl then begin
    application.MessageBox('初期値aは左寄りにして下さい。','注意',0);
    exit;
  end;
  y := bigdecimaltodouble(f(a));
  series3.AddXY(a, y);
  application.ProcessMessages;
  if p > (gr - gl) / 2 then begin
    application.MessageBox('初期間隔pが大きすぎます。','注意',0);
    exit;
  end;
  if p <= 0 then begin
    application.MessageBox('初期間隔pはゼロより大きくして下さい。','注意',0);
    exit;
  end;
  y := bigdecimaltodouble(f(a + p));
  series4.AddXY(a + p, y);
  application.ProcessMessages;
  bitbtn1.Enabled := True;
end;

procedure TForm1.LabeledEdit1Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit2Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit3Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit4Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  bitbtn1.Enabled := false;
  memo1.Clear;
  series1.clear;
  series3.clear;
  series4.clear;
end;

end.


 非線形方程式と傾きを指定された直線との接点計算

 非線形方程式y=f(x)で、指定されたxの範囲の最小値(最下点)あるいは最大値(最上点)を求める計算の応用です。

直線 y=αx

 直線の傾きをαとすると、非線形方程式の値を、-α 分座標変換して最小値(最下点)あるいは最大値(最上点)をを求めます。
座標変換に三角関数を使用する為、計算の誤差が大きくなると同時に、計算繰り返し数が多くなります。

 微分が出来れば、簡単に直線の方程式を導き出せます。
プログラムは例なので微分できる方程式を使用しています。

プログラム

// 非線形方程式と傾き指定の接線計算
// 非線形lineを-傾き分座標変換して頂点として計算します。
// 非線形方程式の頂点又は接線検出
// 頂点のx軸方向の値は、誤差が大きくなります、頂点は水平に成るためです。
// グラフを最初に作図して、範囲の確認をします。
unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VclTee.TeeGDIPlus, VCLTee.Series,
  Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls, VCLTee.TeEngine, VCLTee.TeeProcs,
  VCLTee.Chart;

type
  TForm1 = class(TForm)
    Chart1: TChart;
    Series1: TLineSeries;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    BitBtn1: TBitBtn;
    Memo1: TMemo;
    Series3: TPointSeries;
    Series4: TPointSeries;
    LabeledEdit3: TLabeledEdit;
    LabeledEdit4: TLabeledEdit;
    BitBtn2: TBitBtn;
    CheckBox1: TCheckBox;
    LabeledEdit5: TLabeledEdit;
    Series2: TLineSeries;
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure LabeledEdit1Change(Sender: TObject);
    procedure LabeledEdit2Change(Sender: TObject);
    procedure LabeledEdit3Change(Sender: TObject);
    procedure LabeledEdit4Change(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure LabeledEdit5Change(Sender: TObject);
  private
    { Private 宣言 }
    procedure chart_graph(gl, gr: double);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


// x, y  座標
// q : 角度(rad)
// xt, yt 変換後座標
procedure transformation(x, y, q : double; var xt, yt : double);
var
  qt, ysx, l : double;
begin
  if x <> 0 then begin
    ysx := y / x;
    qt := arctan(ysx);
  end
  else qt := pi / 2;
  qt := qt + q;
  l := x * x + y * y;
  l := Sqrt(l);
  xt := cos(qt) * l;
  yt := sin(qt) * l;
end;

// 関数f(x)の計算式 非線形方程式
// f(x) = (x-3)^2 - 2
// f'(x) = x / 2 - 6
// 傾きαの接線のx座標 α= x/2-6,  x= (α+6)/2
function f(x: double): double;
begin
  result := (x - 3) * (x - 3) - 2;
//  result := x * x;
  if form1.CheckBox1.Checked = true then result := -result;
end;

// グラフ範囲内 傾き確認
// gl グラフ左端
// gr グラフ右端
// a 直線傾き
// チェックピッチ
function tiltcheck(gl, gr, a, p: double): boolean;
var
  sx, ex, sy, ey, sa, ea : double;
  n, i : integer;
begin
  result := false;
  p := p / 10;
  n := round((gr - gl) / p);
  sx := gl;
  sy := f(gl);
  ex := gl + p;
  ey := f(ex);
  sa := (ey - sy) / (ex - sx) - a;
  for i := 2 to n do begin
    sx := ex;
    sy := ey;
    ex := gl + i * p;
    ey := f(ex);
    ea := (ey - sy) / (ex - sx) - a;
    if ea * sa <= 0 then begin
      result := true;
      break;
    end;
    sa := ea;
  end;
end;

// 線分
procedure line_segment(x, y, a, gl, gr: double);
var
  c : double;
begin
  c := y - a * x;
  y := a * gl + c;
  form1.Series2.AddXY(gl, y);
  y := a * gr + c;
  Form1.Series2.AddXY(gr, y);
  Form1.Memo1.Lines.Append('接線式');
  if c >= 0 then
    Form1.Memo1.Lines.Append(' y = ' + floatTostr(a) + 'x + ' + floatTostr(c))
  else
    Form1.Memo1.Lines.Append(' y = ' + floatTostr(a) + 'x ' + floatTostr(c))
end;

// 区間[gl,gr]の関数f(x)のグラフを作図
procedure TForm1.chart_graph(gl, gr: double);
const
  n = 100;
var
  x, xs, xe, dx: double;
  y : double;
  i : integer;
begin
  series1.clear;
  series2.clear;
  series3.clear;
  series4.clear;
  if gl = gr then exit;                     // 範囲がゼロだったら終了
  xs := gl;
  xe := gr;
  dx := (xe - xs) / n;
  for i := 0 to n do begin
    x := xs + i * dx;
    y := f(x);
    series1.AddXY(x, y);
  end;
end;

// 頂点の近似値計算
function bisection_method(a, p, q, epsilon: double; var loop: integer): double;
var
  fn, fa, fnt, fat: double;
  nt, at : double;
  chf : boolean;

begin
  q := arctan(q);
  fn := f(a);                           // 初期点計算
  transformation(a, fn, q, at, fnt);
  a := a + p;                           // 次の点
  fa := f(a);                           // 次の点計算
  transformation(a, fa, q, nt, fat);
  chf := false;
  if form1.CheckBox1.Checked = true then begin
    if fnt > fat then chf := true;        // 上向き頂点
  end
  else begin
    if fnt < fat then chf := true;        // 下向き頂点   最下点
  end;
  if chf then begin
    result := 0;
    exit;
  end;
  // 頂点の計算
  repeat
    chf := false;
    fat := fnt;                           // 前の値保存
    a := a + p;                           // 次の計算点
    fn := f(a);                           // 次の点計算
    transformation(a, fn, q, nt, fnt);
    if form1.CheckBox1.Checked = true then begin
      if fnt < fat then chf := true;      // 上向き頂点
    end
    else begin
      if fnt > fat then chf := true;      // 下向き頂点   最下点
    end;
    if chf then begin
      a := a - p - p;                     // 2ピッチ戻し
      p := p / 4;
      fn := f(a);                         // 元の値再計算
      transformation(a, fn, q, nt, fnt);
    end;
    inc(loop);
  until (p < epsilon * (abs(a) + 1)) or (loop > 500);
  result := a;
end;

// 計算実行
procedure TForm1.BitBtn1Click(Sender: TObject);
var
  a, p, alpha, y, q, gr, gl : double;
  loop : integer;
  epsilon, tmp : double;
begin
  q := strTofloat(labelededit5.Text);
  a := strTofloat(labelededit1.Text);
  p := strTofloat(labelededit2.Text);
  gl := strTofloat(labelededit3.Text);
  gr := strTofloat(labelededit4.Text);
  memo1.Clear;
  // 収束判定値計算
  epsilon := 1;
  repeat
    epsilon := epsilon / 2;
    tmp := 1 + epsilon;
  until tmp = 1;
  epsilon := epsilon * 2;
  // 非線形方程式の計算
  loop := 0;
  alpha := bisection_method(a, p, -q, epsilon, loop);
  if loop = 0 then begin
    application.MessageBox('初期値の位置に間違いがある、又はピッチが大きすぎます。','注意',0);
    exit;
  end;
  // 検算
  y := f(alpha);
  // 計算結果表示
  series4.AddXY(alpha, y);
  memo1.Lines.Append('f(x) = (x - 3)^2 - 2');
  memo1.Lines.Append('ループ数 ' + intTostr(loop));
  memo1.Lines.Append('収束判定値');
  memo1.Lines.Append(floatTostr(epsilon));
  memo1.Lines.Append('x= ' +floatTostr(alpha));
  memo1.Lines.Append('検算 y = f(x)');
  memo1.Lines.Append('y= ' +floatTostr(y));
  line_segment(alpha, y, q, gl, gr);
end;

procedure TForm1.BitBtn2Click(Sender: TObject);
var
  gl, gr : double;
  ch : integer;
  a, p, y, q : double;
begin
  val(labelededit3.Text, gl, ch);
  if ch <> 0 then begin
    application.MessageBox('グラフ範囲左に間違いが有ります。','注意',0);
    exit;
  end;
  val(labelededit4.Text, gr, ch);
  if ch <> 0 then begin
    application.MessageBox('グラフ範囲右に間違いが有ります。','注意',0);
    exit;
  end;
  if gr <= gl then begin
    application.MessageBox('グラフ範囲右には左より大きくして下さい。','注意',0);
    exit;
  end;

  chart_graph(gl, gr);
  application.ProcessMessages;

  val(labelededit1.Text, a, ch);
  if ch <> 0 then begin
    application.MessageBox('初期値aに間違いが有ります。','注意',0);
    exit;
  end;
  val(labelededit2.Text, p, ch);
  if ch <> 0 then begin
    application.MessageBox('初期間隔pに間違いが有ります。','注意',0);
    exit;
  end;
  if (a < gl) or (a > gr) then begin
    application.MessageBox('初期値aがグラフの範囲外です。','注意',0);
    exit;
  end;
  if a > (gr - gl) / 2 + gl then begin
    application.MessageBox('初期値aは左寄りにして下さい。','注意',0);
    exit;
  end;
  val(labelededit5.Text, q, ch);
  if ch <> 0 then begin
    application.MessageBox('直線の傾きに間違いが有ります。','注意',0);
    exit;
  end;
  y := f(a);
  series3.AddXY(a, y);
  application.ProcessMessages;
  if p > (gr - gl) / 2 then begin
    application.MessageBox('初期間隔pが大きすぎます。','注意',0);
    exit;
  end;
  if p <= 0 then begin
    application.MessageBox('初期間隔pはゼロより大きくして下さい。','注意',0);
    exit;
  end;
  y := f(a + p);
  series4.AddXY(a + p, y);

  if not tiltcheck(a, gr, q, p) then begin
    application.MessageBox('計算範囲内(初期値~グラフ範囲右)に指定の傾きは有りません。','注意',0);
    exit;
  end;
  application.ProcessMessages;
  bitbtn1.Enabled := True;
end;

procedure TForm1.LabeledEdit1Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit2Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit3Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit4Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit5Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;


end.

 

bigdecimalによるプログラム

 演算桁数が多いため、計算に非常に時間がかかります。

// 非線形方程式と傾き指定の接線計算
// 非線形lineを-傾き分座標変換して頂点として計算します。
// 非線形方程式の頂点又は接線検出
// 頂点のx軸方向の値は、誤差が大きくなります、頂点は水平に成るためです。
// グラフを最初に作図して、範囲の確認をします。
unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VclTee.TeeGDIPlus, VCLTee.Series,
  Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls, VCLTee.TeEngine, VCLTee.TeeProcs,
  VCLTee.Chart, system.Math;

type
  TForm1 = class(TForm)
    Chart1: TChart;
    Series1: TLineSeries;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    BitBtn1: TBitBtn;
    Memo1: TMemo;
    Series3: TPointSeries;
    Series4: TPointSeries;
    LabeledEdit3: TLabeledEdit;
    LabeledEdit4: TLabeledEdit;
    BitBtn2: TBitBtn;
    CheckBox1: TCheckBox;
    LabeledEdit5: TLabeledEdit;
    Series2: TLineSeries;
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure LabeledEdit1Change(Sender: TObject);
    procedure LabeledEdit2Change(Sender: TObject);
    procedure LabeledEdit3Change(Sender: TObject);
    procedure LabeledEdit4Change(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure LabeledEdit5Change(Sender: TObject);
  private
    { Private 宣言 }
    procedure chart_graph(gl, gr: double);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses Velthuis.Bigdecimals, Velthuis.bigintegers, big_math;

{$R *.dfm}

{
// Bigdecimal -> Double
function BigdecimalToDouble(b: bigdecimal): double;
var
  bigstr : string;
  absb : bigdecimal;
begin
  absb := bigdecimal.Abs(b);
  if absb = bigdecimal.Zero then begin
    result := 0;
    exit;
  end;
  if absb >= maxdouble then begin
    result := maxdouble;
    if b < bigdecimal.Zero then result := -result;
    exit;
  end;
  if absb <= mindouble then begin
    result := mindouble;
    if b < bigdecimal.Zero then result := -result;
    exit;
  end;
  b := b.RoundToPrecision(30);
  bigstr := b.ToString;
  result := strtofloat(bigstr);
end;
}

// 座標変換
// x, y  座標
// q : 角度(rad)
// xt, yt 変換後座標
// 誤差を小さくする為50%程有効桁数を大きくします。
procedure transformation(x, y, q : bigdecimal; var xt, yt : bigdecimal);
var
  qt, ysx, l : bigdecimal;
  pre : integer;
begin
  pre := bigdecimal.DefaultPrecision;
  bigdecimal.DefaultPrecision := pre + pre div 2;  // 50%程有効桁数up
  if x <> 0 then begin
    ysx := y / x;
    qt := arctan_big(ysx);
  end
  else qt := pi_big / 2;
  qt := qt + q;
  l := x * x + y * y;
  l := l.Sqrt(l);
  xt := cos_big(qt) * l;
  yt := sin_big(qt) * l;
  bigdecimal.DefaultPrecision := pre;
end;

// 関数f(x)の計算式 非線形方程式
// f(x) = (x-3)^2 - 2
// f'(x) = x / 2 - 6
// 傾きαの接線のx座標 α= x/2-6,  x= (α+6)/2
function f(x: bigdecimal): bigdecimal;
begin
  result := (x - 3) * (x - 3) - 2;
//  result := x * x;
  if form1.CheckBox1.Checked = true then result := -result;
end;

// グラフ範囲内 傾き確認
// gl グラフ左端
// gr グラフ右端
// a 直線傾き
// チェックピッチ
function tiltcheck(gl, gr, a, p: double): boolean;
var
  sx, ex, sy, ey, sa, ea : double;
  n, i : integer;
begin
  result := false;
  p := p / 10;
  n := round((gr - gl) / p);
  sx := gl;
  sy := bigdecimaltodouble(f(gl));
  ex := gl + p;
  ey := bigdecimaltodouble(f(ex));
  sa := (ey - sy) / (ex - sx) - a;
  for i := 2 to n do begin
    sx := ex;
    sy := ey;
    ex := gl + i * p;
    ey := bigdecimaltodouble(f(ex));
    ea := (ey - sy) / (ex - sx) - a;
    if ea * sa <= 0 then begin
      result := true;
      break;
    end;
    sa := ea;
  end;
end;

// 接線
procedure line_segment(x, y, a, gl, gr: double);
var
  c : double;
begin
  c := y - a * x;
  y := a * gl + c;
  form1.Series2.AddXY(gl, y);
  y := a * gr + c;
  Form1.Series2.AddXY(gr, y);
  Form1.Memo1.Lines.Append('接線式');
  if c >= 0 then
    Form1.Memo1.Lines.Append(' y = ' + floatTostr(a) + 'x + ' + floatTostr(c))
  else
    Form1.Memo1.Lines.Append(' y = ' + floatTostr(a) + 'x ' + floatTostr(c))
end;

// 区間[gl,gr]の関数f(x)のグラフを作図
procedure TForm1.chart_graph(gl, gr: double);
const
  n = 100;
var
  x, xs, xe, dx: double;
  y : double;
  i : integer;
begin
  series1.clear;
  series2.clear;
  series3.clear;
  series4.clear;
  if gl = gr then exit;                     // 範囲がゼロだったら終了
  xs := gl;
  xe := gr;
  dx := (xe - xs) / n;
  for i := 0 to n do begin
    x := xs + i * dx;
    y := bigdecimalTodouble(f(x));
    series1.AddXY(x, y);
  end;
end;

// 傾斜頂点の近似値計算
function bisection_method(x, p, q, epsilon: bigdecimal; var loop: integer): bigdecimal;
var
  yn, ya, ynt, yat : bigdecimal;
  xnt, xat : bigdecimal;
  chf : boolean;
begin
  q := -arctan_big(q);                    // 傾きαを角度(rad)に
  yn := f(x);                             // 初期点計算
  transformation(x, yn, q, xnt, ynt);     // 座標変換
  x := x + p;                             // 次の点
  ya := f(x);                             // 次の点計算
  transformation(x, ya, q, xat, yat);     // 座標変換
  chf := false;
  if form1.CheckBox1.Checked = true then begin
    if ynt > yat then chf := true;        // 上向き頂点
  end
  else begin
    if ynt < yat then chf := true;        // 下向き頂点   最下点
  end;
  if chf then begin
    result := 0;
    exit;
  end;
  // 頂点の計算
  repeat
    chf := false;
    yat := ynt;
    x := x + p;
    yn := f(x);                             // 初期点計算
    transformation(x, yn, q, xnt, ynt);     // 座標変換
    if form1.CheckBox1.Checked = true then begin
      if ynt < yat then chf := true;        // 上向き頂点
    end
    else begin
      if ynt > yat then chf := true;        // 下向き頂点   最下点
    end;
    if chf then begin
      x := x - p - p;                       // 2ピッチ戻し
      p := p / 4;
      yn := f(x);                           // 元の値再計算
      transformation(x, yn, q, xnt, ynt);   // 座標変換
    end;
    transformation(x, yn, q, xnt, ynt);     // 座標変換
    inc(loop);
  until (p < epsilon * (bigdecimal.Abs(x) + 1)) or (loop > 5000);
  result := x.RoundToPrecision(bigdecimal.DefaultPrecision);
end;

// 計算実行
procedure TForm1.BitBtn1Click(Sender: TObject);
var
  x, y, q, gr, gl : double;
  loop, pre : integer;
  ab, pb, qb : bigdecimal;
  epsilon, xbig: bigdecimal;
  epstr : string;
begin
  q  := strTofloat(labelededit5.Text);
  gl := strTofloat(labelededit3.Text);
  gr := strTofloat(labelededit4.Text);
  qb := labelededit5.Text;
  ab := labelededit1.Text;
  pb := labelededit2.Text;
  memo1.Clear;
  // 収束判定値計算
  pre := bigdecimal.DefaultPrecision;
  epstr := '1E-' + inttostr(pre * 4 div 5);
  epsilon := epstr;
  // 非線形方程式の計算
  loop := 0;
  xbig := bisection_method(ab, pb, qb, epsilon, loop);
  if loop = 0 then begin
    application.MessageBox('初期値の位置に間違いがある、又はピッチが大きすぎます。','注意',0);
    exit;
  end;
  x := bigdecimaltodouble(xbig);
  // 検算
  y := bigdecimalTodouble(f(xbig));
  // 計算結果表示
  series4.AddXY(x, y);
  memo1.Lines.Append('f(x) = (x - 3)^2 - 2');
  memo1.Lines.Append('ループ数 ' + intTostr(loop));
  memo1.Lines.Append('収束判定値 ' + epsilon.ToString);
  memo1.Lines.Append('x= ' +floatTostr(x));
  memo1.Lines.Append('検算 y = f(x)');
  memo1.Lines.Append(' y= ' + floatTostr(y));
//  memo1.Lines.Append(xbig.ToString);
  line_segment(x, y, q, gl, gr);        // 接線描画
end;

// 入力値確認 初期グラフ作図
procedure TForm1.BitBtn2Click(Sender: TObject);
var
  gl, gr : double;
  ch : integer;
  a, p, y, q : double;
begin
  val(labelededit3.Text, gl, ch);
  if ch <> 0 then begin
    application.MessageBox('グラフ範囲左に間違いが有ります。','注意',0);
    exit;
  end;
  val(labelededit4.Text, gr, ch);
  if ch <> 0 then begin
    application.MessageBox('グラフ範囲右に間違いが有ります。','注意',0);
    exit;
  end;
  if gr <= gl then begin
    application.MessageBox('グラフ範囲右には左より大きくして下さい。','注意',0);
    exit;
  end;

  chart_graph(gl, gr);
  application.ProcessMessages;

  val(labelededit1.Text, a, ch);
  if ch <> 0 then begin
    application.MessageBox('初期値aに間違いが有ります。','注意',0);
    exit;
  end;
  val(labelededit2.Text, p, ch);
  if ch <> 0 then begin
    application.MessageBox('初期間隔pに間違いが有ります。','注意',0);
    exit;
  end;
  if (a < gl) or (a > gr) then begin
    application.MessageBox('初期値aがグラフの範囲外です。','注意',0);
    exit;
  end;
  if a > (gr - gl) / 2 + gl then begin
    application.MessageBox('初期値aは左寄りにして下さい。','注意',0);
    exit;
  end;
  val(labelededit5.Text, q, ch);
  if ch <> 0 then begin
    application.MessageBox('直線の傾きに間違いが有ります。','注意',0);
    exit;
  end;
  y := bigdecimaltodouble(f(a));
  series3.AddXY(a, y);
  application.ProcessMessages;
  if p > (gr - gl) / 2 then begin
    application.MessageBox('初期間隔pが大きすぎます。','注意',0);
    exit;
  end;
  if p <= 0 then begin
    application.MessageBox('初期間隔pはゼロより大きくして下さい。','注意',0);
    exit;
  end;
  y := bigdecimaltodouble(f(a + p));
  series4.AddXY(a + p, y);

  if not tiltcheck(a, gr, q, p) then begin
    application.MessageBox('計算範囲内(初期値~グラフ範囲右)に指定の傾きは有りません。','注意',0);
    exit;
  end;
  application.ProcessMessages;
  bitbtn1.Enabled := True;
end;

procedure TForm1.LabeledEdit1Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit2Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit3Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit4Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.LabeledEdit5Change(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  bitbtn1.Enabled := false;
end;

end.

download vertex_calculator_big.zip

  三角関数、逆三角関数、その他関数、 連立一次方程式の解法 に戻る