【问题标题】:Is there a C# library that will perform the Excel NORMINV function?是否有可以执行 Excel NORMINV 函数的 C# 库?
【发布时间】:2011-02-23 12:15:33
【问题描述】:

我正在运行一些Monte Carlo simulations 并使用Office Interrop 广泛使用Excel 函数NORM.INV。该函数采用三个参数(概率、平均值、标准差)并返回累积分布的倒数。

我想将我的代码移动到网络应用程序中,但这需要在服务器上安装 Excel。 有人知道具有与 NORM.INV 等效功能的 C# 统计库吗?

【问题讨论】:

  • 无论您做什么,不要在您的网络服务器上安装 Excel。如果需要,请为此编写自己的函数。
  • @MusiGenesis:你为什么对网络服务器上的 Excel 如此敌视?我同意这不是理想,但它似乎比尝试编写自己的统计函数更好,对吧?
  • 我对网络服务器上的 Excel 并不怀有敌意——我对它非常害怕。 Office 对象是巨大 的东西,你不希望你的网络应用程序为像分发函数这样小的东西实例化它们。确实,您不想到处重新发明轮子,但您也不想到处用榴弹炮杀死老鼠,这也是事实。编写自己的函数肯定会更好。
  • @MusiGenesis:不同意。这是一个蒙特卡洛模拟,这个函数被调用了 25,000 次。 Excel 工作簿对象为 3MB。我目前可以在大约 10 秒内调用该函数 25,000 次。我第一次尝试使用@Peter 引用的算法自己编写它没有错误(耶!),但使用了 40MB 并在 20 分钟内运行(嘘!)。
  • 您将面临的问题是在 Web 应用程序 中使用 Excel,此时大量用户同时发出请求。每个请求都需要创建自己的 Excel 工作簿对象,或者您必须在请求之间共享一个工作簿对象 - 无论哪种方式都会导致问题。如果您致力于为此使用 Excel,请确保您使用多个并发用户对您的应用进行大量负载测试。

标签: c# excel statistics montecarlo


【解决方案1】:

我不知道图书馆,但找到了这个链接 - http://home.online.no/~pjacklam/notes/invnorm/ - 描述了一个算法。它有多种语言的实现,但没有 C#。您可以使用 VB.NET 版本,也可以自己移植。

【讨论】:

    【解决方案2】:

    https://numerics.mathdotnet.com/ 有一个非常整洁的库来处理统计数据(所以我假设是 CDF),我没有使用过它,所以我不能确定它是你想要的,但它似乎应该是。

    【讨论】:

    【解决方案3】:

    也许你可以试试这个组件,http://www.smartxls.com,它有一个与 Excel 兼容的运行时计算引擎,不需要安装 Excel。

    【讨论】:

      【解决方案4】:

      我还需要 NORMINV 的 C# 实现,我找到的最接近的是 C++ 实现 http://www.wilmott.com/messageview.cfm?catid=10&threadid=38771,所以我对 C# 进行了快速而肮脏的翻译,详细信息在这里 http://weblogs.asp.net/esanchez/archive/2010/07/29/a-quick-and-dirty-implementation-of-excel-norminv-function-in-c.aspx。我只做了一些基本的测试,所以如果你决定使用它,请小心,无论如何,希望它有所帮助!

      【讨论】:

      • 我也在F#中做了一个实现,你可以在这里查看博文weblogs.asp.net/esanchez/archive/2010/08/08/…
      • 我将此与 Euan Dean 的实现以及 Meta.Numerics 中的实现与我的业务用户认可的参考实现 (Excel) 进行了比较。这个是最精确的,而尤安迪恩是最快的。 Meta.Numerics 非常精确,但速度很慢。
      【解决方案5】:

      包含系数的逆正态 CDF 描述为 here。并且相对误差的绝对值小于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;
          }
      }
      

      【讨论】:

      • 这是从哪里来的?你用它做什么?一点背景很重要。
      • 请添加上下文和引用
      【解决方案6】:

      Meta.Numerics 正是您想要的。这是使用该库执行此操作的代码:

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

      如果您这样做是为了生成正常偏差,GetRandomValue 函数会更快。

      【讨论】:

        【解决方案7】:

        添加图表控件

        双倍结果 = Chart1.DataManipulator.Statistics.InverseNormalDistribution(概率);

        概率例如:0.9, 0.4

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-05
          相关资源
          最近更新 更多