root/trunk/mathextra/normaldist.d

Revision 81, 8.2 kB (checked in by Don Clugston, 2 years ago)

Simpler implementation of normaldistribution, in terms of erfc.

Line 
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) = &int;$(SUP x)<sub>-$(INFINITY)</sub>
29    exp(-t$(SUP 2)/2)/$(SQRT)2*&pi;
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 &plusmn;&infin;) $(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   }
Note: See TracBrowser for help on using the browser.