Failed Conditions
Pull Request — master (#3876)
by Abdul Malik
22:45 queued 13:31
created

GammaBase   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 379
Duplicated Lines 0 %

Test Coverage

Coverage 91.2%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 41
eloc 194
dl 0
loc 379
ccs 114
cts 125
cp 0.912
rs 9.1199
c 1
b 0
f 0

How to fix   Complexity   

Complex Class

Complex classes like GammaBase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GammaBase, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
6
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
7
8
abstract class GammaBase
9
{
10
    private const LOG_GAMMA_X_MAX_VALUE = 2.55e305;
11
12
    private const EPS = 2.22e-16;
13
14
    private const MAX_VALUE = 1.2e308;
15
16
    private const SQRT2PI = 2.5066282746310005024157652848110452530069867406099;
17
18
    private const MAX_ITERATIONS = 256;
19
20 11
    protected static function calculateDistribution(float $value, float $a, float $b, bool $cumulative): float
21
    {
22 11
        if ($cumulative) {
23 8
            return self::incompleteGamma($a, $value / $b) / self::gammaValue($a);
24
        }
25
26 7
        return (1 / ($b ** $a * self::gammaValue($a))) * $value ** ($a - 1) * exp(0 - ($value / $b));
27
    }
28
29
    /** @return float|string */
30 4
    protected static function calculateInverse(float $probability, float $alpha, float $beta)
31
    {
32 4
        $xLo = 0;
33 4
        $xHi = $alpha * $beta * 5;
34
35 4
        $dx = 1024;
36 4
        $x = $xNew = 1;
37 4
        $i = 0;
38
39 4
        while ((abs($dx) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) {
40
            // Apply Newton-Raphson step
41 4
            $result = self::calculateDistribution($x, $alpha, $beta, true);
42 4
            if (!is_float($result)) {
43
                return ExcelError::NA();
44
            }
45 4
            $error = $result - $probability;
46
47 4
            if ($error == 0.0) {
48 3
                $dx = 0;
49 4
            } elseif ($error < 0.0) {
50 4
                $xLo = $x;
51
            } else {
52 4
                $xHi = $x;
53
            }
54
55 4
            $pdf = self::calculateDistribution($x, $alpha, $beta, false);
56
            // Avoid division by zero
57 4
            if (!is_float($pdf)) {
58
                return ExcelError::NA();
59
            }
60 4
            if ($pdf !== 0.0) {
61 4
                $dx = $error / $pdf;
62 4
                $xNew = $x - $dx;
63
            }
64
65
            // If the NR fails to converge (which for example may be the
66
            // case if the initial guess is too rough) we apply a bisection
67
            // step to determine a more narrow interval around the root.
68 4
            if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) {
69 4
                $xNew = ($xLo + $xHi) / 2;
70 4
                $dx = $xNew - $x;
71
            }
72 4
            $x = $xNew;
73
        }
74
75 4
        if ($i === self::MAX_ITERATIONS) {
76
            return ExcelError::NA();
77
        }
78
79 4
        return $x;
80
    }
81
82
    //
83
    //    Implementation of the incomplete Gamma function
84
    //
85 37
    public static function incompleteGamma(float $a, float $x): float
86
    {
87 37
        static $max = 32;
88 37
        $summer = 0;
89 37
        for ($n = 0; $n <= $max; ++$n) {
90 37
            $divisor = $a;
91 37
            for ($i = 1; $i <= $n; ++$i) {
92 37
                $divisor *= ($a + $i);
93
            }
94 37
            $summer += ($x ** $n / $divisor);
95
        }
96
97 37
        return $x ** $a * exp(0 - $x) * $summer;
98
    }
99
100
    //
101
    //    Implementation of the Gamma function
102
    //
103 78
    public static function gammaValue(float $value): float
104
    {
105 78
        if ($value == 0.0) {
106
            return 0;
107
        }
108
109 78
        static $p0 = 1.000000000190015;
110 78
        static $p = [
111 78
            1 => 76.18009172947146,
112 78
            2 => -86.50532032941677,
113 78
            3 => 24.01409824083091,
114 78
            4 => -1.231739572450155,
115 78
            5 => 1.208650973866179e-3,
116 78
            6 => -5.395239384953e-6,
117 78
        ];
118
119 78
        $y = $x = $value;
120 78
        $tmp = $x + 5.5;
121 78
        $tmp -= ($x + 0.5) * log($tmp);
122
123 78
        $summer = $p0;
124 78
        for ($j = 1; $j <= 6; ++$j) {
125 78
            $summer += ($p[$j] / ++$y);
126
        }
127
128 78
        return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x));
129
    }
130
131
    private const LG_D1 = -0.5772156649015328605195174;
132
133
    private const LG_D2 = 0.4227843350984671393993777;
134
135
    private const LG_D4 = 1.791759469228055000094023;
136
137
    private const LG_P1 = [
138
        4.945235359296727046734888,
139
        201.8112620856775083915565,
140
        2290.838373831346393026739,
141
        11319.67205903380828685045,
142
        28557.24635671635335736389,
143
        38484.96228443793359990269,
144
        26377.48787624195437963534,
145
        7225.813979700288197698961,
146
    ];
147
148
    private const LG_P2 = [
149
        4.974607845568932035012064,
150
        542.4138599891070494101986,
151
        15506.93864978364947665077,
152
        184793.2904445632425417223,
153
        1_088_204.76946882876749847,
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_STRING, expecting ',' or ']' on line 153 at column 9
Loading history...
154
        3_338_152.967987029735917223,
155
        5_106_661.678927352456275255,
156
        3_074_109.054850539556250927,
157
    ];
158
159
    private const LG_P4 = [
160
        14745.02166059939948905062,
161
        2_426_813.369486704502836312,
162
        121_475_557.4045093227939592,
163
        2_663_432_449.630976949898078,
164
        29_403_789_566.34553899906876,
165
        170_266_573_776.5398868392998,
166
        492_612_579_337.743088758812,
167
        560_625_185_622.3951465078242,
168
    ];
169
170
    private const LG_Q1 = [
171
        67.48212550303777196073036,
172
        1113.332393857199323513008,
173
        7738.757056935398733233834,
174
        27639.87074403340708898585,
175
        54993.10206226157329794414,
176
        61611.22180066002127833352,
177
        36351.27591501940507276287,
178
        8785.536302431013170870835,
179
    ];
180
181
    private const LG_Q2 = [
182
        183.0328399370592604055942,
183
        7765.049321445005871323047,
184
        133190.3827966074194402448,
185
        1_136_705.821321969608938755,
186
        5_267_964.117437946917577538,
187
        13_467_014.54311101692290052,
188
        17_827_365.30353274213975932,
189
        9_533_095.591844353613395747,
190
    ];
191
192
    private const LG_Q4 = [
193
        2690.530175870899333379843,
194
        639388.5654300092398984238,
195
        41_355_999.30241388052042842,
196
        1_120_872_109.61614794137657,
197
        14_886_137_286.78813811542398,
198
        101_680_358_627.2438228077304,
199
        341_747_634_550.7377132798597,
200
        446_315_818_741.9713286462081,
201
    ];
202
203
    private const LG_C = [
204
        -0.001910444077728,
205
        8.4171387781295e-4,
206
        -5.952379913043012e-4,
207
        7.93650793500350248e-4,
208
        -0.002777777777777681622553,
209
        0.08333333333333333331554247,
210
        0.0057083835261,
211
    ];
212
213
    // Rough estimate of the fourth root of logGamma_xBig
214
    private const LG_FRTBIG = 2.25e76;
215
216
    private const PNT68 = 0.6796875;
217
218
    // Function cache for logGamma
219
220
    private static float $logGammaCacheResult = 0.0;
221
222
    private static float $logGammaCacheX = 0.0;
223
224
    /**
225
     * logGamma function.
226
     *
227
     * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher.
228
     *
229
     * The natural logarithm of the gamma function. <br />
230
     * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz <br />
231
     * Applied Mathematics Division <br />
232
     * Argonne National Laboratory <br />
233
     * Argonne, IL 60439 <br />
234
     * <p>
235
     * References:
236
     * <ol>
237
     * <li>W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural
238
     *     Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.</li>
239
     * <li>K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.</li>
240
     * <li>Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.</li>
241
     * </ol>
242
     * </p>
243
     * <p>
244
     * From the original documentation:
245
     * </p>
246
     * <p>
247
     * This routine calculates the LOG(GAMMA) function for a positive real argument X.
248
     * Computation is based on an algorithm outlined in references 1 and 2.
249
     * The program uses rational functions that theoretically approximate LOG(GAMMA)
250
     * to at least 18 significant decimal digits. The approximation for X > 12 is from
251
     * reference 3, while approximations for X < 12.0 are similar to those in reference
252
     * 1, but are unpublished. The accuracy achieved depends on the arithmetic system,
253
     * the compiler, the intrinsic functions, and proper selection of the
254
     * machine-dependent constants.
255
     * </p>
256
     * <p>
257
     * Error returns: <br />
258
     * The program returns the value XINF for X .LE. 0.0 or when overflow would occur.
259
     * The computation is believed to be free of underflow and overflow.
260
     * </p>
261
     *
262
     * @version 1.1
263
     *
264
     * @author Jaco van Kooten
265
     *
266
     * @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305
267
     */
268 20
    public static function logGamma(float $x): float
269
    {
270 20
        if ($x == self::$logGammaCacheX) {
271 1
            return self::$logGammaCacheResult;
272
        }
273
274 20
        $y = $x;
275 20
        if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) {
276 20
            if ($y <= self::EPS) {
277
                $res = -log($y);
278 20
            } elseif ($y <= 1.5) {
279 4
                $res = self::logGamma1($y);
280 17
            } elseif ($y <= 4.0) {
281 8
                $res = self::logGamma2($y);
282 16
            } elseif ($y <= 12.0) {
283 16
                $res = self::logGamma3($y);
284
            } else {
285 20
                $res = self::logGamma4($y);
286
            }
287
        } else {
288
            // --------------------------
289
            //    Return for bad arguments
290
            // --------------------------
291
            $res = self::MAX_VALUE;
292
        }
293
294
        // ------------------------------
295
        //    Final adjustments and return
296
        // ------------------------------
297 20
        self::$logGammaCacheX = $x;
298 20
        self::$logGammaCacheResult = $res;
299
300 20
        return $res;
301
    }
302
303 4
    private static function logGamma1(float $y): float
304
    {
305
        // ---------------------
306
        //    EPS .LT. X .LE. 1.5
307
        // ---------------------
308 4
        if ($y < self::PNT68) {
309 3
            $corr = -log($y);
310 3
            $xm1 = $y;
311
        } else {
312 3
            $corr = 0.0;
313 3
            $xm1 = $y - 1.0;
314
        }
315
316 4
        $xden = 1.0;
317 4
        $xnum = 0.0;
318 4
        if ($y <= 0.5 || $y >= self::PNT68) {
319 4
            for ($i = 0; $i < 8; ++$i) {
320 4
                $xnum = $xnum * $xm1 + self::LG_P1[$i];
321 4
                $xden = $xden * $xm1 + self::LG_Q1[$i];
322
            }
323
324 4
            return $corr + $xm1 * (self::LG_D1 + $xm1 * ($xnum / $xden));
325
        }
326
327
        $xm2 = $y - 1.0;
328
        for ($i = 0; $i < 8; ++$i) {
329
            $xnum = $xnum * $xm2 + self::LG_P2[$i];
330
            $xden = $xden * $xm2 + self::LG_Q2[$i];
331
        }
332
333
        return $corr + $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
334
    }
335
336 8
    private static function logGamma2(float $y): float
337
    {
338
        // ---------------------
339
        //    1.5 .LT. X .LE. 4.0
340
        // ---------------------
341 8
        $xm2 = $y - 2.0;
342 8
        $xden = 1.0;
343 8
        $xnum = 0.0;
344 8
        for ($i = 0; $i < 8; ++$i) {
345 8
            $xnum = $xnum * $xm2 + self::LG_P2[$i];
346 8
            $xden = $xden * $xm2 + self::LG_Q2[$i];
347
        }
348
349 8
        return $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
350
    }
351
352 16
    protected static function logGamma3(float $y): float
353
    {
354
        // ----------------------
355
        //    4.0 .LT. X .LE. 12.0
356
        // ----------------------
357 16
        $xm4 = $y - 4.0;
358 16
        $xden = -1.0;
359 16
        $xnum = 0.0;
360 16
        for ($i = 0; $i < 8; ++$i) {
361 16
            $xnum = $xnum * $xm4 + self::LG_P4[$i];
362 16
            $xden = $xden * $xm4 + self::LG_Q4[$i];
363
        }
364
365 16
        return self::LG_D4 + $xm4 * ($xnum / $xden);
366
    }
367
368 9
    protected static function logGamma4(float $y): float
369
    {
370
        // ---------------------------------
371
        //    Evaluate for argument .GE. 12.0
372
        // ---------------------------------
373 9
        $res = 0.0;
374 9
        if ($y <= self::LG_FRTBIG) {
375 9
            $res = self::LG_C[6];
376 9
            $ysq = $y * $y;
377 9
            for ($i = 0; $i < 6; ++$i) {
378 9
                $res = $res / $ysq + self::LG_C[$i];
379
            }
380 9
            $res /= $y;
381 9
            $corr = log($y);
382 9
            $res = $res + log(self::SQRT2PI) - 0.5 * $corr;
383 9
            $res += $y * ($corr - 1.0);
384
        }
385
386 9
        return $res;
387
    }
388
}
389