The OpenD Programming Language

1 /++
2 This module contains algorithms for the $(LINK2 https://en.wikipedia.org/wiki/Student%27s_t-distribution, Student's t Distribution).
3 
4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
5 
6 Authors: John Michael Hall
7 
8 Copyright: 2022-3 Mir Stat Authors.
9 
10 +/
11 
12 module mir.stat.distribution.students_t;
13 
14 import mir.internal.utility: isFloatingPoint;
15 
16 /++
17 Computes the Student's t probability density function (PDF).
18 
19 Params:
20     x = value to evaluate PDF
21     nu = degrees of freedom
22 
23 See_also:
24     $(LINK2 https://en.wikipedia.org/wiki/Student%27s_t-distribution, Student's t Distribution)
25 +/
26 @safe pure nothrow @nogc
27 T studentsTPDF(T)(const T x, const T nu)
28     if (isFloatingPoint!T)
29     in (nu > 0, "nu must be greater than zero")
30 {
31     import mir.math.common: pow, sqrt;
32     import mir.stat.constant: M_SQRTPI;
33     import mir.stat.distribution.normal: normalPDF;
34     import std.mathspecial: gamma;
35 
36     if (nu != T.infinity) {
37         return (gamma((nu + 1) * 0.5) / gamma(nu * 0.5)) / sqrt(nu) * T(M_SQRTPI) * pow(1 + (x * x) / nu, -(nu + 1) * 0.5);
38     } else {
39         return normalPDF(x);
40     }
41 }
42 
43 /++
44 Ditto, with location and scale parameters (by standardizing `x`).
45 
46 Params:
47     x = value to evaluate PDF
48     nu = degrees of freedom
49     mean = location parameter
50     stdDev = scale parameter
51 +/
52 @safe pure nothrow @nogc
53 T studentsTPDF(T)(const T x, const T nu, const T mean, const T stdDev)
54     if (isFloatingPoint!T)
55     in (nu > 0, "nu must be greater than zero")
56     in (stdDev > 0, "stdDev must be greater than zero")
57 {
58     return studentsTPDF((x - mean) / stdDev, nu);
59 }
60 
61 ///
62 version(mir_stat_test)
63 @safe pure nothrow @nogc
64 unittest {
65     import mir.test: shouldApprox;
66 
67     studentsTPDF(-3.0, 5).shouldApprox == 0.01729258;
68     studentsTPDF(-2.0, 5).shouldApprox == 0.06509031;
69     studentsTPDF(-1.0, 5).shouldApprox == 0.2196798;
70     studentsTPDF(0.0, 5).shouldApprox == 0.3796067;
71     studentsTPDF(1.0, 5).shouldApprox == 0.2196798;
72     studentsTPDF(2.0, 5).shouldApprox == 0.06509031;
73     studentsTPDF(3.0, 5).shouldApprox == 0.01729258;
74 
75     // Can include location/scale
76     studentsTPDF(-3.0, 5, 1, 2).shouldApprox == 0.06509031;
77     studentsTPDF(-2.0, 5, 1, 2).shouldApprox == 0.1245173;
78     studentsTPDF(-1.0, 5, 1, 2).shouldApprox == 0.2196798;
79     studentsTPDF(0.0, 5, 1, 2).shouldApprox == 0.3279185;
80     studentsTPDF(1.0, 5, 1, 2).shouldApprox == 0.3796067;
81     studentsTPDF(2.0, 5, 1, 2).shouldApprox == 0.3279185;
82     studentsTPDF(3.0, 5, 1, 2).shouldApprox == 0.2196798;
83 }
84 
85 // Checking other DoF
86 version(mir_stat_test)
87 @safe pure nothrow @nogc
88 unittest {
89     import mir.test: shouldApprox;
90 
91     studentsTPDF(-3.0, 25).shouldApprox == 0.007253748;
92     studentsTPDF(-2.0, 25).shouldApprox == 0.0573607;
93     studentsTPDF(-1.0, 25).shouldApprox == 0.237211;
94     studentsTPDF(0.0, 25).shouldApprox == 0.3949738;
95     studentsTPDF(1.0, 25).shouldApprox == 0.237211;
96     studentsTPDF(2.0, 25).shouldApprox == 0.0573607;
97     studentsTPDF(3.0, 25).shouldApprox == 0.007253748;
98 
99     studentsTPDF(-3.0, double.infinity).shouldApprox == 0.004431848;
100     studentsTPDF(-2.0, double.infinity).shouldApprox == 0.05399097;
101     studentsTPDF(-1.0, double.infinity).shouldApprox == 0.2419707;
102     studentsTPDF(0.0, double.infinity).shouldApprox == 0.3989423;
103     studentsTPDF(1.0, double.infinity).shouldApprox == 0.2419707;
104     studentsTPDF(2.0, double.infinity).shouldApprox == 0.05399097;
105     studentsTPDF(3.0, double.infinity).shouldApprox == 0.004431848;
106 }
107 
108 // Checking negative location parameter
109 version(mir_stat_test)
110 @safe pure nothrow @nogc
111 unittest {
112     import mir.test: shouldApprox;
113 
114     studentsTPDF(-3.0, 5, -1, 2).shouldApprox == 0.2196798;
115     studentsTPDF(-2.0, 5, -1, 2).shouldApprox == 0.3279185;
116     studentsTPDF(-1.0, 5, -1, 2).shouldApprox == 0.3796067;
117     studentsTPDF(0.0, 5, -1, 2).shouldApprox == 0.3279185;
118     studentsTPDF(1.0, 5, -1, 2).shouldApprox == 0.2196798;
119     studentsTPDF(2.0, 5, -1, 2).shouldApprox == 0.1245173;
120     studentsTPDF(3.0, 5, -1, 2).shouldApprox == 0.06509031;
121 }
122 
123 /++
124 Computes the Student's t cumulative distribution function (CDF).
125 
126 Params:
127     x = value to evaluate CDF
128     nu = degrees of freedom
129 
130 See_also:
131     $(LINK2 https://en.wikipedia.org/wiki/Student%27s_t-distribution, Student's t Distribution)
132 +/
133 @safe pure nothrow @nogc
134 T studentsTCDF(T)(const T x, const T nu)
135     if (isFloatingPoint!T)
136     in (nu > 0, "nu must be greater than zero")
137 {
138     import mir.stat.distribution.beta: betaCDF, betaCCDF;
139     import mir.stat.distribution.normal: normalCDF;
140 
141     if (nu != T.infinity) {
142         T output;
143         if (nu > x * x) {
144             output = betaCCDF(x * x / (nu + x * x), 0.5, 0.5 * nu);
145         } else {
146             output = betaCDF((nu / (nu + x * x)), 0.5 * nu, 0.5);
147         }
148         output *= 0.5;
149         if (x > 0) {
150             output = 1 - output;
151         }
152         return output;
153     } else {
154         return normalCDF(x);
155     }
156 }
157 
158 /++
159 Ditto, with location and scale parameters (by standardizing `x`).
160 
161 Params:
162     x = value to evaluate CDF
163     nu = degrees of freedom
164     mean = location parameter
165     stdDev = scale parameter
166 +/
167 @safe pure nothrow @nogc
168 T studentsTCDF(T)(const T x, const T nu, const T mean, const T stdDev)
169     if (isFloatingPoint!T)
170     in (nu > 0, "nu must be greater than zero")
171     in (stdDev > 0, "stdDev must be greater than zero")
172 {
173     return studentsTCDF((x - mean) / stdDev, nu);
174 }
175 
176 ///
177 version(mir_stat_test)
178 @safe pure nothrow @nogc
179 unittest {
180     import mir.test: shouldApprox;
181 
182     studentsTCDF(-3.0, 5).shouldApprox == 0.01504962;
183     studentsTCDF(-2.0, 5).shouldApprox == 0.05096974;
184     studentsTCDF(-1.0, 5).shouldApprox == 0.1816087;
185     studentsTCDF(0.0, 5).shouldApprox == 0.5;
186     studentsTCDF(1.0, 5).shouldApprox == 0.8183913;
187     studentsTCDF(2.0, 5).shouldApprox == 0.9490303;
188     studentsTCDF(3.0, 5).shouldApprox == 0.9849504;
189 
190     // Can include location/scale
191     studentsTCDF(-3.0, 5, 1, 2).shouldApprox == 0.05096974;
192     studentsTCDF(-2.0, 5, 1, 2).shouldApprox == 0.09695184;
193     studentsTCDF(-1.0, 5, 1, 2).shouldApprox == 0.1816087;
194     studentsTCDF(0.0, 5, 1, 2).shouldApprox == 0.3191494;
195     studentsTCDF(1.0, 5, 1, 2).shouldApprox == 0.5;
196     studentsTCDF(2.0, 5, 1, 2).shouldApprox == 0.6808506;
197     studentsTCDF(3.0, 5, 1, 2).shouldApprox == 0.8183913;
198 }
199 
200 // Checking other DoF
201 version(mir_stat_test)
202 @safe pure nothrow @nogc
203 unittest {
204     import mir.test: shouldApprox;
205 
206     studentsTCDF(-3.0, 25).shouldApprox == 0.00301909;
207     studentsTCDF(-2.0, 25).shouldApprox == 0.02823799;
208     studentsTCDF(-1.0, 25).shouldApprox == 0.163446;
209     studentsTCDF(0.0, 25).shouldApprox == 0.5;
210     studentsTCDF(1.0, 25).shouldApprox == 0.836554;
211     studentsTCDF(2.0, 25).shouldApprox == 0.971762;
212     studentsTCDF(3.0, 25).shouldApprox == 0.9969809;
213 
214     studentsTCDF(-3.0, double.infinity).shouldApprox == 0.001349898;
215     studentsTCDF(-2.0, double.infinity).shouldApprox == 0.02275013;
216     studentsTCDF(-1.0, double.infinity).shouldApprox == 0.1586553;
217     studentsTCDF(0.0, double.infinity).shouldApprox == 0.5;
218     studentsTCDF(1.0, double.infinity).shouldApprox == 0.8413447;
219     studentsTCDF(2.0, double.infinity).shouldApprox == 0.9772499;
220     studentsTCDF(3.0, double.infinity).shouldApprox == 0.9986501;
221 }
222 
223 // Checking negative location parameter
224 version(mir_stat_test)
225 @safe pure nothrow @nogc
226 unittest {
227     import mir.test: shouldApprox;
228 
229     studentsTCDF(-3.0, 5, -1, 2).shouldApprox == 0.1816087;
230     studentsTCDF(-2.0, 5, -1, 2).shouldApprox == 0.3191494;
231     studentsTCDF(-1.0, 5, -1, 2).shouldApprox == 0.5;
232     studentsTCDF(0.0, 5, -1, 2).shouldApprox == 0.6808506;
233     studentsTCDF(1.0, 5, -1, 2).shouldApprox == 0.8183913;
234     studentsTCDF(2.0, 5, -1, 2).shouldApprox == 0.9030482;
235     studentsTCDF(3.0, 5, -1, 2).shouldApprox == 0.9490303;
236 }
237 
238 /++
239 Computes the Student's t complementary cumulative distribution function (CCDF).
240 
241 Params:
242     x = value to evaluate CCDF
243     nu = degrees of freedom
244 
245 See_also:
246     $(LINK2 https://en.wikipedia.org/wiki/Student%27s_t-distribution, Student's t Distribution)
247 +/
248 @safe pure nothrow @nogc
249 T studentsTCCDF(T)(const T x, const T nu)
250     if (isFloatingPoint!T)
251     in (nu > 0, "nu must be greater than zero")
252 {
253     import mir.stat.distribution.normal: normalCCDF;
254 
255     return studentsTCDF(-x, nu);
256 }
257 
258 /++
259 Ditto, with location and scale parameters (by standardizing `x`).
260 
261 Params:
262     x = value to evaluate CCDF
263     nu = degrees of freedom
264     mean = location parameter
265     stdDev = scale parameter
266 +/
267 @safe pure nothrow @nogc
268 T studentsTCCDF(T)(const T x, const T nu, const T mean, const T stdDev)
269     if (isFloatingPoint!T)
270     in (nu > 0, "nu must be greater than zero")
271     in (stdDev > 0, "stdDev must be greater than zero")
272 {
273     return studentsTCCDF((x - mean) / stdDev, nu);
274 }
275 
276 ///
277 version(mir_stat_test)
278 @safe pure nothrow @nogc
279 unittest {
280     import mir.test: shouldApprox;
281 
282     studentsTCCDF(-3.0, 5).shouldApprox == 0.9849504;
283     studentsTCCDF(-2.0, 5).shouldApprox == 0.9490303;
284     studentsTCCDF(-1.0, 5).shouldApprox == 0.8183913;
285     studentsTCCDF(0.0, 5).shouldApprox == 0.5;
286     studentsTCCDF(1.0, 5).shouldApprox == 0.1816087;
287     studentsTCCDF(2.0, 5).shouldApprox == 0.05096974;
288     studentsTCCDF(3.0, 5).shouldApprox == 0.01504962;
289 
290     // Can include location/scale
291     studentsTCCDF(-3.0, 5, 1, 2).shouldApprox == 0.9490303;
292     studentsTCCDF(-2.0, 5, 1, 2).shouldApprox == 0.9030482;
293     studentsTCCDF(-1.0, 5, 1, 2).shouldApprox == 0.8183913;
294     studentsTCCDF(0.0, 5, 1, 2).shouldApprox == 0.6808506;
295     studentsTCCDF(1.0, 5, 1, 2).shouldApprox == 0.5;
296     studentsTCCDF(2.0, 5, 1, 2).shouldApprox == 0.3191494;
297     studentsTCCDF(3.0, 5, 1, 2).shouldApprox == 0.1816087;
298 }
299 
300 // Checking other DoF
301 version(mir_stat_test)
302 @safe pure nothrow @nogc
303 unittest {
304     import mir.test: shouldApprox;
305 
306     studentsTCCDF(-3.0, 25).shouldApprox == 0.9969809;
307     studentsTCCDF(-2.0, 25).shouldApprox == 0.971762;
308     studentsTCCDF(-1.0, 25).shouldApprox == 0.836554;
309     studentsTCCDF(0.0, 25).shouldApprox == 0.5;
310     studentsTCCDF(1.0, 25).shouldApprox == 0.163446;
311     studentsTCCDF(2.0, 25).shouldApprox == 0.02823799;
312     studentsTCCDF(3.0, 25).shouldApprox == 0.00301909;
313 
314     studentsTCCDF(-3.0, double.infinity).shouldApprox == 0.9986501;
315     studentsTCCDF(-2.0, double.infinity).shouldApprox == 0.9772499;
316     studentsTCCDF(-1.0, double.infinity).shouldApprox == 0.8413447;
317     studentsTCCDF(0.0, double.infinity).shouldApprox == 0.5;
318     studentsTCCDF(1.0, double.infinity).shouldApprox == 0.1586553;
319     studentsTCCDF(2.0, double.infinity).shouldApprox == 0.02275013;
320     studentsTCCDF(3.0, double.infinity).shouldApprox == 0.001349898;
321 }
322 
323 // Checking negative location parameter
324 version(mir_stat_test)
325 @safe pure nothrow @nogc
326 unittest {
327     import mir.test: shouldApprox;
328 
329     studentsTCCDF(-3.0, 5, -1, 2).shouldApprox == 0.8183913;
330     studentsTCCDF(-2.0, 5, -1, 2).shouldApprox == 0.6808506;
331     studentsTCCDF(-1.0, 5, -1, 2).shouldApprox == 0.5;
332     studentsTCCDF(0.0, 5, -1, 2).shouldApprox == 0.3191494;
333     studentsTCCDF(1.0, 5, -1, 2).shouldApprox == 0.1816087;
334     studentsTCCDF(2.0, 5, -1, 2).shouldApprox == 0.09695184;
335     studentsTCCDF(3.0, 5, -1, 2).shouldApprox == 0.05096974;
336 }
337 
338 /++
339 Computes the Student's t inverse cumulative distribution function (InvCDF).
340 
341 Params:
342     p = value to evaluate InvCDF
343     nu = degrees of freedom
344 
345 See_also:
346     $(LINK2 https://en.wikipedia.org/wiki/Student%27s_t-distribution, Student's t Distribution)
347 +/
348 @safe pure nothrow @nogc
349 T studentsTInvCDF(T)(const T p, const T nu)
350     if (isFloatingPoint!T)
351     in (p >= 0, "p must be greater than or equal to 0")
352     in (p <= 1, "p must be less than or equal to 1")
353     in (nu > 0, "nu must be greater than zero")
354 {
355     import mir.math.common: sqrt;
356     import mir.stat.distribution.beta: betaInvCDF;
357     import mir.stat.distribution.normal: normalInvCDF;
358 
359     if (p == 0) {
360         return -T.infinity;
361     } else if (p == 1) {
362         return T.infinity;
363     } else if (nu != T.infinity) {
364         byte output_sign = void;
365         T p_new = void;
366         T output = void;
367         if (p > 0.25 && p < 0.75) {
368             if (p == 0.5) {
369                 return 0;
370             }
371             output_sign = -1;
372             p_new = 1 - 2 * p;
373             if (p > 0.5) {
374                 output_sign = 1;
375                 p_new *= -1;
376             }
377             output = betaInvCDF(p_new, 0.5, 0.5 * nu);
378             output = sqrt(nu * output / (1 - output));
379         } else {
380             output_sign = -1;
381             p_new = p;
382             if (p_new > 0.5) {
383                 output_sign = 1;
384                 p_new = 1 - p_new;
385             }
386             p_new *= 2;
387             output = betaInvCDF(p_new, 0.5 * nu, 0.5);
388             output = sqrt(nu / output - nu);
389         }
390         return output_sign * output;
391     } else {
392         return normalInvCDF(p);
393     }
394 }
395 
396 /++
397 Ditto, with location and scale parameters (by standardizing `x`).
398 
399 Params:
400     p = value to evaluate InvCDF
401     nu = degrees of freedom
402     mean = location parameter
403     stdDev = scale parameter
404 +/
405 @safe pure nothrow @nogc
406 T studentsTInvCDF(T)(const T p, const T nu, const T mean, const T stdDev)
407     if (isFloatingPoint!T)
408     in (p >= 0, "p must be greater than or equal to 0")
409     in (p <= 1, "p must be less than or equal to 1")
410     in (nu > 0, "nu must be greater than zero")
411     in (stdDev > 0, "stdDev must be greater than zero")
412 {
413     return mean + stdDev * studentsTInvCDF(p, nu);
414 }
415 
416 ///
417 version(mir_stat_test)
418 @safe pure nothrow @nogc
419 unittest {
420     import mir.test: shouldApprox;
421 
422     studentsTInvCDF(0.0, 5).shouldApprox == -double.infinity;
423     studentsTInvCDF(0.1, 5).shouldApprox == -1.475884;
424     studentsTInvCDF(0.2, 5).shouldApprox == -0.9195438;
425     studentsTInvCDF(0.3, 5).shouldApprox == -0.5594296;
426     studentsTInvCDF(0.4, 5).shouldApprox == -0.2671809;
427     studentsTInvCDF(0.5, 5).shouldApprox == 0.0;
428     studentsTInvCDF(0.6, 5).shouldApprox == 0.2671809;
429     studentsTInvCDF(0.7, 5).shouldApprox == 0.5594296;
430     studentsTInvCDF(0.8, 5).shouldApprox == 0.9195438;
431     studentsTInvCDF(0.9, 5).shouldApprox == 1.475884;
432     studentsTInvCDF(1.0, 5).shouldApprox == double.infinity;
433 
434     // Can include location/scale
435     studentsTInvCDF(0.2, 5, 1, 2).shouldApprox == -0.8390876;
436     studentsTInvCDF(0.4, 5, 1, 2).shouldApprox == 0.4656382;
437     studentsTInvCDF(0.6, 5, 1, 2).shouldApprox == 1.534362;
438     studentsTInvCDF(0.8, 5, 1, 2).shouldApprox == 2.839088;
439 }
440 
441 // Checking other DoF
442 version(mir_stat_test)
443 @safe pure nothrow @nogc
444 unittest {
445     import mir.test: shouldApprox;
446 
447     studentsTInvCDF(0.1, 25).shouldApprox == -1.316345;
448     studentsTInvCDF(0.2, 25).shouldApprox == -0.8562362;
449     studentsTInvCDF(0.3, 25).shouldApprox == -0.5311538;
450     studentsTInvCDF(0.7, 25).shouldApprox == 0.5311538;
451     studentsTInvCDF(0.8, 25).shouldApprox == 0.8562362;
452     studentsTInvCDF(0.9, 25).shouldApprox == 1.316345;
453 
454     studentsTInvCDF(0.2, double.infinity).shouldApprox == -0.8416212;
455     studentsTInvCDF(0.4, double.infinity).shouldApprox == -0.2533471;
456     studentsTInvCDF(0.6, double.infinity).shouldApprox == 0.2533471;
457     studentsTInvCDF(0.8, double.infinity).shouldApprox == 0.8416212;
458 }
459 
460 // Checking negative location parameter
461 version(mir_stat_test)
462 @safe pure nothrow @nogc
463 unittest {
464     import mir.test: shouldApprox;
465 
466     studentsTInvCDF(0.2, 5, -1, 2).shouldApprox == -2.839088;
467     studentsTInvCDF(0.4, 5, -1, 2).shouldApprox == -1.534362;
468     studentsTInvCDF(0.6, 5, -1, 2).shouldApprox == -0.4656383;
469     studentsTInvCDF(0.8, 5, -1, 2).shouldApprox == 0.8390876;
470 }
471 
472 
473 /++
474 Computes the Student's t log probability density function (LPDF).
475 
476 Params:
477     x = value to evaluate LPDF
478     nu = degrees of freedom
479 
480 See_also:
481     $(LINK2 https://en.wikipedia.org/wiki/Student%27s_t-distribution, Student's t Distribution)
482 +/
483 @safe pure nothrow @nogc
484 T studentsTLPDF(T)(const T x, const T nu)
485     if (isFloatingPoint!T)
486     in (nu > 0, "nu must be greater than zero")
487 {
488     import mir.math.common: log;
489     import mir.math.internal.log1p: log1p;
490     import mir.stat.constant: LOGPI;
491     import mir.stat.distribution.normal: normalLPDF;
492     import std.mathspecial: logGamma;
493 
494     if (nu != T.infinity) {
495         return logGamma((nu + 1) * 0.5) - logGamma(nu * 0.5) - 0.5 * (log(nu) + T(LOGPI) + (nu + 1) * log1p((x * x) / nu));
496     } else {
497         return normalLPDF(x);
498     }
499 }
500 
501 /++
502 Ditto, with location and scale parameters (by standardizing `x`).
503 
504 Params:
505     x = value to evaluate LPDF
506     nu = degrees of freedom
507     mean = location parameter
508     stdDev = scale parameter
509 +/
510 @safe pure nothrow @nogc
511 T studentsTLPDF(T)(const T x, const T nu, const T mean, const T stdDev)
512     if (isFloatingPoint!T)
513     in (nu > 0, "nu must be greater than zero")
514     in (stdDev > 0, "stdDev must be greater than zero")
515 {
516     return studentsTLPDF((x - mean) / stdDev, nu);
517 }
518 
519 ///
520 version(mir_stat_test)
521 @safe pure nothrow @nogc
522 unittest {
523     import mir.test: shouldApprox;
524     import mir.math.common: log;
525 
526     studentsTLPDF(-3.0, 5).shouldApprox == log(0.01729258);
527     studentsTLPDF(-2.0, 5).shouldApprox == log(0.06509031);
528     studentsTLPDF(-1.0, 5).shouldApprox == log(0.2196798);
529     studentsTLPDF(0.0, 5).shouldApprox == log(0.3796067);
530     studentsTLPDF(1.0, 5).shouldApprox == log(0.2196798);
531     studentsTLPDF(2.0, 5).shouldApprox == log(0.06509031);
532     studentsTLPDF(3.0, 5).shouldApprox == log(0.01729258);
533 
534     // Can include location/scale
535     studentsTLPDF(-3.0, 5, 1, 2).shouldApprox == log(0.06509031);
536     studentsTLPDF(-2.0, 5, 1, 2).shouldApprox == log(0.1245173);
537     studentsTLPDF(-1.0, 5, 1, 2).shouldApprox == log(0.2196798);
538     studentsTLPDF(0.0, 5, 1, 2).shouldApprox == log(0.3279185);
539     studentsTLPDF(1.0, 5, 1, 2).shouldApprox == log(0.3796067);
540     studentsTLPDF(2.0, 5, 1, 2).shouldApprox == log(0.3279185);
541     studentsTLPDF(3.0, 5, 1, 2).shouldApprox == log(0.2196798);
542 }
543 
544 // Checking other DoF
545 version(mir_stat_test)
546 @safe pure nothrow @nogc
547 unittest {
548     import mir.test: shouldApprox;
549     import mir.math.common: log;
550 
551     studentsTLPDF(-3.0, 25).shouldApprox == log(0.007253748);
552     studentsTLPDF(-2.0, 25).shouldApprox == log(0.0573607);
553     studentsTLPDF(-1.0, 25).shouldApprox == log(0.237211);
554     studentsTLPDF(0.0, 25).shouldApprox == log(0.3949738);
555     studentsTLPDF(1.0, 25).shouldApprox == log(0.237211);
556     studentsTLPDF(2.0, 25).shouldApprox == log(0.0573607);
557     studentsTLPDF(3.0, 25).shouldApprox == log(0.007253748);
558 
559     studentsTLPDF(-3.0, double.infinity).shouldApprox == log(0.004431848);
560     studentsTLPDF(-2.0, double.infinity).shouldApprox == log(0.05399097);
561     studentsTLPDF(-1.0, double.infinity).shouldApprox == log(0.2419707);
562     studentsTLPDF(0.0, double.infinity).shouldApprox == log(0.3989423);
563     studentsTLPDF(1.0, double.infinity).shouldApprox == log(0.2419707);
564     studentsTLPDF(2.0, double.infinity).shouldApprox == log(0.05399097);
565     studentsTLPDF(3.0, double.infinity).shouldApprox == log(0.004431848);
566 }
567 
568 // Checking negative location parameter
569 version(mir_stat_test)
570 @safe pure nothrow @nogc
571 unittest {
572     import mir.test: shouldApprox;
573     import mir.math.common: log;
574 
575     studentsTLPDF(-3.0, 5, -1, 2).shouldApprox == log(0.2196798);
576     studentsTLPDF(-2.0, 5, -1, 2).shouldApprox == log(0.3279185);
577     studentsTLPDF(-1.0, 5, -1, 2).shouldApprox == log(0.3796067);
578     studentsTLPDF(0.0, 5, -1, 2).shouldApprox == log(0.3279185);
579     studentsTLPDF(1.0, 5, -1, 2).shouldApprox == log(0.2196798);
580     studentsTLPDF(2.0, 5, -1, 2).shouldApprox == log(0.1245173);
581     studentsTLPDF(3.0, 5, -1, 2).shouldApprox == log(0.06509031);
582 }