web-dev-qa-db-fra.com

Fonction de distribution normale cumulative en C / C ++

Je me demandais s'il y avait des fonctions statistiques intégrées dans les bibliothèques mathématiques qui font partie des bibliothèques C++ standard comme cmath. Sinon, pouvez-vous recommander une bonne bibliothèque de statistiques qui aurait une fonction de distribution normale cumulative? Merci d'avance.

Plus précisément, je cherche à utiliser/créer une fonction de distribution cumulative.

46
Tyler Brock

J'ai compris comment le faire en utilisant gsl, à la suggestion des gens qui ont répondu avant moi, mais j'ai ensuite trouvé une solution non-bibliothèque (j'espère que cela aide beaucoup de gens qui le recherchent comme moi):

#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;
}
9
Tyler Brock

Il n'y a pas de fonction directe. Mais comme la fonction d'erreur gaussienne et sa fonction complémentaire sont liées à la fonction de distribution cumulative normale (voir ici , ou ici ) nous pouvons utiliser la fonction c implémentée erfc (fonction d'erreur complémentaire):

double normalCDF(double value)
{
   return 0.5 * erfc(-value * M_SQRT1_2);
}

Ce qui considère la relation de erfc(x) = 1-erf(x) avec M_SQRT1_2 = √0,5.

Je l'utilise pour des calculs statistiques et cela fonctionne très bien. Pas besoin d'utiliser des coefficients.

36
JFS

Voici une implémentation C++ autonome de la distribution normale cumulative dans 14 lignes de 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";
}
32
John D. Cook

Boost est aussi bon que la norme: D vous y allez: boost maths/statistique .

9
Hassan Syed

Les implémentations du CDF normal donné ici sont des approximations simple précision qui ont eu float remplacées par double et ne sont donc précises qu'à 7 ou 8 significatives (décimales ) Les figures.
Pour une approximation VB d'implémentation de Hart double précision, voir la figure 2 de West Meilleures approximations des fonctions normales cumulatives =.

Edit : Ma traduction de l'implémentation de West en 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;
}

Notez que j'ai réorganisé les expressions dans les formes les plus familières pour les approximations de séries et de fractions continues. Le dernier nombre magique dans le code de West est la racine carrée de 2π, que j'ai reportée au compilateur sur la première ligne en exploitant l'identité acos (0) = ½ π.
J'ai triple vérifié les nombres magiques, mais il y a toujours la possibilité que j'ai mal tapé quelque chose. Si vous repérez une faute de frappe, veuillez commenter!

Les résultats des données de test que John Cook a utilisées dans sa réponse sont

 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

Je prends un peu de réconfort du fait qu'ils acceptent tous les chiffres donnés pour les résultats Mathematica.

8
thus spake a.k.

À partir d'échantillons NVIDIA CUDA:

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. Tous droits réservés.

5
serbaut

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

Le CDF normal peut être calculé comme suit:

#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;

double normalCDF(double x) // Phi(-∞, x) aka N(x)
{
    return erfc(-x / sqrt(2))/2;
}

Une note latérale, de mon expérience précédente, si vous avez un problème avec l'obtention uniquement des entiers au lieu des décimales, l'utilisation de 2,0 au lieu de 2 dans le dénominateur pour chaque formule vous aidera.

J'espère que cela pourra aider.