Mercurial > octave-nkf
diff liboctave/lo-specfun.cc @ 9835:1bb1ed717d2f
implement built-in erfinv
author | Jaroslav Hajek <highegg@gmail.com> |
---|---|
date | Thu, 19 Nov 2009 10:30:57 +0100 |
parents | f80c566bc751 |
children | 7c70084b125e |
line wrap: on
line diff
--- a/liboctave/lo-specfun.cc Wed Nov 18 23:14:09 2009 -0500 +++ b/liboctave/lo-specfun.cc Thu Nov 19 10:30:57 2009 +0100 @@ -3101,6 +3101,78 @@ return x < -1.0f ? FloatComplex (logf (-(1.0f + x)), pi) : FloatComplex (log1pf (x)); } +// This algorithm is due to P. Jacklam +// See http:// +// The rational approximation has relative accuracy 1e-9 in the whole region. +// For doubles, it is refined by a single step of Higham's 3rd order method. +// For single precision, the accuracy is already OK, so we skip it to get +// faster evaluation. + +static double do_erfinv (double x, bool refine) +{ + // Coefficients of rational approximation. + static const double a[] = + { -2.806989788730439e+01, 1.562324844726888e+02, + -1.951109208597547e+02, 9.783370457507161e+01, + -2.168328665628878e+01, 1.772453852905383e+00 }; + static const double b[] = + { -5.447609879822406e+01, 1.615858368580409e+02, + -1.556989798598866e+02, 6.680131188771972e+01, + -1.328068155288572e+01 }; + static const double c[] = + { -5.504751339936943e-03, -2.279687217114118e-01, + -1.697592457770869e+00, -1.802933168781950e+00, + 3.093354679843505e+00, 2.077595676404383e+00 }; + static const double d[] = + { 7.784695709041462e-03, 3.224671290700398e-01, + 2.445134137142996e+00, 3.754408661907416e+00 }; + + static const double spi2 = 8.862269254527579e-01; // sqrt(pi)/2. + static const double pbreak = 0.95150; + double ax = fabs (x), y; + + // Select case. + if (ax <= pbreak) + { + // Middle region. + const double q = 0.5 * x, r = q*q; + const double yn = (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5])*q; + const double yd = ((((b[0]*r + b[1])*r + b[2])*r + b[3])*r + b[4])*r + 1.0; + y = yn / yd; + } + else if (ax < 1.0) + { + // Tail region. + const double q = sqrt (-2*log (0.5*(1-ax))); + const double yn = ((((c[0]*q + c[1])*q + c[2])*q + c[3])*q + c[4])*q + c[5]; + const double yd = (((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0; + y = yn / yd * signum (-x); + } + else if (ax == 1.0) + return octave_Inf * signum (x); + else + return octave_NaN; + + if (refine) + { + // One iteration of Halley's method gives full precision. + double u = (erf(y) - x) * spi2 * exp (y*y); + y -= u / (1 + y*u); + } + + return y; +} + +double erfinv (double x) +{ + return do_erfinv (x, true); +} + +float erfinv (float x) +{ + return do_erfinv (x, false); +} + /* ;;; Local Variables: *** ;;; mode: C++ ***