1<?php
2
3namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
4
5use PhpOffice\PhpSpreadsheet\Calculation\Exception;
6use PhpOffice\PhpSpreadsheet\Calculation\Functions;
7
8class StudentT
9{
10    private const MAX_ITERATIONS = 256;
11
12    /**
13     * TDIST.
14     *
15     * Returns the probability of Student's T distribution.
16     *
17     * @param mixed $value Float value for the distribution
18     * @param mixed $degrees Integer value for degrees of freedom
19     * @param mixed $tails Integer value for the number of tails (1 or 2)
20     *
21     * @return float|string The result, or a string containing an error
22     */
23    public static function distribution($value, $degrees, $tails)
24    {
25        $value = Functions::flattenSingleValue($value);
26        $degrees = Functions::flattenSingleValue($degrees);
27        $tails = Functions::flattenSingleValue($tails);
28
29        try {
30            $value = DistributionValidations::validateFloat($value);
31            $degrees = DistributionValidations::validateInt($degrees);
32            $tails = DistributionValidations::validateInt($tails);
33        } catch (Exception $e) {
34            return $e->getMessage();
35        }
36
37        if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) {
38            return Functions::NAN();
39        }
40
41        return self::calculateDistribution($value, $degrees, $tails);
42    }
43
44    /**
45     * TINV.
46     *
47     * Returns the one-tailed probability of the chi-squared distribution.
48     *
49     * @param mixed $probability Float probability for the function
50     * @param mixed $degrees Integer value for degrees of freedom
51     *
52     * @return float|string The result, or a string containing an error
53     */
54    public static function inverse($probability, $degrees)
55    {
56        $probability = Functions::flattenSingleValue($probability);
57        $degrees = Functions::flattenSingleValue($degrees);
58
59        try {
60            $probability = DistributionValidations::validateProbability($probability);
61            $degrees = DistributionValidations::validateInt($degrees);
62        } catch (Exception $e) {
63            return $e->getMessage();
64        }
65
66        if ($degrees <= 0) {
67            return Functions::NAN();
68        }
69
70        $callback = function ($value) use ($degrees) {
71            return self::distribution($value, $degrees, 2);
72        };
73
74        $newtonRaphson = new NewtonRaphson($callback);
75
76        return $newtonRaphson->execute($probability);
77    }
78
79    /**
80     * @return float
81     */
82    private static function calculateDistribution(float $value, int $degrees, int $tails)
83    {
84        //    tdist, which finds the probability that corresponds to a given value
85        //    of t with k degrees of freedom. This algorithm is translated from a
86        //    pascal function on p81 of "Statistical Computing in Pascal" by D
87        //    Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd:
88        //    London). The above Pascal algorithm is itself a translation of the
89        //    fortran algoritm "AS 3" by B E Cooper of the Atlas Computer
90        //    Laboratory as reported in (among other places) "Applied Statistics
91        //    Algorithms", editied by P Griffiths and I D Hill (1985; Ellis
92        //    Horwood Ltd.; W. Sussex, England).
93        $tterm = $degrees;
94        $ttheta = atan2($value, sqrt($tterm));
95        $tc = cos($ttheta);
96        $ts = sin($ttheta);
97
98        if (($degrees % 2) === 1) {
99            $ti = 3;
100            $tterm = $tc;
101        } else {
102            $ti = 2;
103            $tterm = 1;
104        }
105
106        $tsum = $tterm;
107        while ($ti < $degrees) {
108            $tterm *= $tc * $tc * ($ti - 1) / $ti;
109            $tsum += $tterm;
110            $ti += 2;
111        }
112
113        $tsum *= $ts;
114        if (($degrees % 2) == 1) {
115            $tsum = Functions::M_2DIVPI * ($tsum + $ttheta);
116        }
117
118        $tValue = 0.5 * (1 + $tsum);
119        if ($tails == 1) {
120            return 1 - abs($tValue);
121        }
122
123        return 1 - abs((1 - $tValue) - $tValue);
124    }
125}
126