13

I'm running some Monte Carlo simulations and making extensive use of the Excel function NORM.INV using Office Interrop. This functions takes three arguments (probability, average, standard deviation) and returns the inverse of the cumulative distribution.

I'd like to move my code into a web app, but that will require installing Excel on the server. Does anybody know of a C# statistics library that has an equivalent function to NORM.INV?

Portman
  • 31,785
  • 25
  • 82
  • 101
  • 1
    Whatever you do, *don't* install Excel on your web server. Write your own function for this if you have to. – MusiGenesis May 25 '10 at 03:41
  • @MusiGenesis: Why are you so hostile to Excel on a webserver? I agree it's not *ideal*, but it seems better than trying to write my own statistical functions, right? – Portman May 25 '10 at 04:10
  • 1
    I'm not hostile to Excel on a webserver - I'm mortally terrified of it. Office objects are *huge* things, and you don't want your web app to take the hit of instantiating them for something as small as a distribution function. It's true that you don't want to go around reinventing the wheel, but it's also true that you don't want to go around killing mice with howitzers. You're definitely better off writing your own function. – MusiGenesis May 26 '10 at 03:00
  • @MusiGenesis: Respectfully disagree. It's a Monte Carlo simulation, and this function gets called 25,000 times. The Excel workbook object is 3MB. I can currently call the function 25,000 times in about 10 seconds. My first attempt at writing it myself using the algorithm referenced by @Peter was bug-free (yay!) but used 40MB and ran in 20 minutes (boo!). – Portman May 26 '10 at 05:46
  • 1
    the problem you're going to face is with using Excel in a *web app*, when a large number of users are making requests concurrently. Each request will require its own Excel workbook object to be created, or you have to share one workbook object between requests - either way is going to cause problems. If you're committed to using Excel for this, make sure you heavily load test your app with multiple concurrent users. – MusiGenesis May 26 '10 at 11:56

7 Answers7

10

Meta.Numerics has exactly what you are looking for. Here is the code to do it using that library:

Distribution n = new NormalDistribution(mean, standardDeviation);
double x = n.InverseLeftProbability(probability);

If you are doing this in order to generate normal deviates, the GetRandomValue function is even faster.

David Wright
  • 765
  • 6
  • 15
5

https://numerics.mathdotnet.com/ has a pretty neat looking library that deals with stats (so I assume the CDF), I've not used it so I can't say for definite that it's what you want, but it seems like it should be.

RichK
  • 11,318
  • 6
  • 35
  • 49
  • 1
    CDF and inverse CDF are not currently part of that project. Or at least I couldn't find them while looking through the project. –  Dec 06 '12 at 15:32
  • Seems to be in there now (6 years on :)): double InvCDF(double mean, double stddev, double p) https://numerics.mathdotnet.com/api/MathNet.Numerics.Distributions/Normal.htm – grasmi Oct 16 '18 at 02:56
5

I was also needing a C# implementation of NORMINV, the closest thing I found was a C++ implementation http://www.wilmott.com/messageview.cfm?catid=10&threadid=38771, so I made a quick and dirty translation to C#, the details here http://weblogs.asp.net/esanchez/archive/2010/07/29/a-quick-and-dirty-implementation-of-excel-norminv-function-in-c.aspx. I have made only a few basic tests so be careful if you decide to use it, anyway, hope it helps!

  • I also made an implementation in F#, you can check the blog post here http://weblogs.asp.net/esanchez/archive/2010/08/08/a-quick-and-dirty-implementation-of-excel-norminv-in-f.aspx – Édgar Sánchez Gordón Aug 09 '10 at 04:30
  • 1
    I compared this with Euan Dean's implementation as well as the one in Meta.Numerics against the reference implementation my business users recognize (Excel). This one was the most precise, while Euan Dean's was the fastest. Meta.Numerics was pretty precise but very slow. – Andriy Volkov Feb 07 '13 at 18:13
4

The inverse normal CDF, including coefficients, is described here. And the absolute value of the relative error is less than 1.15 × 10−9

public static class NormalDistributionConfidenceCalculator
{
    /// <summary>
    /// 
    /// </summary>
    public static double InverseNormalDistribution(double probability, double min, double max)
    {
        double x = 0;
        double a = 0;
        double b = 1;

        double precision = Math.Pow(10, -3);

        while ((b - a) > precision)
        {
            x = (a + b) / 2;
            if (NormInv(x) > probability)
            {
                b = x;
            }
            else
            {
                a = x;
            }
        }

        if ((max > 0) && (min > 0))
        {
            x = x * (max - min) + min;
        }
        return x;
    }

    /// <summary>
    /// Returns the cumulative density function evaluated at A given value.
    /// </summary>
    /// <param name="x">A position on the x-axis.</param>
    /// <param name="mean"></param>
    /// <param name="sigma"></param>
    /// <returns>The cumulative density function evaluated at <C>x</C>.</returns>
    /// <remarks>The value of the cumulative density function at A point <C>x</C> is
    /// probability that the value of A random variable having this normal density is
    /// less than or equal to <C>x</C>.
    /// </remarks>
    public static double NormalDistribution(double x, double mean, double sigma)
    {
        // This algorithm is ported from dcdflib:
        // Cody, W.D. (1993). "ALGORITHM 715: SPECFUN - A Portabel FORTRAN
        // Package of Special Function Routines and Test Drivers"
        // acm Transactions on Mathematical Software. 19, 22-32.
        int i;
        double del, xden, xnum, xsq;
        double result, ccum;
        double arg = (x - mean) / sigma;
        const double sixten = 1.60e0;
        const double sqrpi = 3.9894228040143267794e-1;
        const double thrsh = 0.66291e0;
        const double root32 = 5.656854248e0;
        const double zero = 0.0e0;
        const double min = Double.Epsilon;
        double z = arg;
        double y = Math.Abs(z);
        const double half = 0.5e0;
        const double one = 1.0e0;

        double[] a =
            {
                2.2352520354606839287e00, 1.6102823106855587881e02, 1.0676894854603709582e03,
                1.8154981253343561249e04, 6.5682337918207449113e-2
            };

        double[] b =
            {
                4.7202581904688241870e01, 9.7609855173777669322e02, 1.0260932208618978205e04,
                4.5507789335026729956e04
            };

        double[] c =
            {
                3.9894151208813466764e-1, 8.8831497943883759412e00, 9.3506656132177855979e01,
                5.9727027639480026226e02, 2.4945375852903726711e03, 6.8481904505362823326e03,
                1.1602651437647350124e04, 9.8427148383839780218e03, 1.0765576773720192317e-8
            };

        double[] d =
            {
                2.2266688044328115691e01, 2.3538790178262499861e02, 1.5193775994075548050e03,
                6.4855582982667607550e03, 1.8615571640885098091e04, 3.4900952721145977266e04,
                3.8912003286093271411e04, 1.9685429676859990727e04
            };
        double[] p =
            {
                2.1589853405795699e-1, 1.274011611602473639e-1, 2.2235277870649807e-2,
                1.421619193227893466e-3, 2.9112874951168792e-5, 2.307344176494017303e-2
            };


        double[] q =
            {
                1.28426009614491121e00, 4.68238212480865118e-1, 6.59881378689285515e-2,
                3.78239633202758244e-3, 7.29751555083966205e-5
            };
        if (y <= thrsh)
        {
            //
            // Evaluate  anorm  for  |X| <= 0.66291
            //
            xsq = zero;
            if (y > double.Epsilon) xsq = z * z;
            xnum = a[4] * xsq;
            xden = xsq;
            for (i = 0; i < 3; i++)
            {
                xnum = (xnum + a[i]) * xsq;
                xden = (xden + b[i]) * xsq;
            }
            result = z * (xnum + a[3]) / (xden + b[3]);
            double temp = result;
            result = half + temp;
        }

            //
        // Evaluate  anorm  for 0.66291 <= |X| <= sqrt(32)
        //
        else if (y <= root32)
        {
            xnum = c[8] * y;
            xden = y;
            for (i = 0; i < 7; i++)
            {
                xnum = (xnum + c[i]) * y;
                xden = (xden + d[i]) * y;
            }
            result = (xnum + c[7]) / (xden + d[7]);
            xsq = Math.Floor(y * sixten) / sixten;
            del = (y - xsq) * (y + xsq);
            result = Math.Exp(-(xsq * xsq * half)) * Math.Exp(-(del * half)) * result;
            ccum = one - result;
            if (z > zero)
            {
                result = ccum;
            }
        }

            //
        // Evaluate  anorm  for |X| > sqrt(32)
        //
        else
        {
            xsq = one / (z * z);
            xnum = p[5] * xsq;
            xden = xsq;
            for (i = 0; i < 4; i++)
            {
                xnum = (xnum + p[i]) * xsq;
                xden = (xden + q[i]) * xsq;
            }
            result = xsq * (xnum + p[4]) / (xden + q[4]);
            result = (sqrpi - result) / y;
            xsq = Math.Floor(z * sixten) / sixten;
            del = (z - xsq) * (z + xsq);
            result = Math.Exp(-(xsq * xsq * half)) * Math.Exp(-(del * half)) * result;
            ccum = one - result;
            if (z > zero)
            {
                result = ccum;
            }
        }

        if (result < min)
            result = 0.0e0;
        return result;
    }

    /// <summary>
    /// Given a probability, a mean, and a standard deviation, an x value can be calculated.
    /// </summary>
    /// <returns></returns>
    public static double NormInv(double probability)
    {
        const double a1 = -39.6968302866538;
        const double a2 = 220.946098424521;
        const double a3 = -275.928510446969;
        const double a4 = 138.357751867269;
        const double a5 = -30.6647980661472;
        const double a6 = 2.50662827745924;

        const double b1 = -54.4760987982241;
        const double b2 = 161.585836858041;
        const double b3 = -155.698979859887;
        const double b4 = 66.8013118877197;
        const double b5 = -13.2806815528857;

        const double c1 = -7.78489400243029E-03;
        const double c2 = -0.322396458041136;
        const double c3 = -2.40075827716184;
        const double c4 = -2.54973253934373;
        const double c5 = 4.37466414146497;
        const double c6 = 2.93816398269878;

        const double d1 = 7.78469570904146E-03;
        const double d2 = 0.32246712907004;
        const double d3 = 2.445134137143;
        const double d4 = 3.75440866190742;

        //Define break-points
        // using Epsilon is wrong; see link above for reference to 0.02425 value
        //const double pLow = double.Epsilon;
        const double pLow = 0.02425;

        const double pHigh = 1 - pLow;

        //Define work variables
        double q;
        double result = 0;

        // if argument out of bounds.
        // set it to a value within desired precision.
        if (probability <= 0) 
            probability = pLow;

        if (probability >= 1)
            probability = pHigh;

        if (probability < pLow)
        {
            //Rational approximation for lower region
            q = Math.Sqrt(-2 * Math.Log(probability));
            result = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
        }
        else if (probability <= pHigh)
        {
            //Rational approximation for lower region
            q = probability - 0.5;
            double r = q * q;
            result = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q /
                     (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1);
        }
        else if (probability < 1)
        {
            //Rational approximation for upper region
            q = Math.Sqrt(-2 * Math.Log(1 - probability));
            result = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
        }

        return result;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="probability"></param>
    /// <param name="mean"></param>
    /// <param name="sigma"></param>
    /// <returns></returns>
    public static double NormInv(double probability, double mean, double sigma)
    {
        double x = NormInv(probability);
        return sigma * x + mean;
    }
}
Euan Dean
  • 49
  • 1
  • 1
1

I do not know of a library, but found this link - http://home.online.no/~pjacklam/notes/invnorm/ - describing an algoritm. It has implementations in a number of languages, but not C#. You could use the VB.NET version, or port it yourself.

Peter
  • 1,595
  • 13
  • 18
1

Maybe you can try this component,http://www.smartxls.com,it has an Excel compatible runtime calculation engine and it does not need Excel installed.

liya
  • 782
  • 5
  • 6
0

add a chart control

double result = Chart1.DataManipulator.Statistics.InverseNormalDistribution(probability);

probability eg : 0.9, 0.4

John doe
  • 21
  • 1
  • 7