tangent function
tangent(タンジェント) 関数は、sin、cosと違って、マクローリン級数でベルヌーイ数を使用し計算が複雑になるので、tan(z)
=sin(z)/cos(z)で計算していますが、一応プログラムを作成してみました。
sin或いはcosが計算できれば、tanの計算が出来ますが、角度が0°か90°に近いとき誤差が大きくなる様です。
プログラムは、多倍長演算で作成しました。
理由は、有効桁巣数が十数桁でも、ベルヌーイ数の値が非常に大きくなる為です。
上記の計算式で角度zが、π/2の値に近づくと、演算のループ数がどんどん増え、ベルヌーイ数が非常に大きくなり、異常に演算時間が増えます。
そこで、45°超えたら角度を対角のπ/2-zとして、答えを逆数とにして、ベルヌーイ数が大きくなるのを防止しています。
それでも、有効桁数、数百桁の計算をすると、分単位の時間がかかります。
tan=sin/cosで計算すると数秒で計算できるので、ベルヌーイ数を使用した計算は実用にはなりません。
有効桁数が15~20桁程度に固定されているのであれば、予め、ベルヌーイ数のテーブルを作成して置けば演算時間を早く出来ます。
プログラムの中で、ベルヌーイ数の計算で最大公約数を求める計算がありますが、省略しても1割程度しか早くなりません。
プログラム
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, system.Diagnostics, Vcl.ExtCtrls, system.UITypes; type TForm1 = class(TForm) Memo1: TMemo; Edit1: TEdit; tanX: TBitBtn; LabeledEdit1: TLabeledEdit; Clearbtn: TBitBtn; LabeledEdit2: TLabeledEdit; procedure tanXClick(Sender: TObject); procedure ClearbtnClick(Sender: TObject); private { Private 宣言 } function incheck(s, m: string): boolean; public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} uses Velthuis.BigIntegers, Velthuis.Bigdecimals; var t : array of biginteger; // πの値 function big_pi(pcs: integer): Bigdecimal; var n, dpcs, pcsBack : integer; rmback : BigDecimal.RoundingMode; SQRT_SQRT_EPSILON, c, b, e : BigDecimal; npow : Bigdecimal; a, one, two, four, five, eight : Bigdecimal; begin pcsBack := BigDecimal.DefaultPrecision; // 除算演算精度バックアップ rmBack := BigDecimal.DefaultRoundingMode; // 除算丸めモードバックアップ dpcs := pcs + 5; // 指定精度 + α BigDecimal.DefaultPrecision := dpcs; BigDecimal.DefaultRoundingMode := rmNearesteven; one := '1'; one := one.RoundToPrecision(dpcs); // oneの有効桁数をdpcsに設定 two := '2'; four := '4'; five := '5'; eight := '8'; SQRT_SQRT_EPSILON := one / BigDecimal.IntPower(two, pcs shl 0); // 収束判定値 c := BigDecimal.Sqrt(one / eight, dpcs * 2); a := one + c + c + c; b := BigDecimal.Sqrt(a, dpcs * 2); e := b - five / eight; b := b + b; c := e - c; a := a + e; npow := '4'; n := 0; while e > SQRT_SQRT_EPSILON do begin npow := npow + npow; e := (a + b) / two; // 平均値 除算有効桁数での丸め b := a * b; b := b.RoundToPrecision(dpcs); // pcs + α 丸め b := BigDecimal.Sqrt(b, dpcs * 2); // 平方根有効桁数での丸め e := e - b; b := b + b; c := c - e; a := e + b; inc(n); end; e := e * e; e := e.RoundToPrecision(dpcs); // pcs + α 丸め e := e / four; // e = e + e / 4 a := a + b; result := (a * a - e - e / two) / (a * c - e) / npow; // 除算の順番に注意 result := result.RoundToPrecision(pcs); // 指定の精度に丸め BigDecimal.DefaultPrecision := pcsBack; // 除算演算精度復帰 BigDecimal.DefaultRoundingMode := rmBack; // 除算丸めモード復帰 // Form1.Memo1.Lines.Append('T.Ooura Big n = ' + intTostr(n)); end; // tan(x) // 級数展開による計算 ベルヌーイ数使用 // -π/2 < x < π/2 function tan_Big(x: bigdecimal): bigdecimal; var // t : array of biginteger; // k, nr, dpcs : integer; n, k, nr, dpcs, dpcsb: integer; b1, b2, q, g : biginteger; a2, c, d, dd, e, one, two : biginteger; s, sb, z, zz, az, m, seven : Bigdecimal; mf, pib, pi2, pis2, onef, x9, pi9: bigdecimal; flg : boolean; // ベルヌーイ値表示 procedure memoout; var bstr : string; begin if b1 < 0 then bstr := ' = ' else bstr := ' = '; Form1.memo1.Lines.Append('B' + intTostr(n) + bstr + b1.ToString +' / ' + b2.ToString); end; // 最大公約数 ユークリッドの互除法 function gcd_int(x, y: biginteger): biginteger; var t : biginteger; begin while y <> 0 do begin t := x mod y; x := y; y := t; end; result := x; end; // Tnの値をBn値に変換表示 procedure TntoBn; begin b1 := n * t[0]; // 分子 n * Tn q := q shl 2; // 分母 2^n q = q * 4 b2 := q * (q - one); // 〃 4^n - 2^n g := gcd_int(b1, b2); // 最大公約数 b1 := b1 div g; b2 := b2 div g; if n mod 4 = 0 then b1 := - b1; // 符号設定 // memoout; // 値表示 end; // kによるベルヌーイ数計算 k = 2~ procedure Bernoulli_number; var j: integer; begin // Seidel's algorithm for Tn Bn値をt[0]としたベルヌーイ計算ループ B2~ setlength(t, (k - 1) * 2 - 1); // 配列確保 追加配列クリア if k > 2 then begin for j := 0 to n - 2 do t[j + 1] := t[j] + t[j + 1]; // -> t[n] := t[n - 1]; for j := n - 2 downto 0 do t[j + 1] := t[j] + t[j + 2]; // <- t[0] := t[1]; end else begin t[0] := '1'; // 分子係数初期値 q := '1'; // 分母係数初期値 end ; n := k * 2 - 2; // nの値設定 TntoBn; end; begin dpcsb := Bigdecimal.DefaultPrecision; dpcs := dpcsb + 5; Bigdecimal.DefaultPrecision := dpcs; seven := '0.786'; // >45°π/4=0.78539816 one := '1'; two := '2'; pib := big_pi(dpcs); pi2 := pib + pib; pis2 := pib / Two; repeat // 0~2π(0~360°)の範囲に修正 if x < 0 then x := x + pi2; if x >= pi2 then x := x - pi2; until (x < pi2) and (x >= 0); flg := true; // 正数フラグセット if (x > pis2) and (x < pib) then begin // π2~π 90°~180° x := pib - x; // 0~π/2(0~90°)の範囲に修正 flg := false // 負数フラグ設定 end; if (x >= pib) and (x < pib + pis2) then // π~π+π/2 180°~270 x := x - pib; // 0~π/2(0~90°)の範囲に修正 if (x >= pib + pis2) and (x < pi2) then begin // π+π/2~2π 270°~360° x := pi2 - x; // 0~π/4(0~90°)の範囲に修正 flg := false // 負数フラグ設定 end; x9 := x.RoundToPrecision(dpcsb); // 角度指定の桁数に丸め pi9 := pis2.RoundToPrecision(dpcsb); // rad(90°)指定の桁数に丸め if x9 = pi9 then begin // 90°の場合 application.MessageBox('ゼロで除算、無効な値です','注意',0); result := 0; exit end; z := x; if x > seven then begin // 0.786(45°)を超えたら対角計算 z := pib / two - z; end; c := 1; d := 1; e := 1; dd := '4'; // 2^2 s := '0'; // Σ=0 az := z; // z zz := z * z; // z^2 nr := 1; // n = 1 ~ repeat sb := s; // 収束判定用 sb k := nr + 1; // 2~ ベルヌーイ数計算用 Bernoulli_number; // ベルヌーイ数計算B(2n) b1 / b2 a2 := nr * two; // 2n c := (a2 - one) * c * a2; // (2n)! d := -d * dd; // -1^n * 2^2n e := e * dd; // 2^2n m := (one - e) * d * b1; // 整数のみまとめて積算 mf := az * m; // 浮動小数点 * 整数 mf := mf.RoundToPrecision(dpcs); // 次の除算の為の有効桁数丸め a2 := c * b2; // 整数 * 整数 mf := mf / a2; // 浮動小数点 / 整数 s := s + mf; // Σ s := s.RoundToPrecision(dpcs); // 収束判定の為の有効桁数丸め az := az * zz; // z^2n-1 inc(nr); // n = n + 1 until sb = s; form1.Memo1.Lines.Append('Loop数' + intTostr(nr - 1)); // form1.Memo1.Lines.Append(s.ToPlainString); if x > seven then begin onef := '1'; onef := onef.RoundToPrecision(dpcs); s := onef / s; // 0.786(45°)を超えていたら対角 end; if not flg then s := -s; Bigdecimal.DefaultPrecision := dpcsb; s := s.RoundToPrecision(dpcsb); s := s.RemoveTrailingZeros(0); result := s; end; // 入力確認 function TForm1.incheck(s, m: string): boolean; const ec = 'e'; eo = 'E'; dt = '.'; var fs, es, ms : string; ch, p, po, leng : integer; a : double; begin result := false; p := pos(ec, s); // 'e' の位置 po := pos(eo, s); // 'E' の位置 p := p + po; if p = 0 then begin // 'e' 'E' が無かったら val(s, a, ch); if ch <> 0 then begin ms := m + ' 入力値に間違いがあります。'; application.MessageBox(pchar(ms),'注意',0); exit; end; result := true; exit; end; leng := length(s); fs := ''; fs := copy(s, 1, p - 1); // 'e'の前の文字取り出し es := ''; es := copy(s, p + 1, leng - p); // 'e'の後ろの文字取り出し val(fs, a, ch); if ch <> 0 then begin // 'e'の前の文字の確認 ms := m + ' 入力値に間違いがあります。'; application.MessageBox(pchar(ms),'注意',0); exit; end; if es <> '' then begin // 'e'の後ろの文字確認 p := pos(ec, es); po := pos(eo, es); p := p + po; po := pos(dt, es); p := p + po; if p <> 0 then begin // 'e','E' '.',が゛有ったら ms := m + ' 入力値に間違いがあります。'; application.MessageBox(pchar(ms),'注意',0); exit; end; val(es, a, ch); if ch <> 0 then begin ms := m + ' 入力値に間違いがあります。'; application.MessageBox(pchar(ms),'注意',0); exit; end; end; result := true; end; procedure TForm1.tanXClick(Sender: TObject); var deg, rad, ans, D180, dega : bigdecimal; dpcs, back, ch : integer; StopWatch : TStopwatch; ElapsedMillseconds : Int64; rmback : BigDecimal.RoundingMode; begin // 入力チェック if not incheck(LabeledEdit1.Text, '角度の') then exit; val(LabeledEdit2.Text, dpcs, ch); if ch <> 0 then begin application.MessageBox('有効桁数に間違いがあります', '注意', 0); exit; end; deg := LabeledEdit1.Text; dega := dega.Abs(deg); if dpcs > 1000 then begin application.MessageBox('有効桁数が大きすぎます。','注意',0); exit; end; if (dpcs >= 500) and (dega > 10) and (dega < 75) then begin if MessageDlg('有効桁数が大きく非常に時間が掛かりますこのまま続行しますか?', mtCustom, [mbOK,mbCancel], 0, mbCancel) = mrCancel then Exit; end; if (dpcs < 500) and (dpcs >= 300) and (dega > 10) and (dega < 75) then begin if MessageDlg('時間が掛かりますこのまま続行しますか?', mtCustom, [mbOK,mbCancel], 0, mbCancel) = mrCancel then Exit; end; Form1.Edit1.text := '計算中'; application.ProcessMessages; back := Bigdecimal.DefaultPrecision; // 有効桁数 rmBack := BigDecimal.DefaultRoundingMode; // 除算丸めモード Bigdecimal.DefaultPrecision := dpcs + 5; BigDecimal.DefaultRoundingMode := rmNearesteven; D180 := '180'; rad := deg * big_pi(dpcs + 5); rad := rad.RoundToPrecision(dpcs + 5); rad := rad / D180; StopWatch := TStopwatch.StartNew; // tan(x) Bigdecimal.DefaultPrecision := dpcs; ans := tan_big(rad); StopWatch.Stop; ElapsedMillseconds := StopWatch.ElapsedMilliseconds; Form1.Edit1.text := '時間 = ' + intTostr(ElapsedMillseconds) + 'msec'; // 答え表示 // rad := rad.RoundToPrecision(dpcs div 2); memo1.Lines.Append('角度deg =' + deg.ToPlainString); // memo1.Lines.Append('角度RAD =' + rad.ToPlainString); memo1.Lines.Append('tan(x) =' + ans.ToPlainString); Bigdecimal.DefaultPrecision := back; // 有効桁数戻し BigDecimal.DefaultRoundingMode := rmBack; // 除算丸めモード復帰 end; procedure TForm1.ClearbtnClick(Sender: TObject); begin memo1.Clear; end; end.
tan_x.zip
三角関数、逆三角関数、その他関数 に戻る