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

NewtonRaphson   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 59
Duplicated Lines 0 %

Test Coverage

Coverage 92.86%

Importance

Changes 0
Metric Value
wmc 12
eloc 30
c 0
b 0
f 0
dl 0
loc 59
ccs 26
cts 28
cp 0.9286
rs 10

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
B execute() 0 46 11
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