見出し画像

数学の小ネタ#14 数値計算の加速法(サンプルプログラム付き)

 無限級数の計算には、多くの繰り返し計算が必要ですが、級数の性質によってはなかなか収束しない厄介な級数も存在します。その中でも交代級数と呼ばれる足し算と引き算が繰り返される級数は、収束が遅いことで知られています。有名な交代級数に円周率の1/4を計算するライプニッツの公式があります。この公式は以下のように、奇数の分母を持つ有理数の足し算と引き算で構成されています。

画像1

 項数を増やして、この式を素直に計算しても、正解には辿り着きません。例えば1000項まで計算しても、有効数字2桁程度までしか一致しません。こんな時に使われるのが、級数の加速法です。収束を加速する方法には何種類かありますが、ここではオイラー法を紹介します。

 オイラー法の仕組みを言葉で説明するのは難しいのですが、ざっくり言えば、二項展開を利用して元の式を変形して、収束しやすい形にする方法です。詳しいことが知りたい人は、色んな所で紹介されていると思うので、ググってみて下さい。キチンとした専門書で知りたい方には、下記の書籍がお薦めです。

 今回は初の試みとして、参考プログラムを乗せてみました。私はオールドプログラマーなので、数値計算には未だにFortranを使っています。言い訳のように聞こえるかもしれませんが、Fortranしか知らないのではなく、C、BASIC、Pascal、Pythonも使えるうえで、敢えてFortranを使っています^^。

 下記のプログラムは、ライプニッツの公式を使ったπの計算プログラムです。サンプルでは項数を30にしていますが、これで小数点以下14桁ぐらいまで正確に計算できます。これ以上の精度が必要であれば、倍精度ではなく4倍精度を使う必要があります。

!-----------------------------------------
!  Test program for Euler transformation
!  to calculate the value of Pi
!-----------------------------------------
program euler_pi
  Implicit None
  Integer, Parameter :: n=30 
  Integer :: i     
  Real(kind(0d0)) :: fpi, sum1, sum2
  Real(kind(0d0)) :: term, wksp(n)
!
   sum1 = 0.0d0
    Do i = 1, n
      sum1 = sum1 + fpi(i-1)
    End Do
    sum1=4.0d0*sum1
!
    sum2 = 0.0d0
    Do i = 1, n
      term = fpi(i-1)
      Call eulsum(sum2, term, i, wksp)
    End Do
    sum2=4.0d0*sum2
!
    Write (6, *) ' N =', n 
    Write (6, *) ' sum1 =', sum1
    Write (6, *) ' sum2 =', sum2
!
End Program euler_pi
!
function fpi(n)
  implicit none
  integer :: n
  real(kind(0d0)) :: fpi
  fpi=(1.0d0/(2.0d0*n+1.0d0))*(-1.0d0)**n
end function fpi  
!-------------------------
!  Euler transformation
!-------------------------
Subroutine eulsum(ssum, term, jterm, wksp)
  Implicit None
  Integer :: jterm
  Real(kind(0d0)) :: ssum, term, wksp(jterm)
  Integer :: j, nterm
  Real(kind(0d0)) ::  dum, tmp
  Save nterm
!
  If (jterm==1) Then
    nterm = 1
    wksp(1) = term
    ssum = 0.5d0*term
  Else
    tmp = wksp(1)
    wksp(1) = term
    Do j = 1, nterm - 1
      dum = wksp(j+1)
      wksp(j+1) = 0.5d0*(wksp(j)+tmp)
      tmp = dum
    End Do
    wksp(nterm+1) = 0.5d0*(wksp(nterm)+tmp)
    If (abs(wksp(nterm+1))<=abs(wksp(nterm))) Then
      ssum = ssum + 0.5d0*wksp(nterm+1)
      nterm = nterm + 1
    Else
      ssum = ssum + wksp(nterm+1)
    End If
  End If
!
End Subroutine eulsum


いいなと思ったら応援しよう!