SeriesProvider::maclaurinSeries()   B
last analyzed

Complexity

Conditions 10
Paths 37

Size

Total Lines 81
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 10.1815

Importance

Changes 0
Metric Value
cc 10
dl 0
loc 81
ccs 36
cts 41
cp 0.878
crap 10.1815
rs 7.3333
c 0
b 0
f 0
eloc 45
nc 37
nop 8

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\Core\Provider;
4
5
use ReflectionException;
6
use Samsara\Exceptions\SystemError\PlatformError\MissingPackage;
7
use Samsara\Exceptions\UsageError\IntegrityConstraint;
8
use Samsara\Exceptions\UsageError\OptionalExit;
9
use Samsara\Fermat\Core\Enums\NumberBase;
10
use Samsara\Fermat\Core\Numbers;
11
use Samsara\Fermat\Core\Types\Base\Interfaces\Callables\ContinuedFractionTermInterface;
12
use Samsara\Fermat\Core\Types\Base\Interfaces\Numbers\SimpleNumberInterface;
13
use Samsara\Fermat\Core\Values\ImmutableDecimal;
14
15
/**
16
 *
17
 */
18
class SeriesProvider
19
{
20
21
    public const SUM_MODE_ADD = 1;
22
    public const SUM_MODE_SUB = 2;
23
    public const SUM_MODE_ALT_ADD = 3;
24
    public const SUM_MODE_ALT_SUB = 4;
25
    public const SUM_MODE_ALT_FIRST_ADD = 5;
26
    public const SUM_MODE_ALT_FIRST_SUB = 6;
27
28
    /**
29
     * Creates a series that evaluates the following:
30
     *
31
     * SUM[$startTerm -> infinity](
32
     *  $numerator($n) × $input^$exponent($n)
33
     *  --------------------------------
34
     *          $denominator($n)
35
     * )
36
     *
37
     * Where $n is the current term number, starting at $startTerm, and increasing by 1 each loop; where $numerator,
38
     * $exponent, and $denominator are callables that take the term number (as an int) as their only input, and give the
39
     * value of that section at that term number; and where $input is the x value being considered for the series.
40
     *
41
     * The function continues adding terms until a term has MORE leading zeros than the $scale setting. (That is,
42
     * until it adds zero to the total when considering significant digits.)
43
     *
44
     * @param SimpleNumberInterface $input
45
     * @param callable $numerator
46
     * @param callable $exponent
47
     * @param callable $denominator
48
     * @param int $startTermAt
49
     * @param int $scale
50
     * @param int $consecutiveDivergeLimit
51
     * @param int $totalDivergeLimit
52
     *
53
     * @return ImmutableDecimal
54
     * @throws IntegrityConstraint
55
     * @throws OptionalExit
56
     * @throws ReflectionException
57
     */
58 388
    public static function maclaurinSeries(
59
        SimpleNumberInterface $input, // x value in series
60
        callable $numerator, // a function determining what the sign (+/-) is at the nth term
61
        callable $exponent, // a function determining the exponent of x at the nth term
62
        callable $denominator, // a function determining the denominator at the nth term
63
        int $startTermAt = 0,
64
        int $scale = 10,
65
        int $consecutiveDivergeLimit = 5,
66
        int $totalDivergeLimit = 10): ImmutableDecimal
67
    {
68
69 388
        ++$scale;
70
71 388
        $sum = Numbers::makeZero($scale);
72 388
        $value = Numbers::make(Numbers::IMMUTABLE, $input->getValue(NumberBase::Ten), $scale);
0 ignored issues
show
Unused Code introduced by
The call to Samsara\Fermat\Core\Type...erInterface::getValue() has too many arguments starting with Samsara\Fermat\Core\Enums\NumberBase::Ten. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

72
        $value = Numbers::make(Numbers::IMMUTABLE, $input->/** @scrutinizer ignore-call */ getValue(NumberBase::Ten), $scale);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
73
74 388
        $continue = true;
75 388
        $termNumber = $startTermAt;
76
77 388
        $adjustmentOfZero = 0;
78 388
        $prevDiff = Numbers::makeZero($scale);
79 388
        $prevSum = $sum;
80 388
        $divergeCount = -1;
81 388
        $persistentDivergeCount = -1;
82 388
        $currentScale = 0;
83
84 388
        while ($continue) {
85 388
            $term = Numbers::makeOne($scale);
86
87
            try {
88 388
                $exTerm = $value->pow($exponent($termNumber));
89 388
                $term = $term->multiply($exTerm);
90 388
                $term = $term->divide($denominator($termNumber), $scale);
91 388
                $term = $term->multiply($numerator($termNumber));
92
            } catch (IntegrityConstraint $constraint) {
93
                return $sum->truncateToScale($currentScale+1);
94
            }
95
96
            /** @var ImmutableDecimal $term */
97 388
            if ($term->numberOfLeadingZeros() >= $scale && !$term->isWhole()) {
98
                $continue = false;
99
            }
100
101 388
            $currentScale = $term->numberOfLeadingZeros();
102
103 388
            if ($term->isEqual(0)) {
104 388
                $adjustmentOfZero++;
105
            } else {
106 388
                $adjustmentOfZero = 0;
107
            }
108
109 388
            if ($adjustmentOfZero > 15) {
110 388
                $continue = false;
111
            }
112
113
            /** @var ImmutableDecimal $sum */
114 388
            $sum = $sum->add($term);
115 388
            $currDiff = $sum->subtract($prevSum)->abs();
116
117 388
            if ($prevDiff->isLessThan($currDiff)) {
118 388
                $divergeCount++;
119 388
                $persistentDivergeCount++;
120
            } else {
121 388
                $divergeCount = 0;
122
            }
123
124 388
            if ($divergeCount === $consecutiveDivergeLimit || $persistentDivergeCount === $totalDivergeLimit) {
125
                throw new OptionalExit(
126
                    'Series appear to be diverging. Current diverge count: '.$divergeCount.' | Persistent diverge count: '.$persistentDivergeCount,
127
                    'A call was made to SeriesProvider::maclaurinSeries() that seems to be diverging. Exiting the loop.',
128
                    'The series being calculated appears to be diverging, and the process has been stopped in an attempt to avoid an infinite loop.'
129
                );
130
            }
131
132 388
            $prevDiff = $currDiff;
133 388
            $prevSum = $sum;
134
135 388
            $termNumber++;
136
        }
137
138 388
        return $sum->roundToScale($scale);
139
140
    }
141
142
    /**
143
     * This function processes a generalized continued fraction. In order to use this you must provide
144
     * two callable classes that implement the ContinuedFractionTermInterface. This interface defines the
145
     * expected inputs and outputs of the callable used by this function.
146
     *
147
     * This function evaluates continued fractions in the form:
148
     *
149
     * b0 + (a1 / (b1 + (a2 / (b2 + (a3 / b3 + ...)))))
150
     *
151
     * This is a continued fraction in the form used in complex analysis, referred to as a generalized continued fraction.
152
     *
153
     * For more information about this, please read the wikipedia article on the subject:
154
     *
155
     * [https://en.wikipedia.org/wiki/Generalized_continued_fraction](https://en.wikipedia.org/wiki/Generalized_continued_fraction)
156
     *
157
     * @param ContinuedFractionTermInterface $aPart
158
     * @param ContinuedFractionTermInterface $bPart
159
     * @param int $terms
160
     * @param int $scale
161
     * @param int $sumMode
162
     * @return ImmutableDecimal
163
     * @throws IntegrityConstraint
164
     * @throws MissingPackage
165
     */
166 872
    public static function generalizedContinuedFraction(
167
        ContinuedFractionTermInterface $aPart,
168
        ContinuedFractionTermInterface $bPart,
169
        int $terms,
170
        int $scale,
171
        int $sumMode = self::SUM_MODE_ADD
172
    ): ImmutableDecimal
173
    {
174
175 872
        $intScale = $scale;
176 872
        $start = $bPart(0);
177 872
        $prevDenominator = new ImmutableDecimal(0, $intScale);
178 872
        $loop = 0;
179
180 872
        for ($i = $terms;$i > 0;$i--) {
181 872
            $loop++;
182 872
            switch ($sumMode) {
183
                case self::SUM_MODE_ADD:
184
                case self::SUM_MODE_ALT_FIRST_SUB:
185 616
                    $prevDenominator = $bPart($i)->add($prevDenominator);
186 616
                    break;
187
188
                case self::SUM_MODE_SUB:
189
                case self::SUM_MODE_ALT_FIRST_ADD:
190 256
                    $prevDenominator = $bPart($i)->subtract($prevDenominator);
191 256
                    break;
192
            }
193
194 872
            if ($prevDenominator->isEqual(0)) {
195
                throw new IntegrityConstraint(
196
                    'Cannot divide by zero',
197
                    'balh',
198
                    'Current $i: '.$i.' | Current terms: '.$terms.' | Loop: '.$loop
199
                );
200
            } else {
201 872
                $prevDenominator = $aPart($i)->divide($prevDenominator, $intScale);
202
            }
203
        }
204
205 872
        switch ($sumMode) {
206
            case self::SUM_MODE_SUB:
207
            case self::SUM_MODE_ALT_FIRST_SUB:
208 80
                $result = $start->subtract($prevDenominator);
209 80
                break;
210
211
            case self::SUM_MODE_ALT_FIRST_ADD:
212
            case self::SUM_MODE_ADD:
213 792
                $result = $start->add($prevDenominator);
214 792
                break;
215
        }
216
217 872
        return $result;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
218
219
    }
220
    
221
}