Completed
Push — master ( 157abc...2683bd )
by Jordan
22s queued 13s
created

IntegerMathTrait::isPrime()   B

Complexity

Conditions 11
Paths 13

Size

Total Lines 46
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 11.0061

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 26
nc 13
nop 1
dl 0
loc 46
ccs 26
cts 27
cp 0.963
crap 11.0061
rs 7.3166
c 1
b 0
f 0

How to fix   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\Types\Traits;
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\NumberBase;
9
use Samsara\Fermat\Enums\RandomMode;
10
use Samsara\Fermat\Numbers;
11
use Samsara\Fermat\Provider\RandomProvider;
12
use Samsara\Fermat\Types\Base\Interfaces\Numbers\NumberInterface;
13
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
14
use Samsara\Fermat\Types\NumberCollection;
15
use Samsara\Fermat\Values\ImmutableDecimal;
16
17
/**
18
 *
19
 */
20
trait IntegerMathTrait
21
{
22
23
    /**
24
     * @return DecimalInterface
25
     * @throws IncompatibleObjectState
26
     * @throws IntegrityConstraint
27
     */
28 189
    public function factorial(): DecimalInterface
29
    {
30 189
        if ($this->isLessThan(1)) {
0 ignored issues
show
Bug introduced by
It seems like isLessThan() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

30
        if ($this->/** @scrutinizer ignore-call */ isLessThan(1)) {
Loading history...
31 89
            if ($this->isEqual(0)) {
0 ignored issues
show
Bug introduced by
It seems like isEqual() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

31
            if ($this->/** @scrutinizer ignore-call */ isEqual(0)) {
Loading history...
32 88
                return $this->setValue(1);
0 ignored issues
show
Bug introduced by
It seems like setValue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

32
                return $this->/** @scrutinizer ignore-call */ setValue(1);
Loading history...
33
            }
34 1
            throw new IncompatibleObjectState(
35
                'Can only perform a factorial on a non-negative number.',
36
                'Ensure that the number is not negative before calling factorial().',
37
                'The factorial() method was called on a value that was negative.'
38
            );
39
        }
40
41 188
        if ($this->getDecimalPart() != 0) {
0 ignored issues
show
Bug introduced by
It seems like getDecimalPart() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

41
        if ($this->/** @scrutinizer ignore-call */ getDecimalPart() != 0) {
Loading history...
42 1
            throw new IncompatibleObjectState(
43
                'Can only perform a factorial on a whole number.',
44
                'Ensure that the number does not have any fractional value before calling factorial().',
45
                'The factorial() method was called on a value that was not a whole number.'
46
            );
47
        }
48
49 187
        if (function_exists('gmp_fact') && function_exists('gmp_strval') && $this->extensions) {
50 187
            return $this->setValue(gmp_strval(gmp_fact($this->getValue(NumberBase::Ten))));
0 ignored issues
show
Bug introduced by
It seems like getValue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

50
            return $this->setValue(gmp_strval(gmp_fact($this->/** @scrutinizer ignore-call */ getValue(NumberBase::Ten))));
Loading history...
51
        }
52
53 1
        $curVal = $this->getValue(NumberBase::Ten);
54 1
        $calcVal = Numbers::make(Numbers::IMMUTABLE, 1);
55
56 1
        for ($i = 1;$i <= $curVal;$i++) {
57 1
            $calcVal = $calcVal->multiply($i);
58
        }
59
60 1
        return $this->setValue($calcVal->getValue(NumberBase::Ten));
0 ignored issues
show
Unused Code introduced by
The call to Samsara\Fermat\Types\Fraction::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

60
        return $this->setValue($calcVal->/** @scrutinizer ignore-call */ getValue(NumberBase::Ten));

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...
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

60
        return $this->setValue($calcVal->/** @scrutinizer ignore-call */ getValue(NumberBase::Ten));

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...
61
62
    }
63
64
    /**
65
     * @return DecimalInterface
66
     * @throws IncompatibleObjectState
67
     * @throws IntegrityConstraint
68
     */
69 3
    public function subFactorial(): DecimalInterface
70
    {
71 3
        if ($this->isLessThan(1)) {
72 2
            if ($this->isEqual(0)) {
73 1
                return $this->setValue(1);
74
            }
75 1
            throw new IncompatibleObjectState(
76
                'Can only perform a sub-factorial on a non-negative number.',
77
                'Ensure that the number is not negative before calling subFactorial().',
78
                'The subFactorial() method was called on a value that was negative.'
79
            );
80
        }
81
82 2
        if (!$this->isInt()) {
0 ignored issues
show
Bug introduced by
It seems like isInt() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

82
        if (!$this->/** @scrutinizer ignore-call */ isInt()) {
Loading history...
83 1
            throw new IncompatibleObjectState(
84
                'Can only perform a sub-factorial on a whole number.',
85
                'Ensure that the number does not have any fractional value before calling subFactorial().',
86
                'The subFactorial() method was called on a value that was not a whole number.'
87
            );
88
        }
89
90 1
        $e = Numbers::makeE($this->getScale());
0 ignored issues
show
Bug introduced by
It seems like getScale() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

90
        $e = Numbers::makeE($this->/** @scrutinizer ignore-call */ getScale());
Loading history...
91 1
        $num = new ImmutableDecimal($this->getAsBaseTenRealNumber());
0 ignored issues
show
Bug introduced by
It seems like getAsBaseTenRealNumber() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

91
        $num = new ImmutableDecimal($this->/** @scrutinizer ignore-call */ getAsBaseTenRealNumber());
Loading history...
92
93 1
        $num = $num->factorial();
94 1
        $num = $num->divide($e, 3)->add('0.5');
95
96 1
        return $num->floor();
0 ignored issues
show
Bug introduced by
The method floor() does not exist on Samsara\Fermat\Types\Fraction. ( Ignorable by Annotation )

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

96
        return $num->/** @scrutinizer ignore-call */ floor();

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...
97
    }
98
99
    /**
100
     * @return DecimalInterface
101
     * @throws IncompatibleObjectState
102
     * @throws IntegrityConstraint
103
     * @throws MissingPackage
104
     */
105 1
    public function doubleFactorial(): DecimalInterface
106
    {
107 1
        if ($this->isWhole() && $this->isLessThanOrEqualTo(1)) {
0 ignored issues
show
Bug introduced by
It seems like isLessThanOrEqualTo() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

107
        if ($this->isWhole() && $this->/** @scrutinizer ignore-call */ isLessThanOrEqualTo(1)) {
Loading history...
Bug introduced by
It seems like isWhole() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

107
        if ($this->/** @scrutinizer ignore-call */ isWhole() && $this->isLessThanOrEqualTo(1)) {
Loading history...
108 1
            return $this->setValue('1');
109 1
        } elseif (!$this->isWhole()) {
110 1
            throw new IncompatibleObjectState(
111
                'Can only perform a double factorial on a whole number.',
112
                'Ensure that the number does not have any fractional value before calling doubleFactorial().',
113
                'The doubleFactorial() method was called on a value that was not a whole number.'
114
            );
115
        }
116
117 1
        $num = Numbers::make(Numbers::MUTABLE, $this->getValue(NumberBase::Ten), $this->getScale(), $this->getBase());
0 ignored issues
show
Bug introduced by
It seems like getBase() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

117
        $num = Numbers::make(Numbers::MUTABLE, $this->getValue(NumberBase::Ten), $this->getScale(), $this->/** @scrutinizer ignore-call */ getBase());
Loading history...
118
119 1
        $newVal = Numbers::makeOne();
120
121 1
        $continue = true;
122
123 1
        while ($continue) {
124 1
            $newVal = $newVal->multiply($num->getValue(NumberBase::Ten));
0 ignored issues
show
Unused Code introduced by
The call to Samsara\Fermat\Types\Fraction::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

124
            $newVal = $newVal->multiply($num->/** @scrutinizer ignore-call */ getValue(NumberBase::Ten));

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...
125 1
            $num = $num->subtract(2);
126
127 1
            if ($num->isLessThanOrEqualTo(1)) {
128 1
                $continue = false;
129
            }
130
        }
131
132 1
        return $this->setValue($newVal->getValue(NumberBase::Ten));
133
134
    }
135
136
    /**
137
     * @return DecimalInterface
138
     * @throws IncompatibleObjectState
139
     */
140 1
    public function semiFactorial(): DecimalInterface
141
    {
142 1
        return $this->doubleFactorial();
143
    }
144
145
    /**
146
     * @param $num
147
     * @return DecimalInterface
148
     * @throws IntegrityConstraint
149
     */
150 135
    public function getLeastCommonMultiple($num): DecimalInterface
151
    {
152
153
        /** @var ImmutableDecimal $num */
154 135
        $num = Numbers::makeOrDont(Numbers::IMMUTABLE, $num);
155
156 135
        if (!$this->isInt() || !$num->isInt()) {
157 1
            throw new IntegrityConstraint(
158
                'Both numbers must be integers',
159
                'Ensure that both numbers are integers before getting the LCM',
160
                'Both numbers being considered must be integers in order to calculate a Least Common Multiple'
161
            );
162
        }
163
164 135
        if (extension_loaded('gmp') && $this->extensions) {
165 135
            $value = gmp_lcm($this->getAsBaseTenRealNumber(), $num->getAsBaseTenRealNumber());
166
167 135
            return $this->setValue(gmp_strval($value), $this->getScale(), $this->getBase());
168
        }
169
170
        return $this->multiply($num)->abs()->divide($this->getGreatestCommonDivisor($num));
0 ignored issues
show
Bug introduced by
It seems like multiply() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

170
        return $this->/** @scrutinizer ignore-call */ multiply($num)->abs()->divide($this->getGreatestCommonDivisor($num));
Loading history...
171
172
    }
173
174
    /**
175
     * @param $num
176
     * @return DecimalInterface
177
     * @throws IntegrityConstraint
178
     */
179 148
    public function getGreatestCommonDivisor($num): DecimalInterface
180
    {
181
        /** @var ImmutableDecimal $num */
182 148
        $num = Numbers::make(Numbers::IMMUTABLE, $num)->abs();
183
        /** @var ImmutableDecimal $thisNum */
184 148
        $thisNum = Numbers::make(Numbers::IMMUTABLE, $this)->abs();
185
186 148
        if (!$this->isInt() || !$num->isInt()) {
187
            throw new IntegrityConstraint(
188
                'Both numbers must be integers',
189
                'Ensure that both numbers are integers before getting the GCD',
190
                'Both numbers being considered must be integers in order to calculate a Greatest Common Divisor'
191
            );
192
        }
193
194 148
        if (extension_loaded('gmp') && $this->extensions) {
195 148
            $val = gmp_strval(gmp_gcd($thisNum->getValue(NumberBase::Ten), $num->getValue(NumberBase::Ten)));
196
197 148
            return Numbers::make(Numbers::IMMUTABLE, $val, $this->getScale());
198
        }
199
200 1
        if ($thisNum->isLessThan($num)) {
201 1
            $greater = $num;
202 1
            $lesser = $thisNum;
203
        } else {
204 1
            $greater = $thisNum;
205 1
            $lesser = $num;
206
        }
207
208
        /** @var NumberInterface $remainder */
209 1
        $remainder = $greater->modulo($lesser);
210
211 1
        while ($remainder->isGreaterThan(0)) {
212
            $greater = $lesser;
213
            $lesser = $remainder;
214
            $remainder = $greater->modulo($lesser);
215
        }
216
217 1
        return $lesser;
218
219
    }
220
221
    /**
222
     * This function is a PHP implementation of the Miller-Rabin primality test. The default "certainty" value of 20
223
     * results in a false-positive rate of 1 in 1.10 x 10^12.
224
     *
225
     * Presumably, the probability of your hardware failing while this code is running is higher, meaning this should be
226
     * statistically as certain as a deterministic algorithm on normal computer hardware.
227
     *
228
     * @param int|null $certainty The certainty level desired. False positive rate = 1 in 4^$certainty.
229
     * @return bool
230
     */
231 1
    public function isPrime(?int $certainty = 20): bool
232
    {
233 1
        switch ($this->_primeEarlyExit()) {
234 1
            case 1:
235 1
                return false;
236
237 1
            case 2:
238 1
                return true;
239
        }
240
241 1
        if (function_exists('gmp_prob_prime') && $this->extensions) {
242 1
            return (bool)gmp_prob_prime($this->getValue(NumberBase::Ten), $certainty);
243
        }
244
245 1
        $thisNum = Numbers::makeOrDont(Numbers::IMMUTABLE, $this, $this->getScale());
246
247 1
        $s = $thisNum->subtract(1);
248 1
        $d = $thisNum->subtract(1);
249 1
        $r = Numbers::makeZero();
250
251 1
        while ($d->modulo(2)->isEqual(0)) {
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

251
        while ($d->/** @scrutinizer ignore-call */ modulo(2)->isEqual(0)) {
Loading history...
Bug introduced by
The method modulo() does not exist on Samsara\Fermat\Types\Fraction. ( Ignorable by Annotation )

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

251
        while ($d->/** @scrutinizer ignore-call */ modulo(2)->isEqual(0)) {

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 modulo() 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

251
        while ($d->/** @scrutinizer ignore-call */ modulo(2)->isEqual(0)) {

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...
252 1
            $r = $r->add(1);
253 1
            $d = $d->divide(2);
254
        }
255
256 1
        $r = $r->subtract(1);
257
258 1
        for ($i = 0;$i < $certainty;$i++) {
259 1
            $a = RandomProvider::randomInt(2, $s, RandomMode::Speed);
0 ignored issues
show
Bug introduced by
It seems like $s can also be of type Samsara\Fermat\Types\Bas...mbers\FractionInterface; however, parameter $max of Samsara\Fermat\Provider\...omProvider::randomInt() does only seem to accept Samsara\Fermat\Types\Bas...nterface|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

259
            $a = RandomProvider::randomInt(2, /** @scrutinizer ignore-type */ $s, RandomMode::Speed);
Loading history...
260 1
            $x = Numbers::make(Numbers::IMMUTABLE, (string)gmp_powm($a->getAsBaseTenRealNumber(), $d->getAsBaseTenRealNumber(), $thisNum->getAsBaseTenRealNumber()));
0 ignored issues
show
Bug introduced by
The method getAsBaseTenRealNumber() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Samsara\Fermat\Types\Bas...Numbers\NumberInterface. ( Ignorable by Annotation )

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

260
            $x = Numbers::make(Numbers::IMMUTABLE, (string)gmp_powm($a->getAsBaseTenRealNumber(), $d->/** @scrutinizer ignore-call */ getAsBaseTenRealNumber(), $thisNum->getAsBaseTenRealNumber()));
Loading history...
261
262 1
            if ($x->isEqual(1) || $x->isEqual($s)) {
263 1
                continue;
264
            }
265
266 1
            for ($j = 0;$j < $r->asInt();$j++) {
0 ignored issues
show
Bug introduced by
The method asInt() does not exist on Samsara\Fermat\Types\Fraction. ( Ignorable by Annotation )

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

266
            for ($j = 0;$j < $r->/** @scrutinizer ignore-call */ asInt();$j++) {

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...
267 1
                $x = $x->pow(2)->modulo($thisNum);
268 1
                if ($x->isEqual($s)) {
269 1
                    continue 2;
270
                }
271
            }
272
273
            return false;
274
        }
275
276 1
        return true;
277
    }
278
279
    /**
280
     * @return NumberCollection
281
     * @throws IntegrityConstraint
282
     * @throws MissingPackage
283
     */
284
    public function getDivisors(): NumberCollection
285
    {
286
        $half = $this->divide(2);
0 ignored issues
show
Bug introduced by
It seems like divide() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

286
        /** @scrutinizer ignore-call */ 
287
        $half = $this->divide(2);
Loading history...
287
288
        $collection = new NumberCollection();
289
        $current = Numbers::makeOne(2);
290
        while ($current->isLessThan($half)) {
291
            if ($this->modulo($current)->isEqual(0)) {
0 ignored issues
show
Bug introduced by
It seems like modulo() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

291
            if ($this->/** @scrutinizer ignore-call */ modulo($current)->isEqual(0)) {
Loading history...
292
                $collection->push($current);
293
                $collection->push($this->divide($current));
294
            }
295
            $current = $current->add(1);
296
        }
297
298
        $collection->sort();
299
300
        return $collection;
301
    }
302
303
    /**
304
     * @return NumberCollection
305
     */
306
    public function asPrimeFactors(): NumberCollection
307
    {
308
309
        return new NumberCollection();
310
311
    }
312
313 1
    private function _primeEarlyExit(): int
314
    {
315 1
        if (!$this->isInt()) {
316 1
            return 1;
317
        }
318
319 1
        if ($this->isEqual(2)) {
320 1
            return 2;
321 1
        } elseif ($this->isEqual(3)) {
322 1
            return 2;
323 1
        } elseif ($this->modulo(2)->isEqual(0)) {
324 1
            return 1;
325 1
        } elseif ($this->modulo(3)->isEqual(0)) {
326 1
            return 1;
327 1
        } elseif ($this->isEqual(1)) {
328
            return 1;
329
        }
330
331 1
        return 0;
332
    }
333
334
}