Passed
Push — master ( 68fd71...055281 )
by
unknown
16:29 queued 06:03
created

NewtonRaphson::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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
class NewtonRaphson
9
{
10
    private const MAX_ITERATIONS = 256;
11
12
    /** @var callable(float): mixed */
13
    protected $callback;
14
15
    /** @param callable(float): mixed $callback */
16 16
    public function __construct(callable $callback)
17
    {
18 16
        $this->callback = $callback;
19
    }
20
21 16
    public function execute(float $probability): string|int|float
22
    {
23 16
        $xLo = 100;
24 16
        $xHi = 0;
25
26 16
        $dx = 1;
27 16
        $x = $xNew = 1;
28 16
        $i = 0;
29
30 16
        while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) {
31
            // Apply Newton-Raphson step
32 16
            $result = call_user_func($this->callback, $x);
33 16
            if (!is_float($result)) {
34
                return ExcelError::VALUE();
35
            }
36 16
            $error = $result - $probability;
37
38 16
            if ($error == 0.0) {
39 6
                $dx = 0;
40 16
            } elseif ($error < 0.0) {
41 16
                $xLo = $x;
42
            } else {
43 16
                $xHi = $x;
44
            }
45
46
            // Avoid division by zero
47 16
            if ($result != 0.0) {
48 16
                $dx = $error / $result;
49 16
                $xNew = $x - $dx;
50
            }
51
52
            // If the NR fails to converge (which for example may be the
53
            // case if the initial guess is too rough) we apply a bisection
54
            // step to determine a more narrow interval around the root.
55 16
            if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
56 16
                $xNew = ($xLo + $xHi) / 2;
57 16
                $dx = $xNew - $x;
58
            }
59 16
            $x = $xNew;
60
        }
61
62 16
        if ($i == self::MAX_ITERATIONS) {
63
            return ExcelError::NA();
64
        }
65
66 16
        return $x;
67
    }
68
}
69