Passed
Pull Request — master (#138)
by Jordan
06:29
created

SequenceProvider::nthBernoulliNumber()   B

Complexity

Conditions 10
Paths 21

Size

Total Lines 89
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 50
CRAP Score 10.0056

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 10
eloc 58
c 2
b 0
f 1
nc 21
nop 2
dl 0
loc 89
ccs 50
cts 52
cp 0.9615
crap 10.0056
rs 7.0496

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 Samsara\Exceptions\SystemError\LogicalError\IncompatibleObjectState;
6
use Samsara\Exceptions\SystemError\PlatformError\MissingPackage;
7
use Samsara\Exceptions\UsageError\IntegrityConstraint;
8
use Samsara\Fermat\Enums\CalcMode;
9
use Samsara\Fermat\Enums\NumberBase;
10
use Samsara\Fermat\Numbers;
11
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
12
use Samsara\Fermat\Types\Base\Interfaces\Numbers\NumberInterface;
13
use Samsara\Fermat\Types\NumberCollection;
14
use Samsara\Fermat\Values\ImmutableDecimal;
15
16
/**
17
 *
18
 */
19
class SequenceProvider
20
{
21
22
    const EULER_ZIGZAG = [
23
        '1',                                                        // 0
24
        '1',                                                        // 1
25
        '1',                                                        // 2
26
        '2',                                                        // 3
27
        '5',                                                        // 4
28
        '16',                                                       // 5
29
        '61',                                                       // 6
30
        '272',                                                      // 7
31
        '1385',                                                     // 8
32
        '7936',                                                     // 9
33
        '50521',                                                    // 10
34
        '353792',                                                   // 11
35
        '2702765',                                                  // 12
36
        '22368256',                                                 // 13
37
        '199360981',                                                // 14
38
        '1903757312',                                               // 15
39
        '19391512145',                                              // 16
40
        '209865342976',                                             // 17
41
        '2404879675441',                                            // 18
42
        '29088885112832',                                           // 19
43
        '370371188237525',                                          // 20
44
        '4951498053124096',                                         // 21
45
        '69348874393137901',                                        // 22
46
        '1015423886506852352',                                      // 23
47
        '15514534163557086905',                                     // 24
48
        '246921480190207983616',                                    // 25
49
        '4087072509293123892361',                                   // 26
50
        '70251601603943959887872',                                  // 27
51
        '1252259641403629865468285',                                // 28
52
        '23119184187809597841473536',                               // 29
53
        '441543893249023104553682821',                              // 30
54
        '8713962757125169296170811392',                             // 31
55
        '177519391579539289436664789665',                           // 32
56
        '3729407703720529571097509625856',                          // 33
57
        '80723299235887898062168247453281',                         // 34
58
        '1798651693450888780071750349094912',                       // 35
59
        '41222060339517702122347079671259045',                      // 36
60
        '970982810785059112379399707952152576',                     // 37
61
        '23489580527043108252017828576198947741',                   // 38
62
        '583203324917310043943191641625494290432',                  // 39
63
        '14851150718114980017877156781405826684425',                // 40
64
        '387635983772083031828014624002175135645696',               // 41
65
        '10364622733519612119397957304745185976310201',             // 42
66
        '283727921907431909304183316295787837183229952',            // 43
67
        '7947579422597592703608040510088070619519273805',           // 44
68
        '227681379129930886488600284336316164603920777216',         // 45
69
        '6667537516685544977435028474773748197524107684661',        // 46
70
        '199500252157859031027160499643195658166340757225472',      // 47
71
        '6096278645568542158691685742876843153976539044435185',     // 48
72
        '190169564657928428175235445073924928592047775873499136',   // 49
73
        '6053285248188621896314383785111649088103498225146815121',  // 50
74
    ];
75
76
    /**
77
     * OEIS: A005408
78
     *
79
     * @param int $n
80
     * @param bool $asCollection
81
     * @param int $collectionSize
82
     *
83
     * @return ImmutableDecimal|NumberCollection
84
     * @throws IntegrityConstraint
85
     * @throws MissingPackage
86
     */
87 151
    public static function nthOddNumber(int $n, bool $asCollection = false, int $collectionSize = 10): ImmutableDecimal|NumberCollection
88
    {
89 151
        if ($asCollection) {
90 1
            $sequence = new NumberCollection();
91
92 1
            for ($i = 0;$i < $collectionSize;$i++) {
93 1
                $sequence->push(self::nthOddNumber($n + $i));
94
            }
95
96 1
            return $sequence;
97
        }
98
99 151
        if ($n >= (PHP_INT_MAX/2)) {
100
            $n = new ImmutableDecimal($n, 100);
101
102
            return $n->multiply(2)->add(1);
103
        } else {
104 151
            return new ImmutableDecimal(($n*2)+1, 100);
105
        }
106
107
    }
108
109
    /**
110
     * OEIS: A005843
111
     *
112
     * @param int $n
113
     * @param int|null $scale
114
     * @param bool $asCollection
115
     * @param int $collectionSize
116
     *
117
     * @return ImmutableDecimal|NumberCollection
118
     * @throws IntegrityConstraint
119
     */
120 89
    public static function nthEvenNumber(int $n, int $scale = null, bool $asCollection = false, int $collectionSize = 10): ImmutableDecimal|NumberCollection
121
    {
122
123 89
        if ($asCollection) {
124 1
            $sequence = new NumberCollection();
125
126 1
            for ($i = 0;$i < $collectionSize;$i++) {
127 1
                $sequence->push(self::nthEvenNumber($n + $i));
128
            }
129
130 1
            return $sequence;
131
        }
132 89
        if ($n > (PHP_INT_MAX/2)) {
133
            $n = Numbers::makeOrDont(Numbers::IMMUTABLE, $n, $scale);
134
135
            return $n->multiply(2);
136
        } else {
137 89
            return new ImmutableDecimal(($n*2), $scale);
138
        }
139
140
    }
141
142
    /**
143
     * OEIS: A033999
144
     *
145
     * @param int $n
146
     * @param bool $asCollection
147
     * @param int $collectionSize
148
     *
149
     * @return ImmutableDecimal|NumberCollection
150
     * @throws IntegrityConstraint
151
     */
152 1
    public static function nthPowerNegativeOne(int $n, bool $asCollection = false, int $collectionSize = 10): ImmutableDecimal|NumberCollection
153
    {
154
155 1
        if ($asCollection) {
156 1
            $sequence = new NumberCollection();
157
158 1
            for ($i = 0;$i < $collectionSize;$i++) {
159 1
                $sequence->push(self::nthPowerNegativeOne($n + $i));
160
            }
161
162 1
            return $sequence;
163
        }
164
165 1
        return ($n % 2 ? new ImmutableDecimal(-1) : new ImmutableDecimal(1));
166
167
    }
168
169
    /**
170
     * OEIS: A000111
171
     *
172
     * @param int $n
173
     * @param bool $asCollection
174
     * @param int $collectionSize
175
     *
176
     * @return ImmutableDecimal|NumberCollection
177
     * @throws IntegrityConstraint
178
     */
179 1
    public static function nthEulerZigzag(int $n, bool $asCollection = false, int $collectionSize = 10): ImmutableDecimal|NumberCollection
180
    {
181
182 1
        if ($asCollection) {
183 1
            $sequence = new NumberCollection();
184
185 1
            for ($i = 0;$i < $collectionSize;$i++) {
186 1
                $sequence->push(self::nthEulerZigzag($n + $i));
187
            }
188
189 1
            return $sequence;
190
        }
191
192 1
        if ($n > 50) {
193 1
            throw new IntegrityConstraint(
194
                '$n cannot be larger than 50',
195
                'Limit your use of the Euler Zigzag Sequence to the 50th index',
196
                'This library does not support the Euler Zigzag Sequence (OEIS: A000111) beyond E(50)'
197
            );
198
        }
199
200 1
        return Numbers::make(Numbers::IMMUTABLE, static::EULER_ZIGZAG[$n], 100);
201
202
    }
203
204
    /**
205
     * Returns the nth Bernoulli Number, where odd indexes return zero, and B1() is -1/2.
206
     *
207
     * This function gets very slow if you demand large precision.
208
     *
209
     * @param $n
210
     * @param int|null $scale
211
     * @return ImmutableDecimal
212
     * @throws IncompatibleObjectState
213
     * @throws IntegrityConstraint
214
     * @throws MissingPackage
215
     */
216 1
    public static function nthBernoulliNumber($n, ?int $scale = null): ImmutableDecimal
217
    {
218
219 1
        $scale = $scale ?? 5;
220
221 1
        $internalScale = (int)ceil($scale*(log10($scale)+1));
222
223 1
        $n = Numbers::makeOrDont(Numbers::IMMUTABLE, $n, $internalScale)->setMode(CalcMode::Precision);
0 ignored issues
show
Bug introduced by
The method setMode() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface such as Samsara\Fermat\Types\Base\Number or Samsara\Fermat\Types\Fraction or Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

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

223
        $n = Numbers::makeOrDont(Numbers::IMMUTABLE, $n, $internalScale)->/** @scrutinizer ignore-call */ setMode(CalcMode::Precision);
Loading history...
224
225 1
        if (!$n->isWhole()) {
0 ignored issues
show
Bug introduced by
The method isWhole() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Samsara\Fermat\Types\Bas...s\SimpleNumberInterface or Samsara\Fermat\Types\Base\Number or Samsara\Fermat\Types\Bas...mbers\FractionInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

225
        if (!$n->/** @scrutinizer ignore-call */ isWhole()) {
Loading history...
226
            throw new IntegrityConstraint(
227
                'Only integers may be indexes for Bernoulli numbers',
228
                'Ensure only integers are provided as indexes',
229
                'An attempt was made to get a Bernoulli number with a non-integer index'
230
            );
231
        }
232
233 1
        if ($n->isLessThan(0)) {
0 ignored issues
show
Bug introduced by
The method isLessThan() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Samsara\Fermat\Types\Base\Number. Are you sure you never get one of those? ( Ignorable by Annotation )

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

233
        if ($n->/** @scrutinizer ignore-call */ isLessThan(0)) {
Loading history...
234
            throw new IntegrityConstraint(
235
                'Index must be non-negative',
236
                'Provide only non-negative indexes for Bernoulli number generation',
237
                'An attempt was made to get a Bernoulli number with a negative index'
238
            );
239
        }
240
241 1
        if ($n->isEqual(0)) {
242 1
            return Numbers::makeOne($scale);
243
        }
244
245 1
        if ($n->isEqual(1)) {
246 1
            return Numbers::make(Numbers::IMMUTABLE, '-0.5', $scale);
247
        }
248
249 1
        if ($n->modulo(2)->isEqual(1)) {
0 ignored issues
show
Bug introduced by
The method modulo() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface such as Samsara\Fermat\Types\Bas...umbers\DecimalInterface or Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

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

249
        if ($n->/** @scrutinizer ignore-call */ modulo(2)->isEqual(1)) {
Loading history...
250 1
            return Numbers::makeZero($scale);
251
        }
252
253 1
        $tau = Numbers::makeTau($internalScale)->setMode(CalcMode::Precision);
254
255 1
        $d = Numbers::make(Numbers::IMMUTABLE, 4, $internalScale)->setMode(CalcMode::Precision)->add(
256 1
            $n->factorial()->ln($internalScale)->subtract(
0 ignored issues
show
Bug introduced by
The method factorial() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface such as Samsara\Fermat\Types\Bas...umbers\DecimalInterface or Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

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

256
            $n->/** @scrutinizer ignore-call */ 
257
                factorial()->ln($internalScale)->subtract(
Loading history...
257 1
                $n->multiply($tau->log10($internalScale))
0 ignored issues
show
Bug introduced by
The method log10() does not exist on Samsara\Fermat\Types\Base\Number. It seems like you code against a sub-type of Samsara\Fermat\Types\Base\Number such as Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

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

257
                $n->multiply($tau->/** @scrutinizer ignore-call */ log10($internalScale))
Loading history...
258 1
            )->truncate()
0 ignored issues
show
Bug introduced by
The method truncate() does not exist on Samsara\Fermat\Types\Bas...mbers\FractionInterface. ( Ignorable by Annotation )

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

258
            )->/** @scrutinizer ignore-call */ truncate()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method truncate() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface such as Samsara\Fermat\Types\Bas...umbers\DecimalInterface or Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

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

258
            )->/** @scrutinizer ignore-call */ truncate()
Loading history...
259 1
        )->add(
260 1
            $n->numberOfIntDigits()
0 ignored issues
show
Bug introduced by
The method numberOfIntDigits() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface such as Samsara\Fermat\Types\Bas...umbers\DecimalInterface or Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

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

260
            $n->/** @scrutinizer ignore-call */ 
261
                numberOfIntDigits()
Loading history...
261
        );
262 1
        $s = $d->multiply(
263 1
            Numbers::makeNaturalLog10($internalScale)
264 1
        )->multiply(
265
            '0.5'
266 1
        )->divide($n, $internalScale)->exp($internalScale)->truncate()->add(1);
267 1
        $internalScale = ($d->isGreaterThan($internalScale)) ? $d->asInt() : $internalScale;
268
269 1
        $s = $s->truncateToScale($internalScale);
270 1
        $n = $n->truncateToScale($internalScale);
0 ignored issues
show
Bug introduced by
The method truncateToScale() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface such as Samsara\Fermat\Types\Bas...umbers\DecimalInterface or Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

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

270
        /** @scrutinizer ignore-call */ 
271
        $n = $n->truncateToScale($internalScale);
Loading history...
271 1
        $tau = Numbers::make2Pi($internalScale)->setMode(CalcMode::Precision);
272 1
        $p = Numbers::makeOne($internalScale)->setMode(CalcMode::Precision);
273 1
        $t1 = Numbers::makeOne($internalScale)->setMode(CalcMode::Precision);
274 1
        $t2 = Numbers::makeOne($internalScale)->setMode(CalcMode::Precision);
275
276 1
        while ($p->isLessThanOrEqualTo($s)) {
0 ignored issues
show
Bug introduced by
The method isLessThanOrEqualTo() does not exist on Samsara\Fermat\Types\Base\Number. Since it exists in all sub-types, consider adding an abstract or default implementation to Samsara\Fermat\Types\Base\Number. ( Ignorable by Annotation )

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

276
        while ($p->/** @scrutinizer ignore-call */ isLessThanOrEqualTo($s)) {
Loading history...
277 1
            $p = self::_nextprime($p);
278 1
            $pn = $p->pow($n);
279 1
            $pn1 = $pn->subtract(1);
280 1
            $t1 = $pn->multiply($t1);
281 1
            $t2 = $pn1->multiply($t2);
282
        }
283
284 1
        $z = $t1->divide($t2, $internalScale);
285 1
        $oz = Numbers::makeZero($internalScale)->setMode(CalcMode::Precision);
286
287 1
        while (!$oz->isEqual($z)) {
288 1
            $oz = $z;
289 1
            $p = self::_nextprime($p);
290 1
            $pn = $p->pow($n);
291 1
            $pn1 = $z->divide($pn, $internalScale);
292 1
            $z = $z->add($pn1);
293
        }
294
295 1
        $f = $n->factorial();
296 1
        $taun = $tau->pow($n);
297
298 1
        $z = $z->multiply(2)->multiply($f)->divide($taun, $internalScale);
299
300 1
        if ($n->modulo(4)->isEqual(0)) {
301 1
            $z = $z->multiply(-1);
302
        }
303
304 1
        return $z->round($scale);
0 ignored issues
show
Bug introduced by
The method round() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface such as Samsara\Fermat\Types\Bas...umbers\DecimalInterface or Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

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

304
        return $z->/** @scrutinizer ignore-call */ round($scale);
Loading history...
Bug introduced by
The method round() does not exist on Samsara\Fermat\Types\Bas...mbers\FractionInterface. ( Ignorable by Annotation )

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

304
        return $z->/** @scrutinizer ignore-call */ round($scale);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method round() does not exist on Samsara\Fermat\Types\Base\Number. It seems like you code against a sub-type of Samsara\Fermat\Types\Base\Number such as Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

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

304
        return $z->/** @scrutinizer ignore-call */ round($scale);
Loading history...
305
306
    }
307
308
    /**
309
     * @param int $n
310
     * @return NumberCollection
311
     * @throws IntegrityConstraint
312
     * @throws MissingPackage
313
     */
314
    public static function nthPrimeNumbers(int $n): NumberCollection
315
    {
316
        $collection = new NumberCollection();
317
318
        $collection->push(Numbers::make(Numbers::IMMUTABLE, 2));
319
320
        $currentPrime = Numbers::make(Numbers::IMMUTABLE, 3);
321
322
        for ($i = 1;$i < $n;$i++) {
323
            while (!$currentPrime->isPrime()) {
324
                $currentPrime = $currentPrime->add(2);
325
            }
326
327
            $collection->push($currentPrime);
328
            $currentPrime = $currentPrime->add(2);
329
        }
330
331
        return $collection;
332
    }
333
334
    /**
335
     * OEIS: A000045
336
     *
337
     * This uses an implementation of the fast-doubling Karatsuba multiplication algorithm as described by 'Nayuki':
338
     *
339
     * https://www.nayuki.io/page/fast-fibonacci-algorithms
340
     *
341
     * @param int $n
342
     * @param bool $asCollection
343
     * @param int $collectionSize
344
     *
345
     * @return ImmutableDecimal|NumberCollection
346
     * @throws IntegrityConstraint
347
     */
348 2
    public static function nthFibonacciNumber(int $n, bool $asCollection = false, int $collectionSize = 10): ImmutableDecimal|NumberCollection
349
    {
350 2
        $n = Numbers::makeOrDont(Numbers::IMMUTABLE, $n);
351
352 2
        if ($n->isLessThan(0)) {
353 1
            throw new IntegrityConstraint(
354
                'Negative term numbers not valid for Fibonacci Sequence',
355
                'Provide a positive term number',
356
                'A negative term number for the Fibonacci sequence was requested; provide a positive term number'
357
            );
358
        }
359
360 1
        $fastFib = static::_fib($n);
361
362 1
        if ($asCollection) {
363 1
            $sequence = new NumberCollection();
364 1
            $sequence->push($fastFib[0]);
365 1
            $sequence->push($fastFib[1]);
366 1
            for ($i = 0;$i < ($collectionSize-2);$i++) {
367 1
                $sequence->push($sequence->get($i)->add($sequence[$i+1]));
368
            }
369
370 1
            return $sequence;
371
        }
372
373 1
        return $fastFib[0];
374
    }
375
376
    /**
377
     * @param ImmutableDecimal $number
378
     * @return ImmutableDecimal[]
379
     * @throws IntegrityConstraint
380
     */
381 1
    private static function _fib(ImmutableDecimal $number): array
382
    {
383 1
        if ($number->isEqual(0)) {
384 1
            return [Numbers::makeZero(), Numbers::makeOne()];
385
        }
386
387
        /**
388
         * @var ImmutableDecimal $a
389
         * @var ImmutableDecimal $b
390
         * @var ImmutableDecimal $prevCall
391
         */
392 1
        $prevCall = $number->divide(2)->floor();
393 1
        [$a, $b] = static::_fib($prevCall);
394 1
        $c = $a->multiply($b->multiply(2)->subtract($a));
395 1
        $d = $a->multiply($a)->add($b->multiply($b));
396
397 1
        if ($number->modulo(2)->isEqual(0)) {
398 1
            return [$c, $d];
399
        }
400
401 1
        return [$d, $c->add($d)];
402
    }
403
404 1
    private static function _nextprime(ImmutableDecimal $number): ImmutableDecimal
405
    {
406 1
        return Numbers::make(Numbers::IMMUTABLE, gmp_strval(gmp_nextprime($number->getValue(NumberBase::Ten))));
407
    }
408
409
}