ポリガンマ関数
ガンマ関数Γ(z)に対し、対数微分で定義される関数。
ウィキペディア ポリガンマ関数に詳しく説明があるのでそちらを参照してください。
値の計算は漸近展開によって計算されます。
Ψ(z).Ψ(1)(z),Ψ(2)(z),Ψ(3)(z),Ψ(4)(z) ディ、トリ、テトラ、ペンタ、ヘキサ ガンマ関数とそれぞれ呼ばれていて、ディガンマ関数は本ホームページで既に取り上げていますが、ポリガンマ関数のプログラムは、計算式は違うが、ディガンマ関数を含めて計算するようになっています。
∑の計算は、無限大迄計算するようになっていますが、実際には不可能なので、必要な桁数に応じて、ベルヌーイ数をどこまで計算するかを決めます。
Zの値が、大きい場合は、誤差が小さくなりますが、値が小さい場合、誤差が大きくなるので、値を修正して計算結果が正しい値に近づくようにいます。
最初のプログラムは、応用統計学のポリ・ガンマ関数のC言語,およびFortran 77言語による算譜にあるC言語のものを、Delphi用に変換したものです。(ポリ・ガンマ関数のC言語,およびFortran 77言語による算譜の方がプログラムリストそのものをダウンロードできます。)
元のプログラムは、マイナス側の値を計算するようになっていませんでしたが、テストをした結果、問題なく計算できるようなので、計算出来る様に修正しました。
プログラムについては、応用統計学のポリ・ガンマ関数のC言語,およびFortran 77言語による算譜 からPDFファイルをダウンロードして読んでもらえばよく分かると思います。
プログラムリストはポリ・ガンマ関数のC言語,およびFortran 77言語による算譜からC言語のリストをダウンロードして、RAD Studio のコンソールアプリケーションとして、コンパイル、コマンドラインから実行すれば、少しの修正で、実行確認が出来ます。
Delphiに変換するに際して、多倍長演算の為にベルヌーイ数の数が自由に出来る様にしたり、不要な符号の設定の削除とかの修正をしました。
左図は、X64のモードで、コンパイルし実行した場合です。
X64のモードなので、多倍長演算以外は全て、Doubleの精度で計算されています。
ポリガンマの種別は、0を含み0以上です。
-1は、対数ガンマを表しているので、必要であれば、ガンマ関数のプログラムで対数指定して計算してください。
プログラム
多倍長演算は、255桁まで計算可能ですが、精度60桁指定で計算し、50桁程表示しています。
多倍長の組み込みは MPArithからmparith_2018-11-27.zipをダウンロードし、Zipファイルを解凍して、解凍したフォルダーにパス(ツール->オプション->Delphiオプション->ライブラリ->ライブラリパス)を追加し、Uses
に mp_types, mp_real, mp_cmplx, mp_baseを追加すれば使用可能になります。
unit Main; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, system.Math, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Buttons, system.UITypes, VclTee.TeeGDIPlus, VCLTee.Series, VCLTee.TeEngine, VCLTee.TeeProcs, VCLTee.Chart, mp_types, mp_real, mp_base; type TForm1 = class(TForm) BitBtn1: TBitBtn; LabeledEdit1: TLabeledEdit; LabeledEdit2: TLabeledEdit; Memo1: TMemo; Chart1: TChart; Series1: TLineSeries; Series2: TPointSeries; procedure BitBtn1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} var slvamp : double; //========================================================================================= // procedure polygamma_mp(km: integer; x: mp_float; var ans: mp_float var INFF: integer); // // km integer polygamma種別 k = 0~ // x mp_float 値 // ans mp_float 答え // ans の精度は50桁以上 mpfの計算桁数は255桁 // INF integer 演算フラグ // 値が∞又は-∞になる場合、値の設定が出来ないのでフラグ使用 // INFF 0 通常 1 演算結果∞ -1 演算経過-∞ 2 ボリガンマ種別Km<0 // //========================================================================================= procedure polygamma_mp(km: integer; x: mp_float; var ans: mp_float; var INFF: integer); label POYEND; const BRNTRM = 29; BRNARY = BRNTRM - 12; // 100桁以上の精度を指定する場合は 12の値を小さくします // ベルヌーイ数 分子 numerator: array[0..BRNTRM] of string = ( '1', '-1', '1', '-1', '5', '-691', '7', '-3617', '43867', '-174611', '854513', '-236364091', '8553103', '-23749461029', '8615841276005', '-7709321041217', '2577687858367', '-26315271553053477373', '2929993913841559', '-261082718496449122051', '1520097643918070802691', '-27833269579301024235023', '596451111593912163277961', '-5609403368997817686249127547', '495057205241079648212477525', '-801165718135489957347924991853', '29149963634884862421418123812691', '-2479392929313226753685415739663229', '84483613348880041862046775994036021', '-1215233140483755572040304994079820246041491' ); // ベルヌーイ数 分母 denominator: array[0..BRNTRM] of string = ( '6', '30', '42', '30', '66', '2730', '6', '510', '798', '330', '138', '2730', '6', '870', '14322', '510', '6', '1919190', '6', '13530', '1806', '690', '282', '46410', '66', '1590', '798', '870', '354', '56786730' ); var i, j: integer; s, y, x2, pk, pxk : mp_float; slv : mp_float; f : mp_float; n : mp_int; i2 : mp_int; isgn : mp_float; brn: array[0..BRNARY] of mp_float; tmp0, tmp1, tmp2: mp_float; kf :mp_float; tmpi, k : mp_int; // 階乗 m! procedure factor(m: mp_int; var ans : mp_float); var i : mp_int; begin // 多倍長初期化 ---- mp_init(i); // ----------------- mpf_set1(ans); // ans = 1 mp_set_int(i, 2); // i = 2 // for i := 2 to m do while mp_is_le(i, m) do begin mpf_mul_mpi(ans, i, ans); // ans = ans * i mp_inc(i); // inc(i) end; // 多倍長解放 ---- mp_clear(i); end; // slv 漸近展開に適用される値 procedure slvcalc(k : mp_int; var ans: mp_float); const n = BRNARY + 1; var a , b, c, f :mp_float; nn, ntmp : mp_int; begin // 多倍長初期化 ------ mpf_init4(a, b, c, f); mp_init2(nn, ntmp); // ------------------- mp_set_int(nn, n); // nn = int(n) // a := 2 * abs(brn[BRNTRM]) * (2 * n + k - 1)!; mp_mul_int(nn, 2, nn); // nn = 2n mp_add(nn, k, ntmp); // ntmp = 2n + k mp_dec(ntmp); // ntmp = 2n + k - 1 factor(ntmp, f); // f = (2n + k - 1)! mpf_abs(brn[BRNARY], a); // a = abs(brn[BRNTRM]) mpf_mul_int(a, 2, a); // a = abs(brn[BRNTRM]) * 2 mpf_mul(a, f, a); // a = 2 * abs(brn[BRNTRM]) * (2n + k - 1)! // b := brn[0] * (2 * n)! * (k + 1)!; factor(nn, f); // f = (2n)! mp_copy(k, ntmp); // ntmp = k mp_inc(ntmp); // ntmp = k + 1 factor(ntmp, c); // c = (k + 1)! mpf_mul(f, c, b); // b = (2n)! * (k + 1)! mpf_mul(b, brn[0], b); // b = brn[0] * (2n)! * (k + 1)! // c := a / b * 1e60; // 計算精度60桁 mpf_div(a, b, c); // c = a / b mpf_mul_dbl(c, 1e60, c); // c = c * 1e60 // 150桁以上を指定すると異状に時間が増えます // result := power(c, 1 / (2 * n - 2)); // その時はベルヌーイ数を増やします mpf_set_mpi(b, nn); // b = float(nn) BRNARY = BRNTRM - 12 <- マイナス値を小さく mpf_sub_int(b, 2, b); // b = 2n - 2 mpf_inv(b, a); // a = 1/(2n - 2) mpf_expt(c, a, ans); // ans = c^a power(c ,1 / (2 * n - 2)) // 多倍長解放 ---------- mpf_clear4(a, b, c, f); mp_clear2(nn, ntmp); // --------------------- end; begin // 多倍長初期化 ------------------------ mpf_init5(s, y, x2, pk, pxk); mpf_init(slv); mpf_init(f); mp_init(n); mp_init(i2); mpf_init(isgn);; for i := 0 to BRNARY do mpf_init(brn[i]); mpf_init3(tmp0, tmp1, tmp2); mpf_init(kf); mp_init2(tmpi, k); // -------------------------------------- mp_set_int(k, km); // km -> k slvamp := 0; //----------------------------------------------------------------- // 分母がゼロになった時の値セット // 多倍長から倍精度に変換したときinfinityになる値をセットします。 // 多倍長の値に無限大INFが無い為の処理です。 // INFFフラグ +無限大の時 INFF = 1 -無限大の時 -1 K<0の時 2 正常 0 //----------------------------------------------------------------- {$IFDEF CPUX86} // プラットフォーム32ビット begin mpf_set_ext(tmp1, MaxExtended); // mpf -> dbl 変換時 INFにします Huge値 end; {$ENDIF CPUX86} {$IFDEF CPUX64} // プラットフォーム64ビット begin mpf_set_dbl(tmp1, Maxdouble); // tmp1 Maxdouble mpf_Mul_dbl(tmp1, Maxdouble, tmp1); // Maxdouble^2 mpf -> dbl 変換時 INFにします Huge値 end; {$ENDIF CPUX64} mpf_chs(tmp1, tmp2); // tmp2 -Maxdouble^2 or -Mxxextended mp_set_int(tmpi, 0); // tmpi 0 INFF := 0; // 通常エラーフラグ 0 // if K < 0 then if mp_is_lt(k, tmpi) then begin mpf_set0(ans); // ans = 0 INFF := 2; // 入力エラー // exit; goto POYEND; // 終了 end; // if x <= 0 then begin if s_mpf_is_le0(x) then begin // xの値が 0 or マイナスの時 // n := round(x); mpf_round(x , n); // x丸め->n mpf_set_mpi(tmp0, n); // n -> tmp0 // if n := x then begin if mpf_is_eq(x, tmp0) then begin // xが整数なら INFF := 1; // +∞ フラグセット // result := maxdouble; mpf_copy(tmp1, ans); // Huge - > ans // i := k mod 2 mp_mod_int(k, 2, i); // ポリガンマ種別 mod 2 if i = 0 then begin // 偶数なら // i := n mod 2 mp_abs(n, n); // nが負数だとmodの計算が出来ない(xの値) mp_mod_int(n, 2, i); // n mod 2 (abs(x) mod 2) if i = 1 then begin // 奇数だったら mpf_copy(tmp2, ans); // -Huge -> ans INFF := -1; // -∞ フラグセット end; // exit; end; goto POYEND; // 終了 end; end; // ベルヌーイ数の誤差を小さくする為、分子と分母に分けて計算で設定しています。 for i := 0 to BRNARY do begin mpf_read_decimal(tmp0, pansichar(ansistring(numerator[i]))); // 分子 mpf_read_decimal(tmp1, pansichar(ansistring(denominator[i]))); // 分母 mpf_div(tmp0, tmp1, brn[i]); end; // slv := slvcalc(k); slvcalc(k, slv); // 漸近展開に適用される値計算 // slvamp := slv; slvamp := mpf_todouble(slv); // slv値表示の為なので無くても可 // pk := 1; mpf_set1(pk); // if k mod 2 = 1 then isgn := 1 else isgn := -1; mp_mod_int(k, 2, i); // i = k mod 2 mpf_set1(isgn); // isgn = 1 if i = 0 then mpf_chs(isgn, isgn); // for i := 0 to k - 1 do pk := pk * (i + 1); for i := 0 to km - 1 do mpf_mul_int(pk, i + 1, pk); // if x >= slv then begin if mpf_is_ge(x, slv) then begin // s := 0; mpf_set0(s); // x2 := x * x; mpf_mul(x, x, x2); // if k = 0 then begin // digamma mp_is0(tmpi); // tmpi = 0 if mp_is_eq(k, tmpi) then begin // digamma for i := BRNARY downto 0 do begin mp_set_int(tmpi, i + 1); // tmp1 = i + 1 // i2 := 2 * (i + 1); mp_mul_int(tmpi, 2, i2); // tmpi = 2 * (i + 1) // s := s + brn[i] / i2; mpf_div_mpi(brn[i], i2, tmp0); // tmp0 = brn[i] / i2; mpf_add(s, tmp0, s); // s = brn[i] / i2 + 2 // s := s / x2; mpf_div(s, x2, s); end; // s := ln(x) - 1 / (2 * x) - s; mpf_ln(x, tmp0); // tmp0 = ln(x) mpf_mul_int(x, 2, tmp1); // tmp1 = 2 * x mpf_inv(tmp1, tmp1); // tmp1 = 1/(2 * x); mpf_sub(tmp0, tmp1, tmp2); // tmp2 =ln(x) - 1/(2*x) mpf_sub(tmp2, s, s); // s := ln(x) - 1/(2*x) - s end else begin // trigamma, tetragamma..... for i := BRNARY downto 0 do begin // f := 1; mpf_set1(f); // i2 := 2 * (i + 1); mp_set_int(tmpi, i); // tmpi = i mp_inc(tmpi); // tmpi = i + 1 mp_mul_int(tmpi, 2, i2); // i2 = i * 2 // j := i2 + k - 1; mp_add(i2, k, tmpi); // tmpi = i2 + k mp_dec(tmpi); // tmpi = i2 + k - 1 // while j > i2 do begin // "tmpi = j" while mp_is_gt(tmpi, i2) do begin // f := f * j; mpf_mul_mpi(f, tmpi, f); // f = f * j // dec(j); mp_dec(tmpi); end; // s := s + brn[i] * f * isgn; mpf_mul(brn[i], f, tmp0); // tmp0 = brn[i] * f mpf_mul(tmp0, isgn, tmp1); // tmp1 = brn[i] * f * isgn mpf_add(s, tmp1, s); // s := s + brn[i] * f * isgn // s := s / x2; mpf_div(s, x2, s); end; // pxk := 1; mpf_set1(pxk); // for i := 0 to k - 1 do begin for i := 0 to km - 1 do begin // s := s / x; mpf_div(s, x, s); // pxk := pxk * x; mpf_mul(pxk, x, pxk); end; // s := s + pk * 1 / (2 * pxk * x) * isgn; mpf_mul(pxk, x, tmp0); // tmp0 = pxk * x mpf_mul_int(tmp0, 2, tmp1); // tmp1 = 2 * pxk * x mpf_inv(tmp1, tmp0); // tmp0 = 1 / (2 * pxk * x) mpf_mul(tmp0, isgn, tmp2); // tmp2 = 1 / (2 * pxk * x) * isgn mpf_mul(tmp2, pk, tmp0); // tmp0 = pk * 1 / (2 * pxk * x) * isgn mpf_add(s, tmp0, s); // s + pk * 1 / (2 * pxk * x) * isgn // f := pk / k; mpf_div_mpi(pk, k, f); // s := s + f / pxk * isgn; mpf_div(f, pxk, tmp0); // tmp0 = f / pxk mpf_mul(tmp0, isgn, tmp1); // tmp1 = f / pxk * isgn mpf_add(s, tmp1, s); // s = s + f / pxk * isgn end; end else begin // x < sly // n := trunc(slv - x); mpf_sub(slv, x, tmp0); // tmp0 = slv - x mpf_trunc(tmp0, n); // n = trunc(slv - x) // y := n + x + 1; mp_add_int(n, 1, tmpi); // tmpi = n + 1 mpf_add_mpi(x, tmpi, y); // n + x + 1 polygamma_mp(km, y, s, INFF); // 再帰 // for i := 0 to n do begin mp_set_int(tmpi, 0); // tmpi = 0 "i =0" while mp_is_le(tmpi, n) do begin // while i <= n do begin // y := y - 1; mpf_sub_int(y, 1, y); // y - 1 mpf_abs(y, tmp0); // tmp0 = abs(y) mpf_set_dbl(tmp1, 1e-3); // tmp1 = 1e-3 // if abs(y) < 1e-3 then begin if mpf_is_lt(tmp0, tmp1) then begin // if x > 0 then y := x - trunc(x + 0.5) // else y := x - trunc(x - 0.5); if s_mpf_is_gt0(x) then mpf_add_dbl(x, 0.5, tmp2) // tmp2 = x + 0.5 else mpf_sub_dbl(x, 0.5, tmp2); // tmp2 = x - 0.5 mpf_int(tmp2, tmp1); // tmp1 = trunc(tmp2) mpf_sub(x, tmp1, y); // y = x - trunc(x+-0.5) end; // pxk := 1; mpf_set_int(pxk , 1); // for j := 0 to k - 1 do pxk := pxk * y; for j := 0 to km - 1 do mpf_mul(pxk, y, pxk); // pxk := pxk * y // s := s + isgn * pk / pxk / y; mpf_mul(isgn, pk, tmp0); // tmp0 = isgn * pk mpf_div(tmp0, pxk, tmp1); // tmp1 = isgn * pk / pxk mpf_div(tmp1, y, tmp2); // tmp2 = isgn * pk / pxk / y mpf_add(s, tmp2, s); // s = s + isgn * pk / pxk / y mp_inc(tmpi); // inc(tmpi) "inc(i)" end; end; // result = s; mpf_copy(s, ans); POYEND: // 多倍長開放 ---------------------------- mpf_clear5(s, y, x2, pk, pxk); mpf_clear(slv); mpf_clear(f); mp_clear(n); mp_clear(i2); mpf_clear(isgn);; for i := 0 to BRNARY do mpf_clear(brn[i]); mpf_clear3(tmp0, tmp1, tmp2); mpf_clear(kf); mp_clear2(tmpi, k); //---------------------------------------- end; //========================================================================================= var slva : double; //============================================================================================ // function polygamma_brn(k: integer; x: double; var INFF: integer): double; // // https://www.jstage.jst.go.jp/article/jappstat1971/22/1/22_1_23/_article/-char/ja // http://202.16.123.1/~tunenori/polygamma.html // // C のプログラムをdelphiに変換 // xの値がマイナスでも計算出来る様に変更 // 精度はポリガンマの精度はdouble計算で 14~15桁 extended(long double) で16~18桁程度です。 // x値が負数の時double 14~15桁 extended(long double)で16~18桁程度 // x64 Doubleモードの時 digamma のゼロ近辺の答えの有効桁は小数点以下14桁程度となります。 // // X86モード時 extended X64モード時 Double // // K integer ポリガンマ種別 0~ // x extended 値 // 戻り値 extended // INFF integer 演算フラグ 0 通常 -1 ∞ +1 +∞ +2 入力エラーK<0 // INFFはDouble,extendedでの計算には不要ですが、多倍長計算にあわせています // モード0での結果ゼロ近辺の値 k = 0 x = 1.46163214496836235 // //============================================================================================ function polygamma_brn(k: integer; x: extended; var INFF: integer): extended; const BRNTRM = 13; BRNARY = BRNTRM - 4; // オリジナルのプログラムに合わせる為のものです。 // BRNARY = BRNTRM; brn: array[0..BRNTRM] of extended = (1.0 / 6, -1.0 / 30, 1.0 / 42, -1.0 / 30, 5.0 / 66, -691.0 / 2730, 7.0 / 6, -3617.0 / 510, 43867.0 / 798, -174611.0 / 330, 854513.0 / 138, -236364091 / 2730, 8553103 / 6, -23749461029 / 870 ); var s, y, x2, pk, pxk : extended; slv : extended; // 漸近展開に適用される十分に大きな値 f : extended; n : integer; i, j : integer; i2, isgn : integer; // 階乗 m! function factor(m: integer): extended; var i : integer; J : extended; begin j := 1; for i := 2 to m do j := j * i; result := j; end; // slv 漸近展開に適用される値 function slvcalc(k : integer): extended; var a , b, c :extended; n : integer; begin n := BRNARY + 1; a := 2 * abs(brn[BRNARY]) * factor(2 * n + k - 1); b := brn[0] * factor(2 * n) * factor(k + 1); {$IFDEF CPUX64} // プラットフォーム64ビット begin c := a / b * 1e16; // 計算精度16桁 end; {$ENDIF CPUX64} {$IFDEF CPUX86} // プラットフォーム32ビット begin c := a / b * 1e20; // 計算精度20桁 end; {$ENDIF CPUX86} result := power(c, 1 / (2 * n - 2)); end; begin INFF := 0; // 演算エラーフラグフラグ 0 slva := 0; if k < 0 then begin result := NAN; // result = 無効な値 INFF := 2; // 入力エラー exit; // 終了 end; if x <= 0 then begin // xの値が0 or マイナスの時 n := round(x); if n = x then begin // xが整数の時 INFF := 1; // ∞ フラグセット result := infinity; // 戻り値 ∞; if k mod 2 = 0 then // ポリガンマが偶数の時 if n mod 2 <> 0 then begin // 奇数の-x値なら result := -infinity; // 戻り値 -∞; INFF := -1; // -∞ フラグセット end; exit; // 終了 end; end; slv := slvcalc(k); slva := slv; // slv値表示の為なので無くても可 pk := 1; if k mod 2 = 1 then isgn := 1 else isgn := -1; for i := 0 to k - 1 do pk := pk * (i + 1); if x >= slv then begin s := 0; x2 := x * x; if k = 0 then begin // digamma for i := BRNARY downto 0 do begin i2 := 2 * (i + 1); s := s + brn[i] / i2; s := s / x2; end; s := ln(x) - 1 / (2 * x) - s; end else begin // trigamma, tetragamma..... for i := BRNARY downto 0 do begin f := 1; i2 := 2 * (i + 1); j := i2 + k - 1; while j > i2 do begin f := f * j; dec(j); end; s := s + brn[i] * f * isgn; s := s / x2; end; pxk := 1; for i := 0 to k - 1 do begin s := s / x; pxk := pxk * x; end; s := s + pk * 1 / (2 * pxk * x) * isgn; f := pk / k; s := s + f / pxk * isgn; end; end else begin // x < sly n := trunc(slv - x); y := n + x + 1; s := polygamma_brn(k, y, INFF); for i := 0 to n do begin y := y - 1; if abs(y) < 1e-3 then begin if x > 0 then y := x - trunc(x + 0.5) else y := x - trunc(x - 0.5); end; pxk := 1; for j := 0 to k - 1 do pxk := pxk * y; s := s + isgn * pk / pxk / y; end; end; result := s; end; //========================================================================================= procedure TForm1.BitBtn1Click(Sender: TObject); var sc : double; n, ch : integer; z, po : extended; min, max, dx, zx: double; dans : double; x, ans: mp_float; maxmp, absans : mp_float; k : mp_int; INFF : integer; begin val(LabeledEdit1.Text, n, Ch); if ch <> 0 then begin MessageDlg('種類に間違いがあります。', mtInformation, [mbOk], 0, mbOk); exit; end; if n < 0 then begin MessageDlg('負数のポリガンマ種別は計算出来ません', mtInformation, [mbOk], 0, mbOk); exit; end; val(LabeledEdit2.Text, z, Ch); if ch <> 0 then begin MessageDlg('値zに間違いがあります。', mtInformation, [mbOk], 0, mbOk); exit; end; if z > 1e100 then begin MessageDlg('値zが大きすぎます。', mtInformation, [mbOk], 0, mbOk); exit; end; if z < -1e10 then begin MessageDlg('値zが小さすぎます。', mtInformation, [mbOk], 0, mbOk); exit; end; bitbtn1.Enabled := False; // 多倍長初期化------ mpf_init2(x, ans); mp_init(k); mpf_init2(maxmp, absans); //------------------- memo1.Clear; Series1.Clear; Series2.Clear; {$IFDEF CPUX64} // プラットフォーム64ビット begin // double 計算精度15桁 memo1.Lines.Add('CPUX64モード'); end; {$ENDIF CPUX64} {$IFDEF CPUX86} // プラットフォーム32ビット begin // extended 計算精度19桁 memo1.Lines.Add('CPUX86モード'); end; {$ENDIF CPUX86} po := polygamma_brn(n, z, INFF); memo1.Lines.Add('漸近展開'); memo1.Lines.Add('slv = ' + floatTostr(slva)); if INFF = 2 then memo1.Lines.Add('Ψ' + intTostr(n) + '入力エラー'); // if INFF = 1 then memo1.Lines.Add('Ψ' + intTostr(n) + '= ∞'); // if INFF = -1 then memo1.Lines.Add('Ψ' + intTostr(n) + '= -∞'); // if INFF = 0 then memo1.Lines.Add('Ψ' + intTostr(n) + '= ' + floatTostrF(po, ffgeneral, 18, 3)); // グラフスケール設定とグラフ作図 sc := 1e3; case n of 0 : sc := 1e3; 1 : sc := 1e4; 2 : sc := 1e5; 3 : sc := 1e8; 4 : sc := 1e10; end; if po > sc then po := sc; if po < -sc then po := -sc; Series2.AddXY(z, po); min := z - 1; if min <= 0 then if z > 0 then min := z; if min > 0 then case n of 0 : min := 0.1; 1 : min := 0.2; 2 : min := 0.3; 3 : min := 0.4; 4 : min := 0.5; end; if abs(z) < min then min := z; if z > 0.7 then min := Z - 0.5; if z >= 3 then min := z - 2; max := z + 2; dx := (max - min) / 200; for ch := 0 to 200 do begin zx := min + ch * dx; if zx <= 0 + dx then if abs(zx - trunc(zx)) < dx / 2 then if zx - trunc(zx) < 0 then zx := trunc(zx) - dx / 2 else zx := trunc(zx) + dx / 2; po := polygamma_brn(n, zx, INFF); if po > sc then po := sc; if po < -sc then po := -sc; Series1.AddXY(zx, po); end; // 多倍長text -> 数値 mp_read_decimal(k, pansichar(ansistring(Form1.LabeledEdit1.Text))); mpf_read_decimal(x, pansichar(ansistring(Form1.LabeledEdit2.Text))); // 多倍長ポリガンマ polygamma_mp(n, x, ans, INFF); memo1.Lines.Add('多倍長計算'); memo1.Lines.Add('slvmp = ' + floatTostr(slvamp)); if INFF = 2 then memo1.Lines.Add('Ψ' + intTostr(n) + 'ポリガンマ種別<0'); if INFF = 1 then memo1.Lines.Add('Ψ' + intTostr(n) + '= ∞'); if INFF = -1 then memo1.Lines.Add('Ψ' + intTostr(n) + '= -∞'); if INFF = 0 then memo1.Lines.Add('Ψ' + intTostr(n) + '= '+ string(mpf_decimal_alt(ans, 52))); if INFF <> 2 then begin dans := mpf_todouble(ans); memo1.Lines.Add('多倍長→Double変換'); memo1.Lines.Append('Ψ' + intTostr(n) + '= '+ FloatTostrF(dans, ffgeneral, 18, 3)); end; // 多倍長開放---- mpf_clear2(x, ans); mp_clear(k); mpf_clear2(maxmp, absans); //--------------- bitbtn1.Enabled := True; end; end.
次のプログラムは、C言語辞典 アルゴリズム一覧からダウンロードした、c言語のポリガンマ関数をDelphiに変換したものです。
こちらのc言語のものは、RAD Studioその他のコンソールアプリケーションに読み込んで、コンパイルすれば、コマンドラインから修正せずに実行可能です。
プログラムは、ディガンマ関数と、トリ、テトラ、ペンタ、ヘキサ ガンマ関数の二つに完全に分かれています。
非常に簡単なプログラムになっていますが、精度は、上記の通常精度ものと殆ど変わりません。
元々のC言語のプログラムに近いルーチンと、ベルヌーイ数の数を変更できるルーチンのプログラムを作成しました。
ベルヌーイ数が変更できるルーチンの方は、一応extended有効桁数20桁にあわせてありますが、計算精度としては19桁程度になります。
ベルヌーイ数の数を増やした場合、同じ演算精度で良い場合は、補正値を小さくします。
mの値がベルヌーイ数の数となっていますが、この値を小さくして、演算精度にあわせます。
(k ポリガンマ種別)
上式は、前記のプログラムに使われている計算式で、15桁の精度でポリガンマの値を計算するものです。
これで計算して、その値をmの値に設定すれば、必要な精度の計算が出来るでしょう。
計算値に完全に合わせる必要はなく、多少大きめに設定しても問題はありません。
下記のプログラムは簡単なので、多倍長演算が出来るようにするのも簡単かと思います。
//----------------------------------------------------------------------- // http://chaste.web.fc2.com/Reference.files/Algo.html // Copyright (C) 2008-2015 Hidekazu Ito, Some rights reserved. // C のプログラムをdelphiに変換 //----------------------------------------------------------------------- unit main; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, system.Math, Vcl.Buttons, Vcl.StdCtrls, Vcl.ExtCtrls, VclTee.TeeGDIPlus, VCLTee.Series, VCLTee.TeEngine, VCLTee.TeeProcs, VCLTee.Chart, system.UITypes; type TForm1 = class(TForm) Chart1: TChart; Series1: TLineSeries; Series2: TPointSeries; BitBtn1: TBitBtn; Memo1: TMemo; LabeledEdit2: TLabeledEdit; LabeledEdit1: TLabeledEdit; CheckBox1: TCheckBox; procedure BitBtn1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} const //ベルヌーイ数B(2)、B(4)、B(6)、...、B(20) B : array[1..10] of extended = ( 1.0 / 6, // B(2) -1.0 / 30, 1.0 / 42, -1.0 / 30, 5.0 / 66, -691.0 / 2730, 7.0 / 6, -3617.0 / 510, 43867.0 / 798, -174611.0 / 330); // B(20) m: integer = sizeof(B) div sizeof(extended); //--------------------------------------------------------------------- function psia(x: extended): extended; var k : integer; v, w, sum : extended; begin if x <= 0 then begin k := round(x); if k = x then begin if k mod 2 = 0 then result := infinity else result := -infinity; exit; end; end; v := 0; repeat v := v + 1 / x; x := x + 1; until x >= m; w := 1 / (x * x); sum := 0; for k := m downto 1 do begin sum := (sum + B[k] / (k * 2)) * w; end; v := v + sum + 0.5 / x; result := ln(x) - v; end; function polygammaa(n: integer; x: extended): extended; var k, k2 : integer; t, u, v, w : extended; begin if x <= 0 then begin k := round(x); if k = x then begin if n mod 2 = 0 then begin if k mod 2 = 0 then result := infinity else result := -infinity; end else result := infinity; exit; end; end; u := 1; for k := 1 - n to -1 do u := u * k; v := 0; repeat v := v + 1 / power(x, n + 1); x := x + 1; until x >= m ; w := x * x; t := 0; for k := m downto 1 do begin k2 := k * 2; t := (t + B[k]) * (n + k2 - 1) * (n + k2 - 2) / (k2 * (k2 - 1) * w); end; t := t + 0.5 * n / x + 1; result := u * (t / power(x, n) + n * v); end; //--------------------------------------------------------------------- //--------------------------------------------------------------------- function psi(x: extended): extended; var k : integer; v, w : extended; begin if x <= 0 then begin k := round(x); if k = x then begin if k mod 2 = 0 then result := infinity else result := -infinity; exit; end; end; v := 0; repeat v := v + 1 / x; x := x + 1; until x >= 8; w := 1 / (x * x); v := v + ((((((( (B[8] / 16) * w + (B[7] / 14)) * w + (B[6] / 12)) * w + (B[5] / 10)) * w + (B[4] / 8)) * w + (B[3] / 6)) * w + (B[2] / 4)) * w + (B[1] / 2)) * w + 0.5 / x; result := ln(x) - v; end; function polygamma(n: integer; x: extended): extended; var k : integer; t, u, v, w : extended; begin if x <= 0 then begin k := round(x); if k = x then begin if n mod 2 = 0 then begin if k mod 2 = 0 then result := infinity else result := -infinity; end else result := infinity; exit; end; end; u := 1; for k := 1 - n to -1 do u := u * k; v := 0; repeat v := v + 1 / power(x, n + 1); x := x + 1; until x >= 8; w := x * x; t := (((((((B[8] * (n + 15.0) * (n + 14) / (16 * 15 * w) + B[7]) * (n + 13.0) * (n + 12) / (14 * 13 * w) + B[6]) * (n + 11.0) * (n + 10) / (12 * 11 * w) + B[5]) * (n + 9.0) * (n + 8) / (10 * 9 * w) + B[4]) * (n + 7.0) * (n + 6) / ( 8 * 7 * w) + B[3]) * (n + 5.0) * (n + 4) / ( 6 * 5 * w) + B[2]) * (n + 3.0) * (n + 2) / ( 4 * 3 * w) + B[1]) * (n + 1.0) * n / ( 2 * w) + 0.5 * n / x + 1; result := u * (t / power(x, n) + n * v); end; //--------------------------------------------------------------------- procedure TForm1.BitBtn1Click(Sender: TObject); var n, ch : integer; x, po : extended; min, max, dx, zx, sc: extended; begin val(LabeledEdit1.Text, n, Ch); if ch <> 0 then begin MessageDlg('種類に間違いがあります。', mtInformation, [mbOk], 0, mbOk); exit; end; if n < 0 then begin MessageDlg('負数のポリガンマは計算出来ません', mtInformation, [mbOk], 0, mbOk); exit; end; val(LabeledEdit2.Text, x, Ch); if ch <> 0 then begin MessageDlg('値zに間違いがあります。', mtInformation, [mbOk], 0, mbOk); exit; end; bitbtn1.Enabled := False; memo1.Clear; Series1.Clear; Series2.Clear; {$IFDEF CPUX64} // プラットフォーム64ビット begin // double 計算精度15桁 memo1.Lines.Add('CPUX64モード'); end; {$ENDIF CPUX64} {$IFDEF CPUX86} // プラットフォーム32ビット begin // extended 計算精度19桁 memo1.Lines.Add('CPUX86モード'); end; {$ENDIF CPUX86} if n = 0 then begin if checkbox1.Checked = false then po := psi(x) else po := psia(x); end else begin if checkbox1.Checked = false then po := polygamma(n, x) else po := polygammaa(n, x); end; memo1.Lines.Add('Ψ' + intTostr(n) + '= ' + floatTostrF(po, ffgeneral, 18, 3)); // グラフスケール設定とグラフ作図 sc := 1e3; case n of 0 : sc := 1e3; 1 : sc := 1e4; 2 : sc := 1e5; 3 : sc := 1e8; 4 : sc := 1e10; end; if po > sc then po := sc; if po < -sc then po := -sc; Series2.AddXY(x, po); min := x - 1; if min <= 0 then if x > 0 then min := x; if min > 0 then case n of 0 : min := 0.1; 1 : min := 0.2; 2 : min := 0.3; 3 : min := 0.4; 4 : min := 0.5; end; if abs(x) < min then min := x; if x > 0.7 then min := x - 0.5; if x >= 3 then min := x - 2; max := x + 2; dx := (max - min) / 200; for ch := 0 to 200 do begin zx := min + ch * dx; if zx <= 0 + dx then if abs(zx - trunc(zx)) < dx / 2 then if zx - trunc(zx) < 0 then zx := trunc(zx) - dx / 2 else zx := trunc(zx) + dx / 2; if n = 0 then po := psi(zx) else po := polygamma(n, zx); if po > sc then po := sc; if po < -sc then po := -sc; Series1.AddXY(zx, po); end; bitbtn1.Enabled := True; end; end.