Failed Conditions
Push — dev ( b97a42...437690 )
by Jordan
05:47 queued 02:36
created

SeriesProvider::maclaurinSeries()   B

Complexity

Conditions 10
Paths 37

Size

Total Lines 77
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 10.1815

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 43
nc 37
nop 8
dl 0
loc 77
ccs 36
cts 41
cp 0.878
crap 10.1815
rs 7.6666
c 1
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Samsara\Fermat\Provider;
4
5
use Samsara\Exceptions\UsageError\IntegrityConstraint;
6
use Samsara\Exceptions\UsageError\OptionalExit;
7
use Samsara\Fermat\Numbers;
8
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
9
use Samsara\Fermat\Types\Base\Interfaces\Numbers\NumberInterface;
10
use Samsara\Fermat\Types\Base\Interfaces\Numbers\SimpleNumberInterface;
11
use Samsara\Fermat\Values\ImmutableDecimal;
12
13
class SeriesProvider
14
{
15
16
    /**
17
     * Creates a series that evaluates the following:
18
     *
19
     * SUM[$startTerm -> infinity](
20
     *  $numerator($n) × $input^$exponent($n)
21
     *  --------------------------------
22
     *          $denominator($n)
23
     * )
24
     *
25
     * Where $n is the current term number, starting at $startTerm, and increasing by 1 each loop; where $numerator,
26
     * $exponent, and $denominator are callables that take the term number (as an int) as their only input, and give the
27
     * value of that section at that term number; and where $input is the x value being considered for the series.
28
     *
29
     * The function continues adding terms until a term has MORE leading zeros than the $precision setting. (That is,
30
     * until it adds zero to the total when considering significant digits.)
31
     *
32
     * @param SimpleNumberInterface $input
33
     * @param callable              $numerator
34
     * @param callable              $exponent
35
     * @param callable              $denominator
36
     * @param int                   $startTermAt
37
     * @param int                   $precision
38
     * @param int                   $consecutiveDivergeLimit
39
     * @param int                   $totalDivergeLimit
40
     *
41
     * @return ImmutableDecimal
42
     * @throws IntegrityConstraint
43
     * @throws OptionalExit
44
     */
45 19
    public static function maclaurinSeries(
46
        SimpleNumberInterface $input, // x value in series
47
        callable $numerator, // a function determining what the sign (+/-) is at the nth term
48
        callable $exponent, // a function determining the exponent of x at the nth term
49
        callable $denominator, // a function determining the denominator at the nth term
50
        int $startTermAt = 0,
51
        int $precision = 10,
52
        int $consecutiveDivergeLimit = 5,
53
        int $totalDivergeLimit = 10)
54
    {
55
56 19
        $sum = Numbers::makeZero($precision);
57 19
        $value = Numbers::make(Numbers::IMMUTABLE, $input->getValue(), $precision);
58
59 19
        $continue = true;
60 19
        $termNumber = $startTermAt;
61
62 19
        $adjustmentOfZero = 0;
63 19
        $prevDiff = Numbers::makeZero($precision);
64 19
        $prevSum = $sum;
65 19
        $divergeCount = -1;
66 19
        $persistentDivergeCount = -1;
67 19
        $currentPrecision = 0;
68
69 19
        while ($continue) {
70 19
            $term = Numbers::makeOne($precision);
71
72
            try {
73 19
                $exTerm = $value->pow($exponent($termNumber));
74 19
                $term = $term->multiply($exTerm);
75 19
                $term = $term->divide($denominator($termNumber));
76 19
                $term = $term->multiply($numerator($termNumber));
77
            } catch (IntegrityConstraint $constraint) {
78
                return $sum->truncateToPrecision($currentPrecision+1);
79
            }
80
81
            /** @var ImmutableDecimal $term */
82 19
            if ($term->numberOfLeadingZeros() >= $precision && !$term->isWhole()) {
83 12
                $continue = false;
84
            }
85
86 19
            $currentPrecision = $term->numberOfLeadingZeros();
87
88 19
            if ($term->isEqual(0)) {
89 8
                $adjustmentOfZero++;
90
            } else {
91 19
                $adjustmentOfZero = 0;
92
            }
93
94 19
            if ($adjustmentOfZero > 5) {
95 8
                $continue = false;
96
            }
97
98 19
            $sum = $sum->add($term);
99 19
            $currDiff = $sum->subtract($prevSum)->abs();
100
101 19
            if ($prevDiff->isLessThan($currDiff)) {
102 19
                $divergeCount++;
103 19
                $persistentDivergeCount++;
104
            } else {
105 19
                $divergeCount = 0;
106
            }
107
108 19
            if ($divergeCount == $consecutiveDivergeLimit || $persistentDivergeCount == $totalDivergeLimit) {
109
                throw new OptionalExit(
110
                    'Series appear to be diverging. Current diverge count: '.$divergeCount.' | Persistent diverge count: '.$persistentDivergeCount,
111
                    'A call was made to SeriesProvider::maclaurinSeries() that seems to be diverging. Exiting the loop.'
112
                );
113
            }
114
115 19
            $prevDiff = $currDiff;
116 19
            $prevSum = $sum;
117
118 19
            $termNumber++;
119
        }
120
121 19
        return $sum->roundToPrecision($precision);
122
123
    }
124
125
    /**
126
     * @param callable $part1
127
     * @param callable $part2
128
     * @param callable $exponent
129
     * @param int $startTermAt
130
     * @param int $precision
131
     *
132
     * @return ImmutableDecimal
133
     */
134
    public static function genericTwoPartSeries(
135
        callable $part1,
136
        callable $part2,
137
        callable $exponent,
138
        int $startTermAt = 0,
139
        int $precision = 10): ImmutableDecimal
140
    {
141
142
        $x = Numbers::makeZero($precision+1);
143
144
        $continue = true;
145
        $termNumber = $startTermAt;
146
147
        while ($continue) {
148
            $term = Numbers::makeOne($precision+1);
149
150
            /** @var ImmutableDecimal $term */
151
            $term = $term->multiply($part2($termNumber))->pow($exponent($termNumber))
152
                ->multiply($part1($termNumber));
153
154
            if ($term->numberOfLeadingZeros()-1 >= $precision) {
155
                $continue = false;
156
            }
157
158
            $x = $x->add($term);
159
160
            $termNumber++;
161
        }
162
163
        return $x->roundToPrecision($precision+1);
164
165
    }
166
    
167
}