アッカーマン関数

 非常に大きな値を生み出すアッカーマン関数があるので、プログラムの息抜きと言うことで、つくってみました。

アッカーマン関数に関しては、インターネットで調べてください。

 普通の整数演算では、m=3,n=12迄しか計算できません、これ以上の値を入れると、再起呼び出しのスタックオーバーフローとなります。
pop push演算と言う特殊な計算を使用すると、m=3,n=60迄の値が計算できます。
 Delphi にはBigIntegerが無いので使用するには、別途組み込む必要があります DelphiのBigIntegers 辺りを組み込んでください。
此処では、参考の為に、今まで使用していた多倍長の計算を使用してみました、計算の桁数の関係で浮動小数点の計算をしています。
 多倍長演算の浮動小数点演算だと、m=4,n=2迄は計算できますが、整数での計算はできません。
計算結果の指数部が19728となっているので、有効桁数が最低19728桁必要となり計算が出来ても、表示することが困難な値です。
 多倍長の組み込みは  MPArithからmparith_2018-11-27.zipをダウンロードして下さい。
Zipファイルを解凍して、解凍したフォルダーにパス(ツール->オプション->Delphiオプション->ライブラリ->ライブラリパス)を追加し、Uses に mp_types, mp_real, mp_cmplx, mp_baseを追加すれば使用可能になります。
 Push, Pop による演算は、  にょきにょきブログ にあるJavaのプログラムを参考に作りました。

プログラム

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls, system.UITypes;

type
  TForm1 = class(TForm)
    BitBtn1: TBitBtn;
    Edit1: TEdit;
    BitBtn2: TBitBtn;
    Edit2: TEdit;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    BitBtn3: TBitBtn;
    Edit3: TEdit;
    Edit4: TEdit;
    BitBtn4: TBitBtn;
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure BitBtn3Click(Sender: TObject);
    procedure BitBtn4Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses mp_base, mp_real, mp_types;

function input(var m, n: cardinal): boolean; forward;

//==============================================================================
// mの値による計算式選択計算
// 多倍長演算

var
  ansl : mp_float;
  mpovf : boolean;

procedure Ackermann_function_calc(m: integer; nl: mp_float);
const
  wp1 = maxword + 1;                              // 65536
  nlmax = 2147483643;                             // m = 3 時 nl最大値
var
  tmp: mp_float;
  two, mpwm: mp_float;
  nlmaxd : mp_float;
begin
  mpovf := false;
  mpf_init4(tmp, two, mpwm, nlmaxd);
  mpf_set_int(two, 2);                          // 2
  mpf_set_int(mpwm, wp1);                       // 65536
  mpf_set_int(nlmaxd, nlmax);                   // 2147483643
  if m = 0 then mpf_add_int(nl, 1, ansl);       // m = 0  n + 1
  if m = 1 then mpf_add_int(nl, 2, ansl);       // m = 1  n + 2
  if m = 2 then begin                           // m = 2
    mpf_mul_int(nl, 2, ansl);                            //  n * 2 + 3
    mpf_add_int(ansl, 3, ansl);
  end;
  if m = 3 then begin                           // m = 3
    if mpf_is_gt(nl, nlmaxd) then begin                  // nl最大値超えているか確認
       mpovf := true;                                    // 超えていたら overflow
    end
    else begin
      mpf_add_int(nl, 3, tmp);                           // 2^(n+3) - 3
      mpf_expt(two , tmp, ansl);
      mpf_sub_int(ansl, 3, ansl);
    end;
  end;
  if m = 4 then begin                           // m = 4
    if mpf_is0(nl) then mpf_set_int(ansl, 13);          // n = 0
    if s_mpf_is_gt0(nl) then begin
      if mpf_is1(nl) then mpf_sub_int(mpwm, 3, ansl);   // n = 1  65536 - 3
      if mpf_is_eq(nl, two) then begin
        mpf_expt(two , mpwm, tmp);                      // n = 2  2^65536 - 3
        mpf_sub_int(tmp, 3, ansl);
      end;
      if mpf_is_gt(nl, two) then mpovf := true;         // n > 2  overflow
    end;
  end;
  if m = 5 then begin                           // m = 5
    if mpf_is0(nl) then mpf_sub_int(mpwm, 3, ansl);     // n = 0  65536 - 3
    if s_mpf_is_gt0(nl) then mpovf := true;             // n > 0  overflow
  end;
  mpf_clear4(tmp, two, mpwm, nlmaxd);
end;

// 整数入力確認
// nの桁数が多すぎて通常のルーチンで確認できないのでここで確認します
function indatacheck(str: string): boolean;
var
  stc : string;
  i, m, leng : integer;
  ch: char;
  ff: boolean;
begin
  stc := '0123456789';
  leng := length(str);                   // 文字長さ
  ff := true;
  for i := 1 to leng do begin
    ch := str[i];
    ff := false;
    for m := 1 to 10 do begin
      if ch = stc[m] then ff := true;    // 一致したらtrue
      if ff then break;                  // 一致したらブレーク
    end;
    if not ff then break;                // 一致しないとブレーク
  end;
  result := ff;
end;

procedure TForm1.BitBtn4Click(Sender: TObject);
const
  mmax = 1;                              // m の最大文字数
  nmax = 200;                            // n の最大文字数
var
  m: integer;
  nl : mp_float;
  ch : integer;
begin
  ch := length(form1.LabeledEdit1.Text);
  if ch > mmax then begin
    MessageDlg('mの桁数が多すぎます。', mtInformation, [mbOk], 0, mbOk);
    exit;
  end;
  val(form1.LabeledEdit1.Text, m, ch);
  if ch <> 0 then begin
    MessageDlg('mの値に間違いがあります。', mtInformation, [mbOk], 0, mbOk);
    exit;
  end;
  ch := length(form1.LabeledEdit2.Text);
  if ch > nmax then begin
    MessageDlg('nの桁数が多すぎます。', mtInformation, [mbOk], 0, mbOk);
    exit;
  end;
  if not indatacheck(Form1.LabeledEdit2.Text) then begin
    MessageDlg('nの値に間違いがあります。', mtInformation, [mbOk], 0, mbOk);
    exit;
  end;
  bitbtn4.Enabled := False;
  mpf_init2(ansl, nl);
  mpf_read_decimal(nl, pansichar(ansistring(Form1.LabeledEdit2.Text)));
  Ackermann_function_calc(m, nl);
  if not mpovf then
    Edit4.Text := string(mpf_decimal_alt(ansl, 35))
  else
    Edit4.Text := '値が大きすぎます。';
  mpf_clear2(ansl, nl);
  bitbtn4.Enabled := True;
end;

//==============================================================================
// 多倍長によるアッカーマン関数計算 push pop 使用
// push深さ制限値を超えると、mp_floatが最大値を超えます。
// 実際の制限値は指定の値(100000)より大きく設定できますが、

// pushl popl用定義
// 多倍長設定
const
  ppsizel = 10000000;                         // push深さ制限値

var
  retl : mp_float;
  buffl: array of mp_float;
  ppindexl, ppsizemax : cardinal;
  ovf : boolean;

// push pop が Delphiに無いため作成
procedure pushl(X: mp_float);
begin
  mpf_copy(X, buffl[ppindexl]);
  if ppindexl > ppsizemax then ppsizemax := ppindexl;
  inc(ppindexl);
end;

procedure popl(var X: mp_float);
begin
  dec(ppindexl);
  mpf_copy(buffl[ppindexl], X);
end;

// アッカーマン関数 push pop 多倍長演算
procedure Ackermann_function_pushpopl;
var
  m, n: mp_float;
  one, two: mp_float;
  tmp: mp_float;
begin
  mpf_init5(m, n, one, two, tmp);
  mpf_set1(one);
  mpf_set_int(two, 2);
  ovf := False;
  while ppindexl > 1 do begin
    popl(n);
    popl(m);
    if mpf_is0(m) then begin
      mpf_add_int(n, 1, tmp);               // n + 1
      pushl(tmp);
    end
    else
    if mpf_is0(n) then begin
      mpf_sub_int(m, 1, tmp);               // m - 1
      pushl(tmp);
      pushl(one);
    end
    else
    if mpf_is1(m) then begin
      mpf_add_int(n, 2, tmp);               // n + 2
      pushl(tmp);
    end
    else
    if mpf_is_eq(m, two) then begin
      mpf_mul_int(n, 2, tmp);               // n * 2
      mpf_add_int(tmp, 3, tmp);             // n * 2 + 3
      pushl(tmp);
    end
    else
    begin
      mpf_sub_int(m, 1, tmp);               // m - 1
      pushl(tmp);
      pushl(m);
      mpf_sub_int(n, 1, tmp);               // n - 1
      pushl(tmp);
    end;
    // poshの深さ値が用意した配列数をに達したらループ終了
    // breakで終了でもOKここでは ppindexlに1をセット
    if ppindexl >= ppsizel - 1 then begin
      ppindexl := 1;
      ovf := True;
    end;
  end;
  popl(retl);
  mpf_clear5(m, n, one, two, tmp);
end;

// m,n の値は integerの最大値を超えないように
procedure TForm1.BitBtn3Click(Sender: TObject);
var
  ml, nl: mp_float;
  m, n: cardinal;
  i : integer;
begin
  if not input(m, n) then exit;
  bitbtn3.Enabled := False;
  mpf_init3(ml, nl, retl);
  mpf_set_int(ml, m);
  mpf_set_int(nl, n);
  ppsizemax := 0;
  ppindexl := 0;
  setlength(buffl, ppsizel);          // push pop 用配列初期化のため一度に確保
  for i := 0 to ppsizel - 1 do begin
    mpf_init(buffl[i]);
  end;
  pushl(ml);
  pushl(nl);
  Ackermann_function_pushpopl;
  Form1.Canvas.TextOut(edit3.Left + 10, edit3.Top - 20, '                                          ');
  if not ovf then begin
    Edit3.Text := string(mpf_decimal_alt(retl, 35));
    Form1.Canvas.TextOut(edit3.Left + 150, edit3.Top - 30, 'Push 深さ = ' + intTostr(ppsizemax));
  end
  else
    Edit3.Text := 'Push 深さが制限値' + intTostr(ppsizel) + 'を超えました。';
  for i := 0 to ppsizel - 1 do begin
    mpf_clear(buffl[i]);
  end;
  mpf_clear3(ml, nl, retl);
  bitbtn3.Enabled := True;
end;

//==============================================================================
// 再起呼び出しは数の制限がかかるので、配列による、pop pushを使用します。
// 演算速度も飛躍的に早くなります。
// push深さ制限値を超えると、uint64が最大値を超えて0からに戻ってしまいます。

// push pop用定義
var
  buff: array of uint64;
  ppindex, ppsize : cardinal;
  ppMax : cardinal;
  oovf : boolean;
const
  addsize = 10;
  psize = 64;                           // push深さ制限値

// push pop が Delphiに無いため作成
procedure push(X: uint64);
begin
  if ppsize <= ppindex then begin       // 配列が不足したら追加
     ppsize := ppsize + addsize;
     setlength(buff, ppsize);           // push pop 用配列サイズ変更
  end;
  buff[ppindex] := X;
  if ppindex > ppmax then ppmax := ppindex;
  inc(ppindex);
end;

procedure pop(var X: uint64);
begin
  dec(ppindex);
  X := buff[ppindex];
end;

// アッカーマン関数 push pop 演算
function Ackermann_function_pushpop: uint64;
var
  m, n: uint64;
begin
  oovf := false;
  while ppindex > 1 do begin
    pop(n);
    pop(m);
    if m = 0 then
      push(n + 1)
    else
    if n = 0 then begin
      push(m - 1);
      push(1);
    end
    else
    if m = 1 then
      push(n + 2)
    else
    if m = 2 then
      push(n * 2 + 3)
    else
    begin
      push(m - 1);
      push(m);
      push(n - 1);
    end;
    // posh深さの値が制限数に達したらループ終了
    // breakで終了でもOKここでは ppindexに1をセット
    if ppindex >= psize then begin
      oovf := true;
      ppindex := 1;
    end;
  end;
  pop(result);
end;

// アッカーマン関数計算 push pop 計算実行
procedure TForm1.BitBtn2Click(Sender: TObject);
var
  m, n: cardinal;
  ans : uint64;
begin
  if not input(m, n) then exit;
  bitbtn2.Enabled := False;
  ppMax := 0;
  ppindex := 0;
  ppsize := addsize;
  setlength(buff, ppsize);          // push pop 用配列確保
  push(m);
  push(n);
  ans := Ackermann_function_pushpop;
  Form1.Canvas.TextOut(edit2.Left + 10, edit2.Top - 20, '                                          ');
  if not oovf then begin
    edit2.Text := uintTostr(ans);
    Form1.Canvas.TextOut(edit2.Left + 10, edit2.Top - 20, 'Push 深さ = ' + intTostr(ppmax));
  end
  else
    edit2.Text := 'Push深さが制限値' + intTostr(psize) + 'を超えました。';
  bitbtn2.Enabled := True;
end;

//==============================================================================
// 再起呼び出しアッカーマン関数計算。
//    ack(m, n)
//        n = 0   n + 1
//        m = 0   ack(m-1 ,1)
//        other   ack(m-1, ack(m, n-1))
// 再起呼びたしが多すぎスタックオーバーによりプログラムが異常終了する場合は
// スタック深さ限度で調整して下さい。
const
  stmax = 64562;                    // スタック深さ限度
var
  ret : cardinal;
  loop, loopn : cardinal;
  stack : cardinal;
  stf   : boolean;

// アッカーマン関数
// スタックの量を少しでも少なくするためFunctionでなくprocedur使用
procedure Ackermann_function(m, n: cardinal);
begin
  if stf then exit;                 // スタック限度に達した場合ここで終了スタック戻し
  inc(loop);                        // スタックカウンターインクリメント
  inc(loopn);                       // ループカウンター
  if loop > stack then begin
    stack := loop;
    if stack >= stmax then begin    // スタックが限度に達したら
      stf := true;                  // スタック限度フラグセット
    end;
  end;
  if m = 0 then begin
    ret := n + 1;                   // アッカーマン数
    dec(loop);                      // スタックカウンターデクリメント
    exit;
  end;
  if n = 0 then begin
    Ackermann_function(m - 1, 1);   // 再起呼び出し
    dec(loop);                      // スタックカウンターデクリメント
    exit;
  end;
  Ackermann_function(m, n - 1);     // 再起呼び出し
  Ackermann_function(m - 1, ret);   // 再起呼び出し
  dec(loop);                        // スタックカウンターデクリメント
end;

// アッカーマン関数計算 実行
// m=4 n=0 より大きい値は計算不可です
procedure TForm1.BitBtn1Click(Sender: TObject);
var
  m, n: cardinal;
begin
  ret := 0;
  if not input(m, n) then exit;
  BitBtn1.Enabled := False;
  edit1.Text := '計算中';
  application.ProcessMessages;
  loop := 0;
  loopn := 0;
  stack := 0;
  stf := false;
  Ackermann_function(m, n);
  Form1.Canvas.TextOut(edit1.Left + edit1.Width + 20, edit1.Top - 5, '                          ');
  Form1.Canvas.TextOut(edit1.Left + edit1.Width + 10, edit1.Top + 10, '                               ');
  if not stf then begin
    edit1.Text := intTostr(ret);
    Form1.Canvas.TextOut(edit1.Left + edit1.Width + 20, edit1.Top - 5, 'Loop = ' + intTostr(loopn));
    Form1.Canvas.TextOut(edit1.Left + edit1.Width + 20, edit1.Top + 10, 'stack = ' + intTostr(stack));
  end
  else begin
    edit1.Text := 'スタック容量超過';
    Form1.Canvas.TextOut(edit1.Left + edit1.Width + 10, edit1.Top + 10, 'stack制限値 = ' + intTostr(stmax));
  end;
  BitBtn1.Enabled := True;
end;

//==============================================================================

// データー入力処理
function input(var m, n: cardinal): boolean;
const
  cmax = Maxlongint;
  mmax = 1;
  lmax = 10;
var
  ch: integer;
  cin: uint64;
begin
  result := true;
  ch := length(form1.LabeledEdit1.Text);
  if ch > mmax then begin
    MessageDlg('mの桁数が多すぎます。', mtInformation, [mbOk], 0, mbOk);
    result := false;
    exit;
  end;
  val(form1.LabeledEdit1.Text, cin, ch);
  if ch <> 0 then begin
    MessageDlg('mの値に間違いがあります。', mtInformation, [mbOk], 0, mbOk);
    result := false;
    exit;
  end;
  if cin > cmax then begin        // integer最大値を超えてないか確認
    MessageDlg('mの値が大きすぎます。', mtInformation, [mbOk], 0, mbOk);
    result := false;
    exit;
  end;
  m := cin;
  ch := length(form1.LabeledEdit2.Text);
  if ch > lmax then begin
    MessageDlg('nの桁数が多すぎます。', mtInformation, [mbOk], 0, mbOk);
    result := false;
    exit;
  end;
  val(form1.LabeledEdit2.Text, cin, ch);
  if ch <> 0 then begin
    MessageDlg('nの値に間違いがあります。', mtInformation, [mbOk], 0, mbOk);
    result := false;
    exit;
  end;
  if cin > cmax then begin        // integer最大値を超えてないか確認
    MessageDlg('nの値が大きすぎます。', mtInformation, [mbOk], 0, mbOk);
    result := false;
    exit;
  end;
  n := cin;
end;

//==============================================================================

procedure TForm1.FormCreate(Sender: TObject);
begin
  edit1.Text := '';
  edit2.Text := '';
  edit3.Text := '';
  edit4.Text := '';
  Form1.Canvas.Font.Size := 11;
end;

end.


download Ackermann_function.zip

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