2023/06/12
 n次多項式補間のプログラムにバグが有ったので、修正しました。

ニュートン補間、ラグランジュ補間

 ニュートン補間についてはウィキペディアのニュートン補間   ラグランジュ補間についての詳細はウィキペディアのラグランジュ補間 をそれぞれ参照して下さい。

 補間となっていますが、計算はデーターの数をNとすると n=N-1 のn次方程式の計算による場合と同じになります。
n次方程式の場合、近似計算のn次回帰計算をする必要がありますが、ニュートン、ラグランジュ補間は、単純な計算で行うことが出来ます。

 計算上の注意点は、関数の逆数を含まないデーターでないと発散(.ルンゲ現象)する場合がある事、同じ X の値が有ってはならない事です。
Xの値は等間隔である必用はありません。

 左図はルンゲ現象が発生している例です。
 テストデーターの計算にf(x)=1/(1+2x^2)を使用した場合で、逆数になっているので、ルンゲ現象が発生します。
f(x)=cos(x)や対数をテストデーターに使用しても発生しません。
当然1~N-1次の計算をテストデーターに使用すれば発生はしません。
f(x)=1/(2 + cos(x))のような関数だとcos(x)でもルンゲ現象は発生します。
プログラムに、 f(x)=1/(1+2x^2)の時逆数計算の指定があるのは、1/f(x)=1+2x^2で計算すればルンゲ現象が発生しない事を確認するためのものです。



 左図はf(x)=1/(1+2x^2)のxの範囲を0.5~2にした場合ですが、ルンゲ現象は小さな値に抑えられ伸す。
xの範囲を0~2にすると、ルンゲ現象は発生しなくなります。


凸カーブデーターの左右に、凹む様に変化する部分があり、そのカーブが、関数の逆数になっている場合です。
片方だけだと、ルンゲ現象は発生しません。

 実際のデーターは何らかの測定データー、サンプリングデーターであり、計算値の様に揃う事は無いので、関数の逆数になってしまう部分が発生すると思われ、ニュートン補間、ラグランジュ補間を使用する場合は、必ず補間グラフを作成して、問題が無いか確認する必要があります。
又、N数を増やしすぎると、:計算の最大次数がN-1となり、桁落ちの発生が起きるので注意が必要です。

 N数を余り増やすと、桁落ちが発生して、左図左側の様に、発振したようになりのす。
桁落ちしているかどうかは、グラフのN点を補間値が通らなくなるのと、ギザギザになることで見分けます。
左図右側は、多倍長演算で、桁落ちしていない場合です。


プログラム

 ニュートン補間 

// データー数(N) - 1 の n次回帰計算と同じ結果になります。
unit Main;

interface

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

type
  TForm1 = class(TForm)
    Chart1: TChart;
    Series1: TLineSeries;
    Series3: TPointSeries;
    StringGrid1: TStringGrid;
    NEdit: TLabeledEdit;
    NsetBtn: TButton;
    interpolationBtn: TButton;
    Series2: TPointSeries;
    XEdit: TLabeledEdit;
    XtoYBtn: TButton;
    Series4: TLineSeries;
    GroupBox1: TGroupBox;
    testNEdit: TLabeledEdit;
    CheckBox2: TCheckBox;
    MinXEdit: TLabeledEdit;
    MaxXEdit: TLabeledEdit;
    RadioGroup1: TRadioGroup;
    testdataBtn: TButton;
    procedure testdataBtnClick(Sender: TObject);
    procedure NsetBtnClick(Sender: TObject);
    procedure interpolationBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure XtoYBtnClick(Sender: TObject);
  private
    { Private 宣言 }
    procedure GridSet(Sin, Colin: integer);
    procedure FGridSet(Sin, Colin: integer);    // グリッドサイズ変更
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
uses system.Math;

type
  DArray = array of double;

var
  x, ncd : Darray;
  P : integer;
  dx, xmax, xmin : double;
  FF : boolean;

// グリッドサイズの変更
procedure TForm1.GridSet(Sin, Colin: integer);    // グリッドサイズ変更
var                                               // データー行の数は10迄
  H, W:integer;                                   // それ以上はスクロールする
begin
  if (StringGrid1.ColCount = Colin) and (StringGrid1.RowCount = Sin) then exit;
  StringGrid1.ColWidths[0] := 30;
  StringGrid1.ColCount := Colin;
  StringGrid1.RowCount := Sin;
  if Sin <= 11 then                               // ストリンググリッドの大きさ設定
    begin                                         // 固定行を含め11行又はそれ以下の場合
      H := (StringGrid1.DefaultRowHeight + 1) * Sin;
      W := (StringGrid1.DefaultColWidth + 1) * (Colin - 1) + 31;
      StringGrid1.ScrollBars := ssNone;           // スクロールバーなし
    end
    else
    begin                                         // 固定行を含め11行より多い場合
      H := (StringGrid1.DefaultRowHeight + 1) * (10 + 1);
      W := (StringGrid1.DefaultColWidth + 1) * (Colin - 1) + 31;
      StringGrid1.ScrollBars := ssVertical;       // スクロールバー表示
    end;
  StringGrid1.ClientWidth := W;
  StringGrid1.ClientHeight := H;
end;

// グリッド固定行の設定
procedure TForm1.FGridSet(Sin, Colin: integer);
var
  E : integer;
begin
  GridSet(Sin, Colin);
  for E := 1 to Sin - 1 do
      StringGrid1.Rows[E].Text := intTostr(E);
  StringGrid1.Cols[1].Text := 'x';
  StringGrid1.Cols[2].Text := 'y';
end;

// N数入力 入力ミス N < 0
function Ninput(testF: boolean): integer;
var
  N, ch : integer;
begin
  result := -1;
  if not testF then
    val(Form1.Nedit.Text, N, ch)
  else
    val(Form1.testNedit.Text, N, ch);
  if ch <> 0 then begin
    application.MessageBox('N数の入力に間違いが有ります。','注意',0);
    exit;
  end;
  if N > 100 then begin
    application.MessageBox('N数は100迄にして下さい。','注意',0);
    exit;
  end;
  if N < 3 then begin
    application.MessageBox('N数は3以上にして下さい。','注意',0);
    exit;
  end;
  result := N;
end;

// グリッドN数セット
procedure TForm1.NsetBtnClick(Sender: TObject);
var
  N : integer;
begin
  N := Ninput(false);
  if N < 0 then exit;
  GridSet(N + 1, 3);
end;

// 参考値f(x) ルンゲ現象確認用データー
function f(x: double): double;
begin
  result := 1 / (1 + 2 * x * x);
end;

// 参考値cos(x)
function f1(x: double): double;
begin
  result := cos(x);
end;

// 参考値 セット
procedure TForm1.testdataBtnClick(Sender: TObject);
var
  N : integer;
  i, ch : integer;
  x, y : Darray;
  minx, maxx :double;
  p, xd, yd : double;
begin
  N := 8;               // データー数
  FF := False;
  Series1.Clear;
  Series3.Clear;
  if radiogroup1.ItemIndex = 0 then begin
    N := 8;               // データー数
    Nedit.Text := inttostr(N);
    GridSet(N + 1, 3);
    setlength(x, N);
    setlength(y, N);
    x[0] := -8; y[0] :=  2;
    x[1] := -5; y[1] :=  3;
    x[2] := -3; y[2] :=  1;
    x[3] :=  0; y[3] :=  2;
    x[4] :=  2; y[4] :=  1;
    x[5] :=  5; y[5] :=  3;
    x[6] :=  8; y[6] := -4;
    x[7] :=  9; y[7] :=  1;
  end;
  if radiogroup1.ItemIndex = 1 then begin
    N := 5;
    Nedit.Text := inttostr(N);
    GridSet(N + 1, 3);
    setlength(x, N);
    setlength(y, N);
    x[0] :=  0; y[0] :=  0.8;
    x[1] :=  2; y[1] :=  3.4;
    x[2] :=  3; y[2] :=  4.9;
    x[3] :=  4; y[3] :=  2.9;
    x[4] :=  5; y[4] :=  2;
  end;
  if (radiogroup1.ItemIndex = 0) or (radiogroup1.ItemIndex = 1) then begin
    for i := 0 to N - 1 do begin
      StringGrid1.cells[1,i + 1] := floatTostr(x[i]);
      StringGrid1.cells[2,i + 1] := floatTostr(y[i])
    end;
  end;
  if (radiogroup1.ItemIndex = 2) or (radiogroup1.ItemIndex = 3) then begin
    N := Ninput(true);
    if N < 0 then exit;
    val(MinXedit.Text, minx, ch);
    if ch <> 0 then begin
      application.MessageBox('min値に間違いが有ります。','注意',0);
      exit;
    end;
    val(MaxXedit.Text, maxx, ch);
    if ch <> 0 then begin
      application.MessageBox('max値に間違いが有ります。','注意',0);
      exit;
    end;
    if minx >= maxx then begin
      application.MessageBox('max値がmin値より小さな値になっています。','注意',0);
      exit;
    end;

    Nedit.Text := inttostr(N);
    GridSet(N + 1, 3);
    p := (maxx - minx) / (N - 1);
    for i := 0 to N - 1 do begin
      xd := i * p + minx;
      yd := 0;
      case radiogroup1.ItemIndex of
        2 : yd := f1(xd);
        3 : yd := f(xd);
      end;
      StringGrid1.cells[1,i + 1] := floatTostr(xd);
      StringGrid1.cells[2,i + 1] := floatTostr(yd);
    end;
    if (Form1.CheckBox2.Checked = true) and (radiogroup1.ItemIndex = 3) then FF := True;
  end;
  interpolationBtnClick(nil);
end;

// 係数テーブル設定
function maketable(var x, ncd: DArray; P: integer): Boolean;    // 係数を求めncdに上書き
var
  i, j : integer;
begin
  result := False;
  for j := 0 to P  do
    for i := 0 to P  do begin
      if j <> i then
        if x[i] = x[j] then begin
          application.MessageBox(pchar('x' + inttostr(j+1) + ' x' + inttostr(i+1)
                                         + 'が同じ値です。'),'注意',0);
          exit;
        end;
    end;
  for i := 1 to P do
    for j := P downto i do
      ncd[j] := (ncd[j] - ncd[j - 1]) / (x[j] - x[j - i]);
  result := True;
end;

// 補間計算
function interpolate(x, ncd: DArray; P: integer; xx: double): double;  // 補間
var
  i : integer;
  r, s : double;
begin
  r := ncd[0];
  s := 1;
  for i := 1 to P do begin
    s := s * (xx - x[i - 1]);
    r := r + s * ncd[i];
  end;
// こちらの計算でもOk
{
  r := ncd[P];
  for i := P - 1 downto 0 do
    r := r * (xx - x[i]) + ncd[i];
}
  if not FF then
    result := r
  else
    if r = 0 then
      result := 1
    else
      result := 1 / r;
end;

// グリッドの値による補間計算
procedure TForm1.interpolationBtnClick(Sender: TObject);
var
  N, ch, i, imax : integer;
  y : Darray;
  cn : string;
  s, xx : double;
begin
  XtoYBtn.Enabled := False;
  N := Ninput(false);
  if N < 0 then exit;
  GridSet(N + 1, 3);
  setlength(x, N);
  setlength(y, N);
  setlength(ncd, N);
  for i := 0 to N - 1 do begin
    val(StringGrid1.cells[1,i + 1], x[i], ch);
    if ch <> 0 then begin
      cn := 'x' + intTostr(i + 1) + 'に間違いが有ります。';
      application.MessageBox(pchar(cn),'注意',0);
      break;
    end;
    val(StringGrid1.cells[2,i + 1], y[i], ch);
    if ch <> 0 then begin
      cn := 'y' + intTostr(i + 1) + 'に間違いが有ります。';
      application.MessageBox(pchar(cn),'注意',0);
      break;
    end;
    if not FF then
      ncd[i] := y[i]
    else
      ncd[i] := 1 / y[i];
  end;
  if ch <> 0 then exit;

  Series1.Clear;
  Series2.Clear;
  Series3.Clear;
  Series4.Clear;
  P := N - 1;                                   
  if not maketable(x, ncd, P) then exit;        // テーブルの作成
  xmax := x[0];
  xmin := x[0];
  for i := 0 to P do begin
    if x[i] > xmax then xmax := x[i];
    if x[i] < xmin then xmin := x[i];
    Series3.AddXY(x[i], y[i]);
  end;
  imax := P * 20;
  dx := (xmax - xmin) / imax;
  for i := 0 to imax do begin
    xx := i * dx + x[0];
    if radiogroup1.ItemIndex = 2 then begin
      s := f1(xx);                               // 元値計算
      Series4.AddXY(xx, s);
    end;
    if radiogroup1.ItemIndex = 3 then begin
      s := f(xx);                               // 元値計算
      Series4.AddXY(xx, s);
    end;
    s := interpolate(x, ncd, P, xx);            // 補間計算
    Series1.AddXY(xx, s)
  end;
  XtoYBtn.Enabled := True;
end;

// 指定値計算 指定値xによるy補間値計算とグラフ表示
procedure TForm1.XtoYBtnClick(Sender: TObject);
var
  i, j, ch : integer;
  xd, xx, s, gmin, gmax : double;
begin
  val(Xedit.Text, xd, ch);
  if ch <> 0 then begin
    application.MessageBox('xからy推定の入力に間違いが有ります。','注意',0);
    exit;
  end;
  // gmin-gmax範囲グラフ再描画
  Series1.Clear;
  gmin := xmin;
  gmax := xmax;
  if xd < xmin then gmin := xd;
  if xd > xmax then gmax := xd;
  i := round((gmax - gmin) / dx);
  for j := 0 to i do begin
    xx := j * dx + gmin;
    s := interpolate(x, ncd, P, xx);                // 補間計算
    Series1.AddXY(xx, s);
  end;
  // 指定値xによるy補間値計算とグラフ表示
  Series2.Clear;
  s := interpolate(x, ncd, P, xd);                  // 補間計算
  Series2.AddXY(xd, s);
end;

// グリッド初期設定
procedure TForm1.FormCreate(Sender: TObject);
begin
  FGridSet(101, 3);                 // 固定行1列1 + 100行2列まで行列を変更しても
                                    // 入力値がクリアされないようにします。
  GridSet(4, 3);                    // 最初の設定
end;

end.

ラグランジェ補間

  係数テーブルを作成せず補間値をX,Yの配列値からその都度計算します。
計算結果はニュートン補間と同じで、N-1次計算による補間値となります。
計算式は、非常に簡単なものとなります。

unit Main;

interface

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

type
  TForm1 = class(TForm)
    Chart1: TChart;
    Series1: TLineSeries;
    Series3: TPointSeries;
    StringGrid1: TStringGrid;
    NEdit: TLabeledEdit;
    NsetBtn: TButton;
    interpolationBtn: TButton;
    testdataBtn: TButton;
    Series2: TPointSeries;
    XEdit: TLabeledEdit;
    XtoYBtn: TButton;
    Series4: TLineSeries;
    GroupBox1: TGroupBox;
    CheckBox2: TCheckBox;
    TestNEdit: TLabeledEdit;
    MinXEdit: TLabeledEdit;
    MaxXEdit: TLabeledEdit;
    RadioGroup1: TRadioGroup;
    procedure testdataBtnClick(Sender: TObject);
    procedure NsetBtnClick(Sender: TObject);
    procedure interpolationBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure XtoYBtnClick(Sender: TObject);
  private
    { Private 宣言 }
    procedure GridSet(Sin, Colin: integer);
    procedure FGridSet(Sin, Colin: integer);    // グリッドサイズ変更
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses system.Math;

type
  DArray = array of double;


var
  x, y, yd : Darray;
  N : integer;
  dx, xmax, xmin : double;
  FF : boolean;

// グリッドサイズの変更
procedure TForm1.GridSet(Sin, Colin: integer);    // グリッドサイズ変更
var                                               // データー行の数は10迄
  H, W :integer;                                  // それ以上はスクロールする
begin
  if (StringGrid1.ColCount = Colin) and (StringGrid1.RowCount = Sin) then exit;
  StringGrid1.ColWidths[0] := 30;
  StringGrid1.ColCount := Colin;
  StringGrid1.RowCount := Sin;
  if Sin <= 11 then                               // ストリンググリッドの大きさ設定
    begin                                         // 固定行を含め11行又はそれ以下の場合
      H := (StringGrid1.DefaultRowHeight + 1) * Sin;
      W := (StringGrid1.DefaultColWidth + 1) * (Colin - 1) + 31;
      StringGrid1.ScrollBars := ssNone;           // スクロールバーなし
    end
    else
    begin                                         // 固定行を含め11行より多い場合
      H := (StringGrid1.DefaultRowHeight + 1) * (10 + 1);
      W := (StringGrid1.DefaultColWidth + 1) * (Colin - 1) + 31;
      StringGrid1.ScrollBars := ssVertical;       // スクロールバー表示
    end;
  StringGrid1.ClientWidth := W;
  StringGrid1.ClientHeight := H;
end;

// グリッド固定行の設定
procedure TForm1.FGridSet(Sin, Colin: integer);
var
  E : integer;
begin
  GridSet(Sin, Colin);
  for E := 1 to Sin - 1 do
      StringGrid1.Rows[E].Text := intTostr(E);
  StringGrid1.Cols[1].Text := 'x';
  StringGrid1.Cols[2].Text := 'y';
end;

// N数入力 エラー時戻り値<0
function Ninput(testF: boolean): integer;
var
  N, ch: integer;
begin
  result := -1;
  if not testF then
    val(Form1.Nedit.Text, N, ch)
  else
    val(Form1.testNedit.Text, N, ch);
  if ch <> 0 then begin
    application.MessageBox('N数の入力に間違いが有ります。','注意',0);
    exit;
  end;
  if N > 100 then begin
    application.MessageBox('N数は100迄にして下さい。','注意',0);
    exit;
  end;
  if N < 3 then begin
    application.MessageBox('N数は3以上にして下さい。','注意',0);
    exit;
  end;
  result := N;
end;

// グリッドN数セット
procedure TForm1.NsetBtnClick(Sender: TObject);
var
  N : integer;
begin
  N := Ninput(false);
  if N < 0 then exit;
  GridSet(N + 1, 3);
end;

// xの値チェック 同じxの値があるか確認 同じ値無 true 有り false
function datacheck(x :DArray): boolean;
var
  i, j : integer;
begin
  result := True;
  for i := Low(x) to High(x) do
    for j := Low(x) to High(x) do begin
      if i <> j then
        if x[i] - x[j] = 0 then begin
          application.MessageBox(pchar('X' + inttostr(i+1) + ' X' + inttostr(j+1)
                                         + 'が同じ値です。'),'注意',0);
          result := False;
          exit;
        end;
    end;
end;

// 補間計算
function interpolate(x, y: DArray; xx: double): double;
var
  i, j : integer;
  s, p : double;
begin
  s := 0;
  for i := Low(x) to High(x) do begin
    p := y[i];
    for j := Low(x) to High(x) do begin
      if i <> j then
        p := p * (xx - x[j]) / (x[i] - x[j]);
    end;
    s := s + p;
  end;
  result := s;
end;

// 参考値f(x) ルンゲ現象確認用データー
function f(x: double): double;
begin
//  x := abs(x);
  result := 1 / (1 + 2 * x * x);
end;

// 参考値 セット
procedure TForm1.testdataBtnClick(Sender: TObject);
var
  N, ch : integer;
  i : integer;
  p, minx, maxx :double;
  x, y : Darray;
  xd, yd : double;
begin
  Series1.Clear;
  Series3.Clear;
  FF := False;;
  N := 5;
  if radiogroup1.ItemIndex = 0 then begin
    N := 8;               // データー数
    Nedit.Text := inttostr(N);
    GridSet(N + 1, 3);
    setlength(x, N);
    setlength(y, N);
    x[0] := -8; y[0] :=  2;
    x[1] := -5; y[1] :=  3;
    x[2] := -3; y[2] :=  1;
    x[3] :=  0; y[3] :=  2;
    x[4] :=  2; y[4] :=  1;
    x[5] :=  5; y[5] :=  3;
    x[6] :=  8; y[6] := -4;
    x[7] :=  9; y[7] :=  1;
  end;
  if radiogroup1.ItemIndex = 1 then begin
    N := 5;               // データー数
    Nedit.Text := inttostr(N);
    GridSet(N + 1, 3);
    setlength(x, N);
    setlength(y, N);
    x[0] :=  0; y[0] :=  0.8;
    x[1] :=  2; y[1] :=  3.4;
    x[2] :=  3; y[2] :=  4.9;
    x[3] :=  4; y[3] :=  2.9;
    x[4] :=  5; y[4] :=  2;
  end;
  if (radiogroup1.ItemIndex = 0) or (radiogroup1.ItemIndex = 1) then
    for i := 0 to N - 1 do begin
      StringGrid1.cells[1,i + 1] := floatTostr(x[i]);
      StringGrid1.cells[2,i + 1] := floatTostr(y[i]);
    end;

  if radiogroup1.ItemIndex = 2 then begin
    N := Ninput(true);
    if N < 0 then exit;
    val(minxedit.Text, minx, ch);
    if ch <> 0 then begin
      application.MessageBox('Min値に間違いが有ります。','注意',0);
      exit;
    end;
    val(maxxedit.Text, maxx, ch);
    if ch <> 0 then begin
      application.MessageBox('Max値に間違いが有ります。','注意',0);
      exit;
    end;
    if minx >= maxx then begin
      application.MessageBox('Max値がMin値より小さいです。','注意',0);
      exit;
    end;
    Nedit.Text := inttostr(N);
    GridSet(N + 1, 3);
    p := (maxx - minx) / (N - 1);
    for i := 0 to N - 1 do begin
      xd := i * p + minx;
      yd := f(xd);
      StringGrid1.cells[1,i + 1] := floatTostr(xd);
      StringGrid1.cells[2,i + 1] := floatTostr(yd);
    end;
    if checkbox2.Checked = True then FF := true;
  end;
  interpolationBtnClick(Nil);
end;

// グリッドの値による補間計算
procedure TForm1.interpolationBtnClick(Sender: TObject);
var
  ch, i, imax : integer;
  cn : string;
  s, xx : double;
begin
  XtoYBtn.Enabled := False;
  N := Ninput(false);
  if N < 0 then exit;
  GridSet(N + 1, 3);
  setlength(x, N);
  setlength(y, N);
  setlength(yd, N);
  for i := 0 to N - 1 do begin
    val(StringGrid1.cells[1,i + 1], x[i], ch);
    if ch <> 0 then begin
      cn := 'x' + intTostr(i + 1) + 'に間違いが有ります。';
      application.MessageBox(pchar(cn),'注意',0);
      break;
    end;
    val(StringGrid1.cells[2,i + 1], y[i], ch);
    if ch <> 0 then begin
      cn := 'y' + intTostr(i + 1) + 'に間違いが有ります。';
      application.MessageBox(pchar(cn),'注意',0);
      break;
    end;
  end;
  if ch <> 0 then exit;
  if not datacheck(x) then exit;            // xの値に重複が無いか確認
  Series1.Clear;
  Series2.Clear;
  Series3.Clear;
  Series4.Clear;
  xmin := x[0];
  xmax := x[0];
  for i := 0 to N - 1 do begin
    if x[i] > xmax then xmax := x[i];
    if x[i] < xmin then xmin := x[i];
    Series3.AddXY(x[i], y[i]);
    if FF then yd[i] := 1 / y[i]
          else yd[i] := y[i];
  end;
  imax := (N - 1) * 20;                     // 分割数
  dx := (xmax - xmin) / imax;
  for i := 0 to imax do begin
    xx := i * dx + x[0];
    if radiogroup1.ItemIndex = 2 then begin
      s := f(xx);
      Series4.AddXY(xx, s);
    end;
    s := interpolate(x, yd, xx);             // 補間計算
    if FF then s := 1  / s;
    Series1.AddXY(xx, s);
  end;
  XtoYBtn.Enabled := True;
end;

// 指定値グラフ表示 指定値xによるy補間値計算とグラフ表示
procedure TForm1.XtoYBtnClick(Sender: TObject);
var
  i, j, ch : integer;
  xd, s, xx, gmin, gmax : double;
begin
  val(Xedit.Text, xd, ch);
  if ch <> 0 then begin
    application.MessageBox('xからY推定の入力に間違いが有ります。','注意',0);
    exit;
  end;
  // 最小最大区間グラフ再描画
  Series1.Clear;
  gmin := xmin;
  gmax := xmax;
  if xd < xmin then gmin := xd;
  if xd > xmax then gmax := xd;
  i := round((gmax - gmin) / dx);
  for j := 0 to i do begin
    xx := j * dx + gmin;
    s := interpolate(x, yd, xx);            // 補間計算
    if FF then s := 1  / s;
    Series1.AddXY(xx, s);
  end;
  // 指定値xによるy補間値計算とグラフ表示
  Series2.Clear;
  s := interpolate(x, yd, xd);              // 補間計算
  if FF then s := 1  / s;
  Series2.AddXY(xd, s);
end;

// グリッド初期設定
procedure TForm1.FormCreate(Sender: TObject);
begin
  FGridSet(101, 3);                 // 固定行1列1 + 100行2列まで行列を変更しても
                                    // 入力値がクリアされないようにします。
  GridSet(4, 3);                    // 最初の設定
end;

end.


n次回帰近似による補間
 ニュートン補間の計算確認の為、n次回帰計算のnの値をN-1として、n次回帰近似(polynomial approximation)プログラムを作成してみました。
次数がN-1の時は、ニュートン補間と同じ結果になります、次数をN-1より小さい値に設定ると、n次回帰近似計算となります。
次数がN-1の時は、回帰曲線が必ずデーター点を通ります、その時のデーターに同じx値が有ってはなりません。
データー点を必ず通る必要がない場合は、回帰計算の次数を0~N-1の値に出来ます。
同じx値の値が複数あっても、問題なく計算が出来ます。

 計算結果は、ニュートン補間、ラグランジュ補間と変わりません。
N数増加による桁落ちはニュートン補間、ラグランジュ補間より少なくなります。
計算次数は、testdataの時はN-1になりますが、その後補間(interpolation)計算では0~N-1の間で自由に指定が出来ます。


unit Main;

interface

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

type
  TAoAoD = array of array of extended;    // 引数で渡す多次元配列はここでtype宣言
  Darray = array of extended;

  TForm1 = class(TForm)
    Chart1: TChart;
    StringGrid1: TStringGrid;
    NEdit: TLabeledEdit;
    NsetBtn: TButton;
    interpolationBtn: TButton;
    testdataBtn: TButton;
    XEdit: TLabeledEdit;
    Series1: TPointSeries;
    Series2: TLineSeries;
    Series3: TLineSeries;
    Series5: TPointSeries;
    Xin_interpolation: TButton;
    Memo1: TMemo;
    RadioGroup1: TRadioGroup;
    GroupBox1: TGroupBox;
    CheckBox2: TCheckBox;
    MinXEdit: TLabeledEdit;
    MaxXEdit: TLabeledEdit;
    TestNEdit: TLabeledEdit;
    ZEdit: TLabeledEdit;
    procedure testdataBtnClick(Sender: TObject);
    procedure NsetBtnClick(Sender: TObject);
    procedure interpolationBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Xin_interpolationClick(Sender: TObject);
    procedure CheckBox2Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure GridSet(Sin, Colin: integer);
    procedure FGridSet(Sin, Colin: integer);    // グリッドサイズ変更
    procedure gauss(ad: TAoAoD; xxd: DArray; N : integer);
    procedure sai(xi, yi, xxd: DArray; N, S : integer);  // n次計算ルーチン
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
uses system.Math;

var
  x, y, xx : Darray;
  DEF, DEFD: Boolean;
  xmax, xmin, dx : extended;

// グリッドサイズの変更
procedure TForm1.GridSet(Sin, Colin: integer);    // グリッドサイズ変更
var                                               // データー行の数は10迄
  H, W :integer;                                  // それ以上はスクロールする
begin
  if (StringGrid1.ColCount = Colin) and (StringGrid1.RowCount = Sin) then exit;
  StringGrid1.ColWidths[0] := 30;
  StringGrid1.ColCount := Colin;
  StringGrid1.RowCount := Sin;
  if Sin <= 11 then                               // ストリンググリッドの大きさ設定
    begin                                         // 固定行を含め11行又はそれ以下の場合
      H := (StringGrid1.DefaultRowHeight + 1) * Sin;
      W := (StringGrid1.DefaultColWidth + 1) * (Colin - 1) + 31;
      StringGrid1.ScrollBars := ssNone;           // スクロールバーなし
    end
    else
    begin                                         // 固定行を含め11行より多い場合
      H := (StringGrid1.DefaultRowHeight + 1) * (10 + 1);
      W := (StringGrid1.DefaultColWidth + 1) * (Colin - 1) + 31;
      StringGrid1.ScrollBars := ssVertical;       // スクロールバー表示
    end;
  StringGrid1.ClientWidth := W;
  StringGrid1.ClientHeight := H;
end;

// グリッド固定行の設定
procedure TForm1.FGridSet(Sin, Colin: integer);
var
  E : integer;
begin
  GridSet(Sin, Colin);
  for E := 1 to Sin - 1 do
      StringGrid1.Rows[E].Text := intTostr(E);
  StringGrid1.Cols[1].Text := 'x';
  StringGrid1.Cols[2].Text := 'y';
end;

// N数入力 入力エラー result < 0
function Ninput(testF : boolean; var Z: integer): integer;
var
  N, ch : integer;
begin
  result := -1;
  if not testF then
    val(Form1.Nedit.Text, N, ch)
  else
    val(Form1.testNedit.Text, N, ch);
  if ch <> 0 then begin
    application.MessageBox('N数の入力に間違いが有ります。','注意',0);
    exit;
  end;
  if (N < 3) or (N > 100) then begin
    application.MessageBox('N数は3~100にして下さい。','注意',0);
    exit;
  end;
  val(Form1.Zedit.Text, Z, ch);
  if ch <> 0 then begin
    application.MessageBox('次数Zの入力に間違いが有ります。','注意',0);
    exit;
  end;
  if Z > N - 1 then begin
    application.MessageBox('次数ZがN数より大きいので小さくします。','注意',0);
    Z := N - 1;
    Form1.Zedit.Text := intTostr(Z);
  end;
  if Z < 0 then begin
    application.MessageBox('次数はゼロ以上にしてください。','注意',0);
    exit;
  end;

  result := N;
end;

// グリッドN数セット
procedure TForm1.NsetBtnClick(Sender: TObject);
var
  N, Z : integer;
begin
  N := Ninput(false, Z);
  if N < 0 then exit;
  GridSet(N + 1, 3);
end;

// xの値チェック 同じxの値があるか確認 同じ値無 true 有り false
function datacheck(x :DArray; N: integer): boolean;
var
  i, j : integer;
begin
  result := true;
  for i := 0 to N - 1 do
    for j := 0 to N - 1 do begin
      if i <> j then
        if x[i] - x[j] = 0 then begin
          application.MessageBox(pchar('x' + inttostr(i+1) + '-x' + inttostr(j+1)
                                         + 'がゼロになります。'),'注意',0);
          result := false;
          exit;
        end;
    end;
end;

// 参考値f(x) ルンゲ現象確認用データー
function f3(x: extended): extended;
begin
  result := cos(x);
end;

// 参考値f(x) ルンゲ現象確認用データー
function f2(x: extended): extended;
begin
//  x := abs(x);
  result := 1 / (1 + 2 * x * x);
end;

// 参考値の関数
function f(x: extended): extended;
begin
  result := x - power(x,3) / 5 + power(x,5) / 100;
end;

// 参考値 セット
procedure TForm1.testdataBtnClick(Sender: TObject);
var
  N, Z : integer;
  i, ch : integer;
  d, pc : extended;
  minx, maxx : extended;
  xd, yd : extended;
begin
  DEFD := False;
  N := 5;
  if radiogroup1.ItemIndex = 0 then begin
      N := 8;               // データー数
      Nedit.Text := inttostr(N);
      Zedit.Text := inttostr(N - 1);
      GridSet(N + 1, 3);
      setlength(x, N);
      setlength(y, N);
      x[0] := -8; y[0] :=  2;
      x[1] := -5; y[1] :=  3;
      x[2] := -3; y[2] :=  1;
      x[3] :=  0; y[3] :=  2;
      x[4] :=  2; y[4] :=  1;
      x[5] :=  5; y[5] :=  3;
      x[6] :=  8; y[6] := -4;
      x[7] :=  9; y[7] :=  1;
      DEF := False;                        // 差分グラフ表示フラグ
  end;
  if radiogroup1.ItemIndex = 1 then begin
      N := 6;
      Nedit.Text := inttostr(N);
      Zedit.Text := inttostr(N - 1);
      GridSet(N + 1, 3);
      setlength(x, N);
      setlength(y, N);
      minx := - 4.5;
      maxx :=   4.5;
      pc := (maxx - minx) / (N - 1);
      for i := 0 to N - 1 do begin
        d   := i * Pc + minx;
        x[i] := d;
        y[i] := f(d);
      end;
      DEF := True;                        // 差分グラフ表示フラグ
  end;
  if (radiogroup1.ItemIndex = 0) or (radiogroup1.ItemIndex = 1) then begin
    for i := 0 to N - 1 do begin
      StringGrid1.cells[1,i + 1] := floatTostr(x[i]);
      StringGrid1.cells[2,i + 1] := floatTostr(y[i]);
    end;
  end;
  if (radiogroup1.ItemIndex = 2) or (radiogroup1.ItemIndex = 3) then begin
    N := Ninput(true, Z);
    Zedit.Text := inttostr(N - 1);
    if N < 0 then begin
      exit;
    end;
    val(MinXedit.Text, minx, ch);
    if ch <> 0 then begin
      application.MessageBox('Min値の入力に間違いが有ります。','注意',0);
      exit;
    end;
    val(MaxXedit.Text, maxx, ch);
    if ch <> 0 then begin
      application.MessageBox('Max値の入力に間違いが有ります。','注意',0);
      exit;
    end;
    if minx >= maxx then begin
      application.MessageBox('Max値がMax値より大きくなっています。','注意',0);
      exit;
    end;
    Nedit.Text := inttostr(N);
    GridSet(N + 1, 3);
    pc := (maxx - minx) / (N - 1);
    yd := 0;
    for i := 0 to N - 1 do begin
      xd := i * pc + minx;
      case radiogroup1.ItemIndex of
        2: yd := f3(xd);
        3: begin
            yd := f2(xd);
            if checkbox2.Checked = True then  DEFD := True;
           end;
      end;
      StringGrid1.cells[1,i + 1] := floatTostr(xd);
      StringGrid1.cells[2,i + 1] := floatTostr(yd);
    end;
    DEF := True;                        // 差分グラフ表示フラグ
  end;
  interpolationBtnClick(Nil);
end;

// ガウスの消去法ルーチン
procedure TForm1.gauss(ad: TAoAoD; xxd: DArray; N : integer);
var
  i, j, k, pivot:  integer;
  b : extended;
  p, q, max: extended;
  LL : string;
begin
//  N := High(xxd) + 1;
//  配列a[i,k]をkで スキャン
  for K := 0 to N -1 do               // 配列a[i,k]をk スキャン
    begin
      max := 0;                       // Max値初期化
      pivot := K;                     // ピボット仮設定
      for i := K to N -1 do           // 配列a[i,k]をi 縦スキャン
        begin
          if abs(ad[i, k]) > max then  // k列の中で一番大きい値を選ぶ
            begin
              max := abs(ad[i, k]);
              pivot := i;             // ピボットに設定
            end;
        end;
// pivotがkと違えば、pivotとkの行の入れ替え
      if pivot <> k then
        for j := 0 to N do            // 横方向にjでスキャンしてa[k, j]とa[pivot,j]をいれかえ
          begin
            b := ad[k, j];
            ad[k, j] := ad[pivot, j];
            ad[pivot, j] := b;
          end;
// a[k,k] が1になるように、横方向にjでスキャン
      p := ad[k, k];                   // 対角要素を保存
      ad[k, k] := 1;                   // 1になる事がわかっているのでa[k, K +1]から計算
      for j := k + 1 to N do ad[K, j] := ad[K, j] / p;

// a[i,k] を、縦方向にスキャン
      for i := 0 to N - 1 do
        begin
          if k <> i then  // 対角要素は1になっているので計算しない
            begin
              q := ad[i, k];
              // a[i,k] が0 になるように横方向にスキャン
              ad[i, k] := 0;           // ゼロになる事がわかっているのでa[i,K + 1]から計算
              for j := k + 1 to N do ad[i, j] := ad[i, j] - q * ad[k, j];
            end;
        end;
    end;
// 戻り値の設定
  for i := 0 to N - 1 do xxd[i] := ad[i, N];

  Memo1.Clear;
  LL := intTostr(N - 1) + '次回帰';
  Memo1.Lines.Add(LL);
{
  LL := '配列結果表示';
  Memo1.Lines.Add(LL);
// 行列の最後がどうなったか見たいとき実行
  for i := 0 to N - 1 do begin
      LL := '';
      for j := 0 to N do
        LL := LL + floatTostrF(a[i, j], ffGeneral, 7, 5) + '  ';
        Memo1.Lines.Add(LL);
  end;
}
  Memo1.Lines.Add('');
  LL := '解は';
  Memo1.Lines.Add(LL);
  for i := 0 to N - 1 do
    begin
      LL := floatTostrF(xx[i],ffGeneral, 8, 5);
      if i = 0 then LL :='a0=' + LL;
      if i = 1 then LL :='a1=' + LL + '   X';
      if i > 1 then LL :='a' + intTostr(i) + '=' + LL + '   X^' + intTostr(i);

      Memo1.Lines.Add(LL);
   end;
end;

// n次回帰計算用データーの配列割付と、ガウスサブルーチンの呼び出し
procedure TForm1.sai(xi, yi, xxd: DArray; N, S : integer);  // n次計算ルーチン
var
  i, j, k: integer;
  a: TAoAoD;
  yha, yj: DArray;
  my:  extended;
  Sy,Syh, r:  extended;
  LL : string;
begin
//  N := high(xxd) + 1;
//  S := high(xi) + 1;
  SetLength(a, N, N + 1);     // ガウス計算用配列確保
  SetLength(YhA, S);          // 推定値用配列確保
  SetLength(yj, S);          // 推定値用配列確保

  for i := 0 to S -1 do
    if DEFD then
      yj[i] := 1 / yi[i]
    else
      yj[i] := yi[i];

// 行列の作成  データーX^n の組み込み
  for i := 0 to N -1 do
    for j := 0 to N -1 do
      for k := 0 to S - 1 do
        a[i, j] := a[i,j] + power(xi[k] , i + j);
// X^n * Y の組み込み
  for i := 0 to N - 1 do
    for k := 0 to S - 1 do
      a[i, N] := a[i, N] + power(xi[k], i) * yj[k];

// ガウスの消去法実行
  gauss(a, xxd, N);

// 平均値の計算
  my  := Mean(yj);
//  my  := Mean(YA);
// 相関係数計算
  Sy  := 0;
  Syh := 0;
  for i := 0 to S -1 do
    begin
      YhA[i] := xxd[0];
      for j := 1 to N -1 do YhA[i] := YhA[i] + xxd[j] * power(Xi[i], j);  // 推定値の計算
      Sy := Sy + power(yj[i] - my, 2);      // データーの分散計算
      Syh := Syh + Power(YhA[i] - my, 2);   // 推定値の分散計算
    end;
  if Sy > 0 then
      r := Sqrt(Syh/Sy)                        // 相関係数計算
     else r := 1;
  LL := '相関係数 r= '+ floatTostrF(r, ffFixed, 2, 3);
  Memo1.Lines.Add(' ');
  Memo1.Lines.Add(LL);
end;

// グリッドの値による補間計算
procedure TForm1.interpolationBtnClick(Sender: TObject);
var
  N, ch, i, j, imax, Z : integer;
  cn : string;
  y2, y3 : extended;
  xd : extended;
begin
  Xin_interpolation.Enabled := False;
  N := Ninput(false, Z);
  if N < 0 then begin
    exit;
  end;
  GridSet(N + 1, 3);
  setlength(x, N);
  setlength(y, N);
  SetLength(xx, Z + 1);           // 計算結果用配列確保
  for i := Low(x) to High(x) do begin
    val(StringGrid1.cells[1,i + 1], x[i], ch);
    if ch <> 0 then begin
      cn := 'x' + intTostr(i + 1) + 'に間違いが有ります。';
      application.MessageBox(pchar(cn),'注意',0);
      break;
    end;
    val(StringGrid1.cells[2,i + 1], y[i], ch);
    if ch <> 0 then begin
      cn := 'y' + intTostr(i + 1) + 'に間違いが有ります。';
      application.MessageBox(pchar(cn),'注意',0);
      break;
    end;
  end;
  if ch <> 0 then exit;
//  if not datacheck(x, N) then exit;           // xの値に重複が無いか確認

  Series1.Clear;                              // プロット値
  Series2.Clear;                              // テスト計算値
  Series3.Clear;                              // 補間値
  Series5.Clear;                              // 指定点計算値

  xmin := x[0];
  xmax := x[0];
  for i := Low(x) to High(x) do begin
    if x[i] > xmax then xmax := x[i];
    if x[i] < xmin then xmin := x[i];
    Series1.AddXY(x[i], y[i]);
  end;
  sai(x, y, xx, Z + 1, N);

  imax := (N - 1) * 40;                             // 分割数
  dx := (xmax - xmin) / imax;
  for i := 0 to imax do
    begin
      xd := dx * i + xmin;
      y2 := 0;
      for j := 0 to Z do y2 := y2 + xx[j] * power(xd, j);
      if DEFD then y2 := 1 / y2;
      Series3.AddXY(xd, y2);            // グラフ表示
      if DEF then begin
        y3 := 0;
        case radiogroup1.ItemIndex of
          1: y3 := f(xd);
          2: y3 := f3(xd);
          3: y3 := f2(xd);
        end;
        Series2.AddXY(xd, y3);                  // テスト値
      end;
    end;
  DEF := False;
  Xin_interpolation.Enabled := True;
end;


// 指定値からの計算 指定値xによるy補間値計算とグラフ表示
procedure TForm1.Xin_interpolationClick(Sender: TObject);
var
  ch, i, j, Z, ii : integer;
  xd, y1, y3, xi, gmin, gmax : extended;
begin
  val(Xedit.Text, xd, ch);
  if ch <> 0 then begin
    application.MessageBox('xからY推定の入力に間違いが有ります。','注意',0);
    exit;
  end;
  Z := high(xx);
  Series2.Clear;                                  // テスト値グラフ
  Series3.Clear;                                  // 補間値グラフ
  Series5.Clear;                                  // 指定値点
  // 最小最大区間グラフ再描画
  gmin := xmin;
  gmax := xmax;
  if xd < xmin then gmin := xd;
  if xd > xmax then gmax := xd;
  ii := round((gmax - gmin) / dx);
  for j := 0 to ii do begin
    xi := j * dx + gmin;
    y3 := 0;
    for i := 0 to Z do y3 := y3 + xx[i] * power(xi, i);
    if DEFD then y3 := 1 / y3;
    Series3.AddXY(xi, y3);
  end;
  // 指定値点グラフ追加 指定値xによるy補間値計算とグラフ表示
  y1 := 0;
  for i := 0 to Z do y1 := y1 + xx[i] * power(xd, i);
  if DEFD then y1 := 1 / y1;
  Series5.AddXY(xd, y1);
end;

procedure TForm1.CheckBox2Click(Sender: TObject);
begin
  Xin_interpolation.Enabled := False;
end;


// グリッド初期設定
procedure TForm1.FormCreate(Sender: TObject);
begin
  FGridSet(101, 3);                 // 固定行1列1 + 100行2列まで行列を変更しても
                                    // 入力値がクリアされないようにします。
  GridSet(4, 3);                    // 最初の設定
end;

end.


download Lagrange_interpolation.zip

  三角関数、逆三角関数、その他関数 に戻る