Cumulative Normal Distribution Function in C/C++

后端 未结 7 2176
走了就别回头了
走了就别回头了 2020-12-07 14:15

I was wondering if there were statistics functions built into math libraries that are part of the standard C++ libraries like cmath. If not, can you guys recommend a good st

相关标签:
7条回答
  • 2020-12-07 14:29

    Here's a stand-alone C++ implementation of the cumulative normal distribution in 14 lines of code.

    http://www.johndcook.com/cpp_phi.html

    #include <cmath>
    
    double phi(double x)
    {
        // constants
        double a1 =  0.254829592;
        double a2 = -0.284496736;
        double a3 =  1.421413741;
        double a4 = -1.453152027;
        double a5 =  1.061405429;
        double p  =  0.3275911;
    
        // Save the sign of x
        int sign = 1;
        if (x < 0)
            sign = -1;
        x = fabs(x)/sqrt(2.0);
    
        // A&S formula 7.1.26
        double t = 1.0/(1.0 + p*x);
        double y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*exp(-x*x);
    
        return 0.5*(1.0 + sign*y);
    }
    
    void testPhi()
    {
        // Select a few input values
        double x[] = 
        {
            -3, 
            -1, 
            0.0, 
            0.5, 
            2.1 
        };
    
        // Output computed by Mathematica
        // y = Phi[x]
        double y[] = 
        { 
            0.00134989803163, 
            0.158655253931, 
            0.5, 
            0.691462461274, 
            0.982135579437 
        };
    
            int numTests = sizeof(x)/sizeof(double);
    
        double maxError = 0.0;
        for (int i = 0; i < numTests; ++i)
        {
            double error = fabs(y[i] - phi(x[i]));
            if (error > maxError)
                maxError = error;
        }
    
            std::cout << "Maximum error: " << maxError << "\n";
    }
    
    0 讨论(0)
  • 2020-12-07 14:32

    I figured out how to do it using gsl, at the suggestion of the folks who answered before me, but then found a non-library solution (hopefully this helps many people out there who are looking for it like I was):

    #ifndef Pi 
    #define Pi 3.141592653589793238462643 
    #endif 
    
    double cnd_manual(double x)
    {
      double L, K, w ;
      /* constants */
      double const a1 = 0.31938153, a2 = -0.356563782, a3 = 1.781477937;
      double const a4 = -1.821255978, a5 = 1.330274429;
    
      L = fabs(x);
      K = 1.0 / (1.0 + 0.2316419 * L);
      w = 1.0 - 1.0 / sqrt(2 * Pi) * exp(-L *L / 2) * (a1 * K + a2 * K *K + a3 * pow(K,3) + a4 * pow(K,4) + a5 * pow(K,5));
    
      if (x < 0 ){
        w= 1.0 - w;
      }
      return w;
    }
    
    0 讨论(0)
  • 2020-12-07 14:34

    From https://en.cppreference.com/w/cpp/numeric/math/erfc

    Normal CDF can be calculated as below:

    #include <iostream>
    #include <cmath>
    #include <iomanip>
    using namespace std;
    
    double normalCDF(double x) // Phi(-∞, x) aka N(x)
    {
        return erfc(-x / sqrt(2))/2;
    }
    

    Using 2.0 instead of 2 in the denominator helps in getting decimals instead of integers.

    Hope that helps.

    0 讨论(0)
  • 2020-12-07 14:36

    The implementations of the normal CDF given here are single precision approximations that have had float replaced with double and hence are only accurate to 7 or 8 significant (decimal) figures.
    For a VB implementation of Hart's double precision approximation, see figure 2 of West's Better approximations to cumulative normal functions.

    Edit: My translation of West's implementation into C++:

    double
    phi(double x)
    {
      static const double RT2PI = sqrt(4.0*acos(0.0));
    
      static const double SPLIT = 7.07106781186547;
    
      static const double N0 = 220.206867912376;
      static const double N1 = 221.213596169931;
      static const double N2 = 112.079291497871;
      static const double N3 = 33.912866078383;
      static const double N4 = 6.37396220353165;
      static const double N5 = 0.700383064443688;
      static const double N6 = 3.52624965998911e-02;
      static const double M0 = 440.413735824752;
      static const double M1 = 793.826512519948;
      static const double M2 = 637.333633378831;
      static const double M3 = 296.564248779674;
      static const double M4 = 86.7807322029461;
      static const double M5 = 16.064177579207;
      static const double M6 = 1.75566716318264;
      static const double M7 = 8.83883476483184e-02;
    
      const double z = fabs(x);
      double c = 0.0;
    
      if(z<=37.0)
      {
        const double e = exp(-z*z/2.0);
        if(z<SPLIT)
        {
          const double n = (((((N6*z + N5)*z + N4)*z + N3)*z + N2)*z + N1)*z + N0;
          const double d = ((((((M7*z + M6)*z + M5)*z + M4)*z + M3)*z + M2)*z + M1)*z + M0;
          c = e*n/d;
        }
        else
        {
          const double f = z + 1.0/(z + 2.0/(z + 3.0/(z + 4.0/(z + 13.0/20.0))));
          c = e/(RT2PI*f);
        }
      }
      return x<=0.0 ? c : 1-c;
    }
    

    Note that I have rearranged expressions into the more familiar forms for series and continued fraction approximations. The last magic number in West's code is the square root of 2π, which I've deferred to the compiler on the first line by exploiting the identity acos(0) = ½ π.
    I've triple checked the magic numbers, but there's always the chance that I've mistyped something. If you spot a typo, please comment!

    The results for the test data John Cook used in his answer are

     x               phi                Mathematica
    -3     1.3498980316301150e-003    0.00134989803163
    -1     1.5865525393145702e-001    0.158655253931
     0     5.0000000000000000e-001    0.5
    0.5    6.9146246127401301e-001    0.691462461274
    2.1    9.8213557943718344e-001    0.982135579437
    

    I take some small comfort from the fact that they agree to all of the digits given for the Mathematica results.

    0 讨论(0)
  • 2020-12-07 14:39

    Boost is as good as the standard :D here you go: boost maths/statistical.

    0 讨论(0)
  • 2020-12-07 14:44

    From NVIDIA CUDA samples:

    static double CND(double d)
    {
        const double       A1 = 0.31938153;
        const double       A2 = -0.356563782;
        const double       A3 = 1.781477937;
        const double       A4 = -1.821255978;
        const double       A5 = 1.330274429;
        const double RSQRT2PI = 0.39894228040143267793994605993438;
    
        double
        K = 1.0 / (1.0 + 0.2316419 * fabs(d));
    
        double
        cnd = RSQRT2PI * exp(- 0.5 * d * d) *
              (K * (A1 + K * (A2 + K * (A3 + K * (A4 + K * A5)))));
    
        if (d > 0)
            cnd = 1.0 - cnd;
    
        return cnd;
    }
    

    Copyright 1993-2012 NVIDIA Corporation. All rights reserved.

    0 讨论(0)
提交回复
热议问题