Up

正規分布の確率計算

C++/Linuxプログラミング

 

 標準正規分布

の累積確率分布

を求めるプログラムと、 が与えられたときに(2)式において対応する  を求めるプログラムC++/Linuxで作成した。

 

式(2)において  が与えられたときに対応する  を求めるプログラム

 このプログラムは、式(2)における積分をガウス・ルジャンドルの積分公式を用いた適応的方法(岡本安晴(2009統計学数学入門培風館pp. 117-118)によって行うものである。ソースリストをリスト1に示す。

================== リスト1 CalcNormalCum.cpp ============================

#include <iostream>

#include <iomanip>

#include <cmath>

#include "CalcIntgrt.h"                   //            積分用クラス型 AdaptiveGL の宣言

 

using namespace std;

using namespace myLib;

 

double knormal( double z ){

              return exp(-0.5 * z * z);

}

 

double Cum_Normal( double z ) {

              double p;

              double pi = atan(1.0) * 4.0;

              if (z == 0.0){

                            p = 0.5;

              }else if(z > 0.0){

                            AdaptiveGL agl;                //            積分用クラス型 AdaptiveGL のオブジェクト agl

                            p = 0.5 + agl.integral( 0.0, z, knormal ) / sqrt(2.0 * pi);

              } else {

                            AdaptiveGL agl;

                            p = 0.5 - agl.integral( z, 0.0, knormal ) / sqrt(2.0 * pi);

              }

 

              return p;

}

 

int main(){

              while(true){

                            cout << "z = ";

                            double z;

                            cin >> z;

                            if(cin.fail()) break;

                            double p = Cum_Normal( z );         //            p = Prob(X < z),  X ~ standard normal distribution

                            cout << "p = " << fixed << setprecision(9) << p << endl;

              }

 

              return 0;

}

====================================================

積分をクラス型AdaptiveGLのオブジェクトaglによって行っている。このクラス型AdaptiveGLはリスト2、リスト3に示すヘッダーファイルとソースコードにより宣言・定義されている。

================== リスト2 CalcIntgrt.h =======================

#ifndef CALCINTGRL_CK

#define CALCINTGRL_CK

 

namespace myLib {

              //

              //            ガウス・ルジャンドルの積分公式による適応的方法

              //            岡本安晴(2009)「統計学を学ぶための数学入門[]」培風館、pp. 117-118.

              //

              class AdaptiveGL {

                            public:

                                          double (*f)(double);

                                          AdaptiveGL(){}

                                          virtual ~AdaptiveGL(){}

 

                                          double integral( double a, double b, double (*func)(double));

                                          void   calcIntegral( double & s0, double a, double b );

                                          double Gauss_Legendre( double a, double b );

              };

 

} 

 

#endif

==================================================

 

================== リスト3 CalcIntgrt.cpp ===================

#include "CalcIntgrt.h"

#include <cmath>

 

using namespace std;

using namespace myLib;

 

namespace myLib {

 

              //

              //            ガウス・ルジャンドルの積分公式による適応的方法

              //            岡本安晴(2009)「統計学を学ぶための数学入門[]」培風館、pp. 117-118.

              //

              double AdaptiveGL::integral(double a, double b, double (*func)(double)){

                            f = func;

                            double v = 0.0;

                            calcIntegral( v, a, b );

                            return v;

              }

 

 

              void AdaptiveGL::calcIntegral(double & s0, double a, double b){

                            double s1 = Gauss_Legendre( a, 0.5*(a + b) );

                            double s2 = Gauss_Legendre( 0.5*(a + b), b );

                            if (((abs( s0 - s1 - s2 ) < (1.0e-14)*abs(s1 + s2))

                                          ||

                                          ((abs(s0) + abs(s1 + s2)) < 1.0e-14))

                                          &&

                                          (abs(b - a) < 1.0)){

                                                        s0 = s1 + s2;

                                                        return;

                            }

                            else{

                                                        calcIntegral( s1, a, 0.5*(a + b) );

                                                        calcIntegral( s2, 0.5*(a + b), b );

                                                        s0 = s1 + s2;

                                                        return;

                            }

              }

 

 

              double AdaptiveGL::Gauss_Legendre(double a, double b){

                            double x0[9] = {

                                                     -9.68160239507626090E-0001,

                                                        -8.36031107326635794E-0001,

                                                        -6.13371432700590397E-0001,

                                                        -3.24253423403808929E-0001,

                                                         0.00000000000000000E+0000,

                                                         3.24253423403808929E-0001,

                                                         6.13371432700590397E-0001,

                                                         8.36031107326635794E-0001,

                                                         9.68160239507626090E-0001

                                                        };

                            double w[9] = {

                                                        8.12743883615744120E-0002,

                                                        1.80648160694857404E-0001,

                                                        2.60610696402935462E-0001,

                                                        3.12347077040002840E-0001,

                                                        3.30239355001259763E-0001,

                                                        3.12347077040002840E-0001,

                                                        2.60610696402935462E-0001,

                                                        1.80648160694857404E-0001,

                                                        8.12743883615744120E-0002 

                                                        };

 

                            double v1 = 0.0;

                            double i_w = b - a;

                            double xi;

                            for (int i = 0; i < 9; i++){

                                          xi = (0.5 * i_w * x0[i]) + (0.5 * (b + a));

                                          v1 += w[i] * f(xi);

                            }

                            v1 = 0.5 * i_w * v1;

                            return v1;

              }

 

} 

=======================================================

 

 プログラムを実行すると標準入力ストリームcinから

                            cin >> z;

zの値が読み込まれるが、数値として不適切な入力(英字など)があるとcin.fail()trueを返すので、次のif

                            if(cin.fail()) break;

によって、while文を抜け出し、プログラムの終了となる。

 プログラムの実行例を図1に示す。

図1

 

zの値として英字qが入力されたとき、プログラムが終了していることがわかる。

 プログラムのソースファイルを圧縮ファイルPrgNormalCum.zipとしてまとめた。ファイル名PrgNormalCum.zipをクリックしてダウンロードすることができる。

 

 

式(2)において  が与えられたときに対応する  を求めるプログラム

 次式

を満たす  に対する  をニュートン法(岡本安晴(2009統計学数学入門培風館pp. 82-83)によって求める。ニュートン法が収束しないときは、Bisection法に切り替える。プログラムをリスト4に示す。

=================== リスト4 calcz.cpp ==============================

#include <iostream>

#include <iomanip>

#include <cmath>

#include "calcNormal.h"                  //            ニュートン法の関数 Newton の宣言

 

using namespace std;

using namespace calcnormal;

 

 

double dcalc_p_normal( double z ){

              double pi = atan(1.0) * 4.0;

              double v = exp(-0.5 * z * z) / sqrt(2.0 * pi);

              return v;

}

 

double calc_p_normal( double z ){

              double p = Cum_Normal( z );

              return p;

}

 

double calc_z_normal( double p ){

              if ((p <= 0.0) || (p >= 1.0)) {

                         throw "Invalid value for p.";

              }

              double z = 0.0;

              Newton( z, p, calc_p_normal, dcalc_p_normal, 1.0e-12 );                      //            ニュートン法による根の計算

              return z;              //            p = Prob(X < z),  X ~ standard normal distribution

}

 

int main(){

              while(true){

                            cout << "p = ";

                            double p;

                            cin >> p;

                            if(cin.fail()) break;

                            try{       

                                          //            p = Prob(X < z),  X ~ standard normal distribution

                                          double z = calc_z_normal( p );

                                          cout << "z = " << setprecision(9) << z << endl;

                            }

                            catch(const char * ce){

                                          cout << ce << endl;

                                          continue;

                            }

              }

             

              return 0;

}

=====================================================

 p の入力として英字など不適当な入力があったときは、cin.fail()trueを返すので、if

                            if(cin.fail()) break;

により、while文の外に出て、プログラムの実行終了となる。

 p に設定された値が確率として不適切な値のときは、関数 calc_z_normal において例外がthrowされ、main関数におけるcatchでの例外処理によってcontinue文が呼び出されて、while文の実行が次のループに移る。

 ニュートン法はリスト5およびリスト6に示すコードによって行われている。

================== リスト5 calcNormal.h ======================

#ifndef CALCNORMAL_CK

#define CALCNORMAL_CK

 

namespace calcnormal {

 

              double Cum_Normal( double z );

             

              //

              //     Newton法による根の計算  

              //

              void Newton(      double & x,                        //  根の初期値、戻り値は根 f(x)=c

                                          double c,                            //  関数値

                                          double (*f)( double ),         //  関数

                                          double (*df)( double ),       //  導関数

                                          double acc );

}

 

#endif

====================================================

 

================== リスト6 calcNormal.cpp ====================

#include <cmath>

#include "CalcIntgrt.h"                   //            ガウス・ルジャンドル積分公式用クラス型 AdaptiveGL の宣言

#include "calcNormal.h"

 

namespace calcnormal {

 

              using namespace std;

              using namespace myLib;

              using namespace calcnormal;

 

              double knormal( double z ){

                            return exp(-0.5 * z * z);

              }

 

              double Cum_Normal( double z ) {

                            double p;

                            double pi = atan(1.0) * 4.0;

                            if (z == 0.0){

                                          p = 0.5;

                            }else if(z > 0.0){

                                          AdaptiveGL agl;                //            積分用クラス型 AdaptiveGL のオブジェクト agl

                                          p = 0.5 + agl.integral( 0.0, z, knormal ) / sqrt(2.0 * pi);

                            } else {

                                          AdaptiveGL agl;

                                          p = 0.5 - agl.integral( z, 0.0, knormal ) / sqrt(2.0 * pi);

                            }

 

                            return p;              //            p = Prob(X < z),  X ~ standard normal distribution

              }

 

 

              //

              //     Bisection法による根の計算   

              //

              void Bisection( double (*f)(double),

                                          double s,                                          //   fが増加関数のとき s > 0.0

                                          double c,                                          //   根に対する関数値

                                          double L_b, double U_b,                 //   根の存在区間の下限と上限

                                          double & Root,                                //     f(Root) = c

                                          double acc ){

                            if ( (f(L_b) - c) * (f(U_b) - c) > 0.0 ){

                                          throw  "上限と下限の組み合わせが不適切です。";

                            }

                            do {

                                          double m = 0.5 * (L_b + U_b);

                                          double v = f(m);

                                          if ( s * (v - c) > 0.0 ) {

                                                        U_b = m;

                                          } else {

                                                        L_b = m;

                                          }

                            } while ( (abs(U_b - L_b) >= acc * abs(U_b))

                                                                      &&

                                            ( (abs(U_b) + abs(L_b)) >= 1.0e-14)         );

                            Root = 0.5 * (L_b + U_b);

              }

 

              //

              //     Newton法による根の計算  

              //

              void Newton(      double & x,                        //  根の初期値、戻り値は根 f(x)=c

                                          double c,                            //  関数値

                                          double (*f)( double ),         //  関数

                                          double (*df)( double ),       //  導関数

                                          double acc ){

                             int NStep = 0;

                             int ck = 0;

                             double x0;

                             do {

                                           NStep++;

                                           if (NStep > 20) {              //  20回以上繰り返しのとき

                                                         ck = 1;

                                                         break;

                                           };

                                           x0 = x;

                                           x = x0 + (c - f(x0)) / df(x0);

                             } while ( (abs(x - x0) >= (acc * (abs(x) + abs(x0))))

                                                                       &&

                                                       ( (abs(x) + abs(x0)) >= 1.0e-14 )  );

 

                             if (ck == 0) return;

 

              //               Bisection法で計算

 

                             double L_b = x;

                             do {

                                                        L_b -= 1.0;

                             } while ( f(L_b) >= c );

                             double U_b = x;

                             do {

                                                        U_b += 1.0;

                             } while ( f(U_b) <= c );

                             Bisection( f, 1.0, c, L_b, U_b, x, acc );

                             return;

              }

}

==================================================

 

 積分のためのクラス型を宣言・定義しているソースコードファイルは、先にリスト2およびリスト3に示したものである。

 プログラムの実行例を図2に示す。

図2

 

 不適切な範囲のpの値「p = 9」が入力されたときは例外の処理が行われていること、pに英字qが入力されたときはプログラムの実行が終了していることがわかる。

 プログラムファイルは圧縮ファイルPrgNormalDev.zipにまとめた。ファイル名PrgNormalDev.zipをクリックしてダウンロードすることができる。

 

Up