Passed
Push — master ( e75397...a94b8a )
by Jordan
06:28
created

SeriesProvider::generalizedContinuedFraction()   B

Complexity

Conditions 11
Paths 35

Size

Total Lines 51
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 11.1043

Importance

Changes 0
Metric Value
eloc 31
nc 35
nop 5
dl 0
loc 51
ccs 19
cts 21
cp 0.9048
c 0
b 0
f 0
cc 11
crap 11.1043
rs 7.3166

How to fix   Long Method    Complexity   

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:

1
<?php
2
3
namespace Samsara\Fermat\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\Enums\NumberBase;
10
use Samsara\Fermat\Numbers;
11
use Samsara\Fermat\Types\Base\Interfaces\Callables\ContinuedFractionTermInterface;
12
use Samsara\Fermat\Types\Base\Interfaces\Numbers\SimpleNumberInterface;
13
use Samsara\Fermat\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 5
    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 5
        ++$scale;
70
71 5
        $sum = Numbers::makeZero($scale);
72 5
        $value = Numbers::make(Numbers::IMMUTABLE, $input->getValue(NumberBase::Ten), $scale);
0 ignored issues
show
Unused Code introduced by
The call to Samsara\Fermat\Types\Bas...erInterface::getValue() has too many arguments starting with Samsara\Fermat\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 5
        $continue = true;
75 5
        $termNumber = $startTermAt;
76
77 5
        $adjustmentOfZero = 0;
78 5
        $prevDiff = Numbers::makeZero($scale);
79 5
        $prevSum = $sum;
80 5
        $divergeCount = -1;
81 5
        $persistentDivergeCount = -1;
82 5
        $currentScale = 0;
83
84 5
        while ($continue) {
85 5
            $term = Numbers::makeOne($scale);
86
87
            try {
88 5
                $exTerm = $value->pow($exponent($termNumber));
89 5
                $term = $term->multiply($exTerm);
90 5
                $term = $term->divide($denominator($termNumber));
91 5
                $term = $term->multiply($numerator($termNumber));
92
            } catch (IntegrityConstraint $constraint) {
93
                return $sum->truncateToScale($currentScale+1);
94
            }
95
96
            /** @var ImmutableDecimal $term */
97 5
            if ($term->numberOfLeadingZeros() >= $scale && !$term->isWhole()) {
98 5
                $continue = false;
99
            }
100
101 5
            $currentScale = $term->numberOfLeadingZeros();
102
103 5
            if ($term->isEqual(0)) {
104 1
                $adjustmentOfZero++;
105
            } else {
106 5
                $adjustmentOfZero = 0;
107
            }
108
109 5
            if ($adjustmentOfZero > 15) {
110 1
                $continue = false;
111
            }
112
113
            /** @var ImmutableDecimal $sum */
114 5
            $sum = $sum->add($term);
115 5
            $currDiff = $sum->subtract($prevSum)->abs();
116
117 5
            if ($prevDiff->isLessThan($currDiff)) {
118 5
                $divergeCount++;
119 5
                $persistentDivergeCount++;
120
            } else {
121 5
                $divergeCount = 0;
122
            }
123
124 5
            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 5
            $prevDiff = $currDiff;
133 5
            $prevSum = $sum;
134
135 5
            $termNumber++;
136
        }
137
138 5
        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 16
    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 16
        $start = $bPart(0);
176 16
        $prevDenominator = new ImmutableDecimal(0, $scale);
177 16
        $loop = 0;
178
179 16
        for ($i = $terms;$i > 0;$i--) {
180 16
            $loop++;
181 16
            switch ($sumMode) {
182
                case self::SUM_MODE_ADD:
183
                case self::SUM_MODE_ALT_FIRST_SUB:
184 12
                    $prevDenominator = $bPart($i)->add($prevDenominator);
185 12
                    break;
186
187
                case self::SUM_MODE_SUB:
188
                case self::SUM_MODE_ALT_FIRST_ADD:
189 4
                    $prevDenominator = $bPart($i)->subtract($prevDenominator);
190 4
                    break;
191
            }
192
193 16
            if ($prevDenominator->isEqual(0)) {
194
                throw new IntegrityConstraint(
195
                    'Cannot divide by zero',
196
                    'balh',
197
                    'Current $i: '.$i.' | Current terms: '.$terms.' | Loop: '.$loop
198
                );
199
            } else {
200 16
                $prevDenominator = $aPart($i)->divide($prevDenominator, $scale);
201
            }
202
        }
203
204 16
        switch ($sumMode) {
205
            case self::SUM_MODE_SUB:
206
            case self::SUM_MODE_ALT_FIRST_SUB:
207 1
                $result = $start->subtract($prevDenominator);
208 1
                break;
209
210
            case self::SUM_MODE_ALT_FIRST_ADD:
211
            case self::SUM_MODE_ADD:
212 15
                $result = $start->add($prevDenominator);
213 15
                break;
214
        }
215
216 16
        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...
217
218
    }
219
    
220
}