| 1 |
/** |
|---|
| 2 |
* Macros: |
|---|
| 3 |
* |
|---|
| 4 |
* TABLE_SV = <table border=1 cellpadding=4 cellspacing=0> |
|---|
| 5 |
* <caption>Special Values</caption> |
|---|
| 6 |
* $0</table> |
|---|
| 7 |
* |
|---|
| 8 |
* NAN = $(RED NAN) |
|---|
| 9 |
* SUP = <span style="vertical-align:super;font-size:smaller">$0</span> |
|---|
| 10 |
*/ |
|---|
| 11 |
module mathextra.normaldist; |
|---|
| 12 |
private import std.math; |
|---|
| 13 |
debug import std.stdio; |
|---|
| 14 |
|
|---|
| 15 |
unittest { |
|---|
| 16 |
debug writefln("--- UnitTest: " __FILE__ " ---"); |
|---|
| 17 |
} |
|---|
| 18 |
|
|---|
| 19 |
const real SQRT2PI = 0x1.40d931ff62705966p+1; // 2.5066282746310005024 |
|---|
| 20 |
const real EXP_2 = 0.13533528323661269189L; /* exp(-2) */ |
|---|
| 21 |
|
|---|
| 22 |
/*** |
|---|
| 23 |
Computes the normal distribution function. |
|---|
| 24 |
|
|---|
| 25 |
The normal (or Gaussian, or bell-shaped) distribution is |
|---|
| 26 |
defined as: |
|---|
| 27 |
|
|---|
| 28 |
normalDist(x) = ∫$(SUP x)<sub>-$(INFINITY)</sub> |
|---|
| 29 |
exp(-t$(SUP 2)/2)/$(SQRT)2*π |
|---|
| 30 |
= 0.5 + 0.5 * erf(x/sqrt(2)) |
|---|
| 31 |
= 0.5 * erfc(- x/sqrt(2)) |
|---|
| 32 |
|
|---|
| 33 |
To maintain accuracy at high values of x, use |
|---|
| 34 |
normalDistribution(x) = 1 - normalDistribution(-x). |
|---|
| 35 |
|
|---|
| 36 |
Accuracy: |
|---|
| 37 |
Within a few bits of machine resolution over the entire |
|---|
| 38 |
range. |
|---|
| 39 |
|
|---|
| 40 |
$(TABLE_SV |
|---|
| 41 |
$(TR $(TH x) $(TH cos(x)) $(TH invalid?) ) |
|---|
| 42 |
$(TR $(TD $(NAN)) $(TD $(NAN)) $(TD yes) ) |
|---|
| 43 |
$(TR $(TD ±∞) $(TD $(NAN)) $(TD yes) ) |
|---|
| 44 |
) |
|---|
| 45 |
References: |
|---|
| 46 |
G. Marsaglia, "Evaluating the Normal Distribution", |
|---|
| 47 |
Journal of Statistical Software <b>11</b>, (July 2004). |
|---|
| 48 |
*/ |
|---|
| 49 |
real normalDistribution(real x) |
|---|
| 50 |
{ |
|---|
| 51 |
return 0.5 * erfc(-x * SQRT1_2); |
|---|
| 52 |
} |
|---|
| 53 |
|
|---|
| 54 |
private { |
|---|
| 55 |
static real P0[] = [ |
|---|
| 56 |
-0x1.758f4d969484bfdcp-7, // -0.011400139698853582732 |
|---|
| 57 |
0x1.53cee17a59259dd2p-3, // 0.16592193750979583221 |
|---|
| 58 |
-0x1.ea01e4400a9427a2p-1, // -0.95704568177942689081 |
|---|
| 59 |
0x1.61f7504a0105341ap+1, // 2.7653599130008302859 |
|---|
| 60 |
-0x1.09475a594d0399f6p+2, // -4.1449800369337538286 |
|---|
| 61 |
0x1.7c59e7a0df99e3e2p+1, // 2.971493676711545292 |
|---|
| 62 |
-0x1.87a81da52edcdf14p-1, // -0.76495449677843806914 |
|---|
| 63 |
0x1.1fb149fd3f83600cp-7 // 0.0087796794200550691607 |
|---|
| 64 |
]; |
|---|
| 65 |
|
|---|
| 66 |
static real Q0[] = [ |
|---|
| 67 |
-0x1.64b92ae791e64bb2p-7, // -0.010886331510064192632 |
|---|
| 68 |
0x1.7585c7d597298286p-3, // 0.1823840725000038842 |
|---|
| 69 |
-0x1.40011be4f7591ce6p+0, // -1.2500169214248199725 |
|---|
| 70 |
0x1.1fc067d8430a425ep+2, // 4.4961185085232139506 |
|---|
| 71 |
-0x1.21008ffb1e7ccdf2p+3, // -9.0313186554593813887 |
|---|
| 72 |
0x1.3d1581cf9bc12fccp+3, // 9.9088753752567182205 |
|---|
| 73 |
-0x1.53723a89fd8f083cp+2, // -5.3038469646037218604 |
|---|
| 74 |
0x1p+0 // 1 |
|---|
| 75 |
]; |
|---|
| 76 |
|
|---|
| 77 |
static real P1[] = [ |
|---|
| 78 |
0x1.20ceea49ea142f12p-13, // 0.00013771451113809605662 |
|---|
| 79 |
0x1.cbe8a7267aea80bp-7, // 0.014035302749980729871 |
|---|
| 80 |
0x1.79fea765aa787c48p-2, // 0.36913549001712241224 |
|---|
| 81 |
0x1.d1f59faa1f4c4864p+1, // 3.6403083401370131097 |
|---|
| 82 |
0x1.1c22e426a013bb96p+4, // 17.75851836288460008 |
|---|
| 83 |
0x1.a8675a0c51ef3202p+5, // 53.050464721918523919 |
|---|
| 84 |
0x1.75782c4f83614164p+6, // 93.367356531518738722 |
|---|
| 85 |
0x1.7a2f3d90948f1666p+6, // 94.546133288447683183 |
|---|
| 86 |
0x1.5cd116ee4c088c3ap+5, // 43.602094518370966827 |
|---|
| 87 |
0x1.1361e3eb6e3cc20ap+2 // 4.3028497504355521807 |
|---|
| 88 |
]; |
|---|
| 89 |
|
|---|
| 90 |
static real Q1[] = [ |
|---|
| 91 |
0x1.3a4ce1406cea98fap-13, // 0.00014987006762866754669 |
|---|
| 92 |
0x1.f45332623335cda2p-7, // 0.015268706895221911913 |
|---|
| 93 |
0x1.98f28bbd4b98db1p-2, // 0.39936273901812389627 |
|---|
| 94 |
0x1.ec3b24f9c698091cp+1, // 3.8455549449546995474 |
|---|
| 95 |
0x1.1cc56ecda7cf58e4p+4, // 17.79820137342627204 |
|---|
| 96 |
0x1.92c6f7376bf8c058p+5, // 50.347151215536627131 |
|---|
| 97 |
0x1.4154c25aa47519b4p+6, // 80.332772651946720635 |
|---|
| 98 |
0x1.1b321d3b927849eap+6, // 70.798939638914882544 |
|---|
| 99 |
0x1.403a5f5a4ce7b202p+4, // 20.014251091705301368 |
|---|
| 100 |
0x1p+0 // 1 |
|---|
| 101 |
]; |
|---|
| 102 |
|
|---|
| 103 |
static real P2[] = [ |
|---|
| 104 |
0x1.8c124a850116a6d8p-21, // 7.3774056430545041787e-07 |
|---|
| 105 |
0x1.534abda3c2fb90bap-13, // 0.0001617870121822776094 |
|---|
| 106 |
0x1.29a055ec93a4718cp-7, // 0.0090828342009931074419 |
|---|
| 107 |
0x1.6468e98aad6dd474p-3, // 0.17402822927913678347 |
|---|
| 108 |
0x1.3dab2ef4c67a601cp+0, // 1.2408933017345389353 |
|---|
| 109 |
0x1.e1fb3a1e70c67464p+1, // 3.7654793404231444828 |
|---|
| 110 |
0x1.b6cce8035ff57b02p+2, // 6.8562564881284157607 |
|---|
| 111 |
0x1.9f4c9e749ff35f62p+1 // 3.2445257253129069325 |
|---|
| 112 |
]; |
|---|
| 113 |
|
|---|
| 114 |
static real Q2[] = [ |
|---|
| 115 |
0x1.af03f4fc0655e006p-21, // 8.0282885006885383316e-07 |
|---|
| 116 |
0x1.713192048d11fb2p-13, // 0.00017604524340842589303 |
|---|
| 117 |
0x1.4357e5bbf5fef536p-7, // 0.0098676559208996361084 |
|---|
| 118 |
0x1.7fdac8749985d43cp-3, // 0.18742901426157036096 |
|---|
| 119 |
0x1.4a080c813a2d8e84p+0, // 1.2891853156563028786 |
|---|
| 120 |
0x1.c3a4b423cdb41bdap+1, // 3.528463857156936774 |
|---|
| 121 |
0x1.8160694e24b5557ap+2, // 6.0215094817275106307 |
|---|
| 122 |
0x1p+0 // 1 |
|---|
| 123 |
]; |
|---|
| 124 |
|
|---|
| 125 |
static real P3[] = [ |
|---|
| 126 |
-0x1.55da447ae3806168p-34, // -7.7728283809481633868e-11 |
|---|
| 127 |
-0x1.145635641f8778a6p-24, // -6.4339663876133447143e-08 |
|---|
| 128 |
-0x1.abf46d6b48040128p-17, // -1.2754046756102807876e-05 |
|---|
| 129 |
-0x1.7da550945da790fcp-11, // -0.00072793152007373443093 |
|---|
| 130 |
-0x1.aa0b2a31157775fap-8, // -0.0065009096152460679857 |
|---|
| 131 |
0x1.b11d97522eed26bcp-3, // 0.21148222178987070632 |
|---|
| 132 |
0x1.1106d22f9ae89238p+1, // 2.1330206615874130532 |
|---|
| 133 |
0x1.029a358e1e630f64p+1 // 2.0203310913027725356 |
|---|
| 134 |
]; |
|---|
| 135 |
|
|---|
| 136 |
static real Q3[] = [ |
|---|
| 137 |
-0x1.74022dd5523e6f84p-34, // -8.4584942637876803775e-11 |
|---|
| 138 |
-0x1.2cb60d61e29ee836p-24, // -7.0014768675591937804e-08 |
|---|
| 139 |
-0x1.d19e6ec03a85e556p-17, // -1.3876523894802171788e-05 |
|---|
| 140 |
-0x1.9ea2a7b4422f6502p-11, // -0.00079085420887378582886 |
|---|
| 141 |
-0x1.c54b1e852f107162p-8, // -0.0069167088997199649828 |
|---|
| 142 |
0x1.e05268dd3c07989ep-3, // 0.23453218388704381964 |
|---|
| 143 |
0x1.239c6aff14afbf82p+1, // 2.2782109971534491995 |
|---|
| 144 |
0x1p+0 // 1 |
|---|
| 145 |
]; |
|---|
| 146 |
|
|---|
| 147 |
} |
|---|
| 148 |
|
|---|
| 149 |
/****************************** |
|---|
| 150 |
* Inverse of Normal distribution function |
|---|
| 151 |
* |
|---|
| 152 |
* Returns the argument, x, for which the area under the |
|---|
| 153 |
* Normal probability density function (integrated from |
|---|
| 154 |
* minus infinity to x) is equal to p. |
|---|
| 155 |
* |
|---|
| 156 |
* For small arguments 0 < p < exp(-2), the program computes |
|---|
| 157 |
* z = sqrt( -2 log(p) ); then the approximation is |
|---|
| 158 |
* x = z - log(z)/z - (1/z) P(1/z) / Q(1/z) . |
|---|
| 159 |
* For larger arguments, x/sqrt(2 pi) = w + w^3 R(w^2)/S(w^2)) , |
|---|
| 160 |
* where w = p - 0.5 . |
|---|
| 161 |
*/ |
|---|
| 162 |
real normalDistributionInv(real p) |
|---|
| 163 |
in { |
|---|
| 164 |
assert(p>=0.0L && p<=1.0L); // domain error |
|---|
| 165 |
} |
|---|
| 166 |
body |
|---|
| 167 |
{ |
|---|
| 168 |
if (p == 0.0L) { |
|---|
| 169 |
return -real.infinity; |
|---|
| 170 |
} |
|---|
| 171 |
if( p == 1.0L ) { |
|---|
| 172 |
return real.infinity; |
|---|
| 173 |
} |
|---|
| 174 |
real x, z, y2, x0, x1; |
|---|
| 175 |
int code = 1; |
|---|
| 176 |
real y = p; |
|---|
| 177 |
if( y > (1.0L - EXP_2) ) { |
|---|
| 178 |
y = 1.0L - y; |
|---|
| 179 |
code = 0; |
|---|
| 180 |
} |
|---|
| 181 |
|
|---|
| 182 |
if ( y > EXP_2 ) { |
|---|
| 183 |
y = y - 0.5L; |
|---|
| 184 |
y2 = y * y; |
|---|
| 185 |
x = y + y * (y2 * poly( y2, P0)/poly( y2, Q0)); |
|---|
| 186 |
x = x * SQRT2PI; |
|---|
| 187 |
return x; |
|---|
| 188 |
} |
|---|
| 189 |
|
|---|
| 190 |
x = sqrt( -2.0L * log(y) ); |
|---|
| 191 |
x0 = x - log(x)/x; |
|---|
| 192 |
z = 1.0L/x; |
|---|
| 193 |
if( x < 8.0L ) { |
|---|
| 194 |
x1 = z * poly( z, P1)/poly( z, Q1); |
|---|
| 195 |
} else if( x < 32.0L ) { |
|---|
| 196 |
x1 = z * poly( z, P2)/poly( z, Q2); |
|---|
| 197 |
} else { |
|---|
| 198 |
// assert(0); |
|---|
| 199 |
x1 = z * poly( z, P3)/poly( z, Q3); |
|---|
| 200 |
} |
|---|
| 201 |
x = x0 - x1; |
|---|
| 202 |
if( code != 0 ) { |
|---|
| 203 |
x = -x; |
|---|
| 204 |
} |
|---|
| 205 |
return x; |
|---|
| 206 |
} |
|---|
| 207 |
|
|---|
| 208 |
|
|---|
| 209 |
unittest { |
|---|
| 210 |
// The values below are from Excel 2003. |
|---|
| 211 |
assert(fabs(normalDistributionInv(0.001) - (-3.09023230616779))< 0.00000000000005); |
|---|
| 212 |
assert(fabs(normalDistributionInv(1e-50) - (-14.9333375347885))< 0.00000000000005); |
|---|
| 213 |
assert(feqrel(normalDistributionInv(0.999), -normalDistributionInv(0.001))>real.mant_dig-6); |
|---|
| 214 |
|
|---|
| 215 |
// Excel 2003 gets all the following values wrong! |
|---|
| 216 |
assert(normalDistributionInv(0.0)==-real.infinity); |
|---|
| 217 |
assert(normalDistributionInv(1.0)==real.infinity); |
|---|
| 218 |
assert(normalDistributionInv(0.5)==0); |
|---|
| 219 |
|
|---|
| 220 |
// I don't know the correct result for low values |
|---|
| 221 |
// (Excel 2003 returns norminv(p) = -30 for all p < 1e-200). |
|---|
| 222 |
// The value tested here is the one the function returned in Jan 2006. |
|---|
| 223 |
real unknown1 = normalDistributionInv(1e-250L); |
|---|
| 224 |
assert( fabs(unknown1 -(-33.79958617269L) ) < 0.00000005); |
|---|
| 225 |
} |
|---|
| 226 |
|
|---|
| 227 |
unittest { |
|---|
| 228 |
|
|---|
| 229 |
//writefln("%.20g", normalDistribution(normalDistributionInv(1e-90))); |
|---|
| 230 |
//normalDistribution(normalDistributionInv(1e-50)) |
|---|
| 231 |
assert(fabs(normalDistribution(1L) - (0.841344746068543))< 0.0000000000000005); |
|---|
| 232 |
assert(isnan(normalDistribution(real.nan))); |
|---|
| 233 |
|
|---|
| 234 |
//writefln("%.20f", normalDistributionInv(1e-250L)); |
|---|
| 235 |
//-3.09023230616779000000 |
|---|
| 236 |
|
|---|
| 237 |
//assert(normalDistribution(0)==0.5); |
|---|
| 238 |
/+ |
|---|
| 239 |
for (int i=0; i<=10; ++i) { |
|---|
| 240 |
writefln(i/10.0, " ", normalDistributionInv(i/10.0)); |
|---|
| 241 |
} |
|---|
| 242 |
real qq= 1-real.epsilon; |
|---|
| 243 |
writefln(qq, " %a ", qq, normalDistributionInv(qq)); |
|---|
| 244 |
qq = real.min; |
|---|
| 245 |
writefln(qq, " %a ", qq, normalDistributionInv(qq)); |
|---|
| 246 |
writefln(normalDistribution(9)); |
|---|
| 247 |
writefln(normalDistribution(-150)); |
|---|
| 248 |
+/ |
|---|
| 249 |
} |
|---|