Passed
Push — dev ( c3b672...0c0d88 )
by Jordan
04:34 queued 03:46
created

SeriesProvider   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 114
Duplicated Lines 0 %

Test Coverage

Coverage 86.05%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 46
c 1
b 0
f 0
dl 0
loc 114
ccs 37
cts 43
cp 0.8605
rs 10
wmc 10

1 Method

Rating   Name   Duplication   Size   Complexity  
B maclaurinSeries() 0 81 10
1
<?php
2
3
namespace Samsara\Fermat\Provider;
4
5
use ReflectionException;
6
use Samsara\Exceptions\UsageError\IntegrityConstraint;
7
use Samsara\Exceptions\UsageError\OptionalExit;
8
use Samsara\Fermat\Numbers;
9
use Samsara\Fermat\Types\Base\Interfaces\Numbers\SimpleNumberInterface;
10
use Samsara\Fermat\Values\ImmutableDecimal;
11
12
class SeriesProvider
13
{
14
15
    /**
16
     * Creates a series that evaluates the following:
17
     *
18
     * SUM[$startTerm -> infinity](
19
     *  $numerator($n) × $input^$exponent($n)
20
     *  --------------------------------
21
     *          $denominator($n)
22
     * )
23
     *
24
     * Where $n is the current term number, starting at $startTerm, and increasing by 1 each loop; where $numerator,
25
     * $exponent, and $denominator are callables that take the term number (as an int) as their only input, and give the
26
     * value of that section at that term number; and where $input is the x value being considered for the series.
27
     *
28
     * The function continues adding terms until a term has MORE leading zeros than the $scale setting. (That is,
29
     * until it adds zero to the total when considering significant digits.)
30
     *
31
     * @param SimpleNumberInterface $input
32
     * @param callable $numerator
33
     * @param callable $exponent
34
     * @param callable $denominator
35
     * @param int $startTermAt
36
     * @param int $scale
37
     * @param int $consecutiveDivergeLimit
38
     * @param int $totalDivergeLimit
39
     *
40
     * @return ImmutableDecimal
41
     * @throws IntegrityConstraint
42
     * @throws OptionalExit
43
     * @throws ReflectionException
44
     */
45 18
    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 $scale = 10,
52
        int $consecutiveDivergeLimit = 5,
53
        int $totalDivergeLimit = 10): ImmutableDecimal
54
    {
55
56 18
        ++$scale;
57
58 18
        $sum = Numbers::makeZero($scale);
59 18
        $value = Numbers::make(Numbers::IMMUTABLE, $input->getValue(), $scale);
60
61 18
        $continue = true;
62 18
        $termNumber = $startTermAt;
63
64 18
        $adjustmentOfZero = 0;
65 18
        $prevDiff = Numbers::makeZero($scale);
66 18
        $prevSum = $sum;
67 18
        $divergeCount = -1;
68 18
        $persistentDivergeCount = -1;
69 18
        $currentScale = 0;
70
71 18
        while ($continue) {
72 18
            $term = Numbers::makeOne($scale);
73
74
            try {
75 18
                $exTerm = $value->pow($exponent($termNumber));
76 18
                $term = $term->multiply($exTerm);
77 18
                $term = $term->divide($denominator($termNumber));
78 18
                $term = $term->multiply($numerator($termNumber));
79
            } catch (IntegrityConstraint $constraint) {
80
                return $sum->truncateToScale($currentScale+1);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sum->truncateToScale($currentScale + 1) returns the type Samsara\Fermat\Types\Bas...umbers\DecimalInterface which includes types incompatible with the type-hinted return Samsara\Fermat\Values\ImmutableDecimal.
Loading history...
81
            }
82
83
            /** @var ImmutableDecimal $term */
84 18
            if ($term->numberOfLeadingZeros() >= $scale && !$term->isWhole()) {
85 12
                $continue = false;
86
            }
87
88 18
            $currentScale = $term->numberOfLeadingZeros();
89
90 18
            if ($term->isEqual(0)) {
91 6
                $adjustmentOfZero++;
92
            } else {
93 18
                $adjustmentOfZero = 0;
94
            }
95
96 18
            if ($adjustmentOfZero > 15) {
97 6
                $continue = false;
98
            }
99
100
            /** @var ImmutableDecimal $sum */
101 18
            $sum = $sum->add($term);
102 18
            $currDiff = $sum->subtract($prevSum)->abs();
103
104 18
            if ($prevDiff->isLessThan($currDiff)) {
105 18
                $divergeCount++;
106 18
                $persistentDivergeCount++;
107
            } else {
108 18
                $divergeCount = 0;
109
            }
110
111 18
            if ($divergeCount === $consecutiveDivergeLimit || $persistentDivergeCount === $totalDivergeLimit) {
112
                throw new OptionalExit(
113
                    'Series appear to be diverging. Current diverge count: '.$divergeCount.' | Persistent diverge count: '.$persistentDivergeCount,
114
                    'A call was made to SeriesProvider::maclaurinSeries() that seems to be diverging. Exiting the loop.',
115
                    'The series being calculated appears to be diverging, and the process has been stopped in an attempt to avoid an infinite loop.'
116
                );
117
            }
118
119 18
            $prevDiff = $currDiff;
120 18
            $prevSum = $sum;
121
122 18
            $termNumber++;
123
        }
124
125 18
        return $sum->roundToScale($scale);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sum->roundToScale($scale) returns the type Samsara\Fermat\Types\Bas...umbers\DecimalInterface which includes types incompatible with the type-hinted return Samsara\Fermat\Values\ImmutableDecimal.
Loading history...
126
127
    }
128
    
129
}