Passed
Push — master ( 621c3c...4216b1 )
by Jordan
23:02 queued 16:19
created

Numbers   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 370
Duplicated Lines 0 %

Test Coverage

Coverage 88.78%

Importance

Changes 0
Metric Value
eloc 124
c 0
b 0
f 0
dl 0
loc 370
ccs 87
cts 98
cp 0.8878
rs 8.4
wmc 50

13 Methods

Rating   Name   Duplication   Size   Complexity  
A make2Pi() 0 3 1
A makeOne() 0 3 1
A makePi() 0 20 4
A make() 0 27 6
A makeGoldenRatio() 0 16 4
A makeTau() 0 21 4
A makeZero() 0 3 1
A makeFractionFromString() 0 29 5
A makeFromBase10() 0 8 1
B makeOrDont() 0 31 11
A makeNaturalLog2() 0 20 4
A makeE() 0 20 4
A makeNaturalLog10() 0 20 4

How to fix   Complexity   

Complex Class

Complex classes like Numbers often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Numbers, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Samsara\Fermat;
4
5
use Samsara\Exceptions\UsageError\IntegrityConstraint;
6
use Samsara\Fermat\Enums\NumberBase;
7
use Samsara\Fermat\Provider\ConstantProvider;
8
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
9
use Samsara\Fermat\Types\Base\Interfaces\Numbers\FractionInterface;
10
use Samsara\Fermat\Types\Base\Interfaces\Numbers\NumberInterface;
11
use Samsara\Fermat\Values\ImmutableFraction;
12
use Samsara\Fermat\Values\ImmutableDecimal;
13
use Samsara\Fermat\Values\MutableFraction;
14
use Samsara\Fermat\Values\MutableDecimal;
15
16
/**
17
 * This class contains useful factory methods to create various numbers, verify the
18
 * class of a given number, and generally handle all of the formatting necessary to
19
 * satisfy the various constructors of valid value objects.
20
 *
21
 * @package Samsara\Fermat
22
 */
23
class Numbers
24
{
25
26
    public const MUTABLE = MutableDecimal::class;
27
    public const IMMUTABLE = ImmutableDecimal::class;
28
    public const MUTABLE_FRACTION = MutableFraction::class;
29
    public const IMMUTABLE_FRACTION = ImmutableFraction::class;
30
    /* 105 digits after decimal, which is going to be overkill in almost all places */
31
    public const PI = '3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679';
32
    /* Tau (2pi) to 100 digits */
33
    public const TAU = '6.2831853071795864769252867665590057683943387987502116419498891846156328125724179972560696506842341359';
34
    /* Euler's Number to 100 digits */
35
    public const E = '2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427';
36
    /* Golden Ratio to 100 digits */
37
    public const GOLDEN_RATIO = '1.618033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137';
38
    /* Natural log of 10 to 100 digits */
39
    public const LN_10 = '2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298';
40
    /* Natural log of 2 to 100 digits */
41
    public const LN_2 = '0.693147180559945309417232121458176568075500134360255254120680009493393621969694715605863326996418687';
42
    /* The value of i^i */
43
    public const I_POW_I = '0.2078795763507619085469556198349787700338778416317696080751358830554198772854821397886002778654260353';
44
45
    /**
46
     * This class will make and return an instance of a concrete value.
47
     *
48
     * The main reason for this class is that you can pass an unknown value instance as the
49
     * $type parameter and it will behave as if you passed the FQCN.
50
     *
51
     * @param mixed         $type   An instance of FQCN for any Fermat value class.
52
     * @param mixed         $value  Any value which is valid for the constructor which will be called.
53
     * @param int|null      $scale  The scale setting the created instance should have.
54
     * @param NumberBase    $base   The base to create the number in. Note, this is not the same as the base of $value, which is always base-10
55
     *
56
     * @return ImmutableDecimal|MutableDecimal|ImmutableFraction|MutableFraction|NumberInterface|FractionInterface
57
     * @throws IntegrityConstraint
58
     */
59 125
    public static function make(mixed $type, mixed $value, ?int $scale = null, NumberBase $base = NumberBase::Ten)
60
    {
61
62 125
        if (is_object($type)) {
63 9
            $type = get_class($type);
64
        }
65
66 125
        if ($type === static::IMMUTABLE) {
67 125
            return new ImmutableDecimal(trim($value), $scale, $base, true);
68
        }
69
70 21
        if ($type === static::MUTABLE) {
71 12
            return new MutableDecimal(trim($value), $scale, $base, true);
72
        }
73
74 10
        if ($type === static::IMMUTABLE_FRACTION) {
75 8
            return self::makeFractionFromString($type, $value, $base);
76
        }
77
78 3
        if ($type === static::MUTABLE_FRACTION) {
79 3
            return self::makeFractionFromString($type, $value, $base);
80
        }
81
82
        throw new IntegrityConstraint(
83
            '$type must be an implemented concrete class that is supported',
84
            'Provide a type that implements NumberInterface or CoordinateInterface (the Numbers class contains constants for the built in ones)',
85
            'The $type argument was not an implementation of NumberInterface or CoordinateInterface'
86
        );
87
    }
88
89
    /**
90
     * @param $type
91
     * @param $value
92
     * @param int|null $scale
93
     * @param NumberBase $base
94
     *
95
     * @return NumberInterface
96
     * @throws IntegrityConstraint
97
     */
98 1
    public static function makeFromBase10($type, $value, ?int $scale = null, NumberBase $base = NumberBase::Ten): NumberInterface
99
    {
100
        /**
101
         * @var ImmutableDecimal|MutableDecimal $number
102
         */
103 1
        $number = self::make($type, $value, $scale);
104
105 1
        return $number->setBase($base);
106
    }
107
108
    /**
109
     * @param string|object $type
110
     * @param int|float|string|array|NumberInterface|DecimalInterface|FractionInterface $value
111
     * @param int|null $scale
112
     * @param NumberBase $base
113
     *
114
     * @return ImmutableDecimal|MutableDecimal|NumberInterface|ImmutableDecimal[]|MutableDecimal[]|NumberInterface[]
115
     * @throws IntegrityConstraint
116
     */
117 120
    public static function makeOrDont(string|object $type, mixed $value, ?int $scale = null, NumberBase $base = NumberBase::Ten)
118
    {
119
120 120
        if (is_object($value)) {
121 109
            if ($value instanceof $type) {
122 109
                return $value;
123
            }
124
125 10
            if ($value instanceof NumberInterface) {
126 10
                return static::make($type, $value->getValue(NumberBase::Ten), $scale, $base);
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

126
                return static::make($type, $value->/** @scrutinizer ignore-call */ getValue(NumberBase::Ten), $scale, $base);

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...
127
            }
128 118
        } elseif (is_array($value)) {
129 5
            $newInput = [];
130
131 5
            foreach ($value as $key => $item) {
132 5
                $newInput[$key] = static::makeOrDont($type, $item, $scale, $base);
133
            }
134
135 5
            return $newInput;
136 118
        } elseif (is_string($value) || is_int($value) || is_float($value)) {
137 118
            $isImaginary = str_contains($value, 'i');
138
139 118
            if (is_numeric($value) || $isImaginary) {
140 118
                return static::make($type, $value, $scale, $base);
141
            }
142
        }
143
144
        throw new IntegrityConstraint(
145
            '$input must be an int, float, numeric string, or an implementation of NumberInterface',
146
            'Provide any of the MANY valid inputs',
147
            'The $input argument was not numeric or an implementation of NumberInterface. Given value: '.$value
148
        );
149
150
    }
151
152
    /**
153
     * @param string $type
154
     * @param string $value
155
     * @param NumberBase $base
156
     *
157
     * @return FractionInterface
158
     * @throws IntegrityConstraint
159
     */
160 11
    public static function makeFractionFromString(string $type, string $value, NumberBase $base = NumberBase::Ten): FractionInterface
161
    {
162 11
        $parts = explode('/', $value);
163
164 11
        if (count($parts) > 2) {
165
            throw new IntegrityConstraint(
166
                'Only one division symbol (/) can be used',
167
                'Change the calling code to not provide more than one division symbol',
168
                'makeFractionFromString needs either one or zero division symbols in the $value argument; '.$value.' given'
169
            );
170
        }
171
172
        /** @var ImmutableDecimal $numerator */
173 11
        $numerator = self::make(self::IMMUTABLE, trim(ltrim($parts[0])));
174
        /** @var ImmutableDecimal $denominator */
175 11
        $denominator = isset($parts[1]) ? self::make(self::IMMUTABLE, trim(ltrim($parts[1]))) : self::makeOne();
176
177 11
        if ($type === self::IMMUTABLE_FRACTION) {
178 9
            return new ImmutableFraction($numerator, $denominator, $base);
179
        }
180
181 3
        if ($type === self::MUTABLE_FRACTION) {
182 3
            return new MutableFraction($numerator, $denominator, $base);
183
        }
184
185
        throw new IntegrityConstraint(
186
            'Type must be an implementation of FractionInterface',
187
            'Alter to calling code to use the correct type',
188
            'makeFractionFromString can only make objects which implement the FractionInterface; '.$type.' given'
189
        );
190
    }
191
192
    /**
193
     * @param int|null $scale
194
     *
195
     * @return ImmutableDecimal
196
     * @throws IntegrityConstraint
197
     */
198 17
    public static function makePi(int $scale = null): ImmutableDecimal
199
    {
200
201 17
        if (!is_null($scale)) {
202 16
            if ($scale < 1) {
203 1
                throw new IntegrityConstraint(
204
                    '$scale must be at least 1',
205
                    'Provide a scale within range',
206
                    'The pi constant cannot have a scale less than 1'
207
                );
208
            }
209
210 16
            if ($scale > 100) {
211 3
                return self::make(self::IMMUTABLE, ConstantProvider::makePi($scale), $scale);
212
            }
213
214 16
            return self::make(self::IMMUTABLE, self::PI, $scale)->truncateToScale($scale);
0 ignored issues
show
Bug introduced by
The method truncateToScale() does not exist on Samsara\Fermat\Values\ImmutableFraction. ( Ignorable by Annotation )

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

214
            return self::make(self::IMMUTABLE, self::PI, $scale)->/** @scrutinizer ignore-call */ truncateToScale($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 truncateToScale() does not exist on Samsara\Fermat\Values\MutableFraction. ( Ignorable by Annotation )

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

214
            return self::make(self::IMMUTABLE, self::PI, $scale)->/** @scrutinizer ignore-call */ truncateToScale($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...
215
        }
216
217 6
        return self::make(self::IMMUTABLE, self::PI, 100);
218
219
    }
220
221
    /**
222
     * @param null $scale
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $scale is correct as it would always require null to be passed?
Loading history...
223
     *
224
     * @return ImmutableDecimal
225
     * @throws IntegrityConstraint
226
     */
227 10
    public static function makeTau($scale = null): ImmutableDecimal
228
    {
229 10
        if (!is_null($scale)) {
0 ignored issues
show
introduced by
The condition is_null($scale) is always true.
Loading history...
230 10
            if ($scale < 1) {
231 1
                throw new IntegrityConstraint(
232
                    '$scale must be at least 1',
233
                    'Provide a scale within range',
234
                    'The E constant cannot have a scale less than 1'
235
                );
236
            }
237
238 10
            if ($scale > 100) {
239 3
                $pi = self::make(self::IMMUTABLE, ConstantProvider::makePi($scale), $scale + 2);
240
                /** @var ImmutableDecimal */
241 3
                return $pi->multiply(2)->truncateToScale($scale);
242
            }
243
244 10
            return self::make(self::IMMUTABLE, self::TAU, $scale)->truncateToScale($scale);
245
        }
246
247 5
        return self::make(self::IMMUTABLE, self::TAU, 100);
248
    }
249
250
    /**
251
     * @param int|null $scale
252
     *
253
     * @return ImmutableDecimal
254
     * @throws IntegrityConstraint
255
     */
256 8
    public static function make2Pi(int $scale = null): ImmutableDecimal
257
    {
258 8
        return self::makeTau($scale);
0 ignored issues
show
Bug introduced by
It seems like $scale can also be of type integer; however, parameter $scale of Samsara\Fermat\Numbers::makeTau() does only seem to accept null, 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

258
        return self::makeTau(/** @scrutinizer ignore-type */ $scale);
Loading history...
259
    }
260
261
    /**
262
     * @param int|null $scale
263
     *
264
     * @return ImmutableDecimal
265
     * @throws IntegrityConstraint
266
     */
267 11
    public static function makeE(int $scale = null): ImmutableDecimal
268
    {
269
270 11
        if (!is_null($scale)) {
271 9
            if ($scale < 1) {
272 1
                throw new IntegrityConstraint(
273
                    '$scale must be at least 1',
274
                    'Provide a scale within range',
275
                    'The E constant cannot have a scale less than 1'
276
                );
277
            }
278
279 9
            if ($scale > 100) {
280 1
                return self::make(self::IMMUTABLE, ConstantProvider::makeE($scale), $scale);
281
            }
282
283 9
            return self::make(self::IMMUTABLE, self::E, $scale)->truncateToScale($scale);
284
        }
285
286 4
        return self::make(self::IMMUTABLE, self::E, 100);
287
288
    }
289
290
    /**
291
     * @param int|null $scale
292
     *
293
     * @return ImmutableDecimal
294
     * @throws IntegrityConstraint
295
     */
296 1
    public static function makeGoldenRatio(?int $scale = null): ImmutableDecimal
297
    {
298
299 1
        if (!is_null($scale)) {
300 1
            if ($scale > 100 || $scale < 1) {
301 1
                throw new IntegrityConstraint(
302
                    '$scale must be between 1 and 100 inclusive',
303
                    'Provide a scale within range',
304
                    'The Golden Ratio constant cannot have a scale higher than the constant stored (100)'
305
                );
306
            }
307
308 1
            return self::make(self::IMMUTABLE, self::GOLDEN_RATIO, $scale)->truncateToScale($scale);
309
        }
310
311 1
        return self::make(self::IMMUTABLE, self::GOLDEN_RATIO, 100);
312
313
    }
314
315
    /**
316
     * @param int|null $scale
317
     *
318
     * @return ImmutableDecimal
319
     * @throws IntegrityConstraint
320
     */
321 3
    public static function makeNaturalLog10(?int $scale = null): ImmutableDecimal
322
    {
323
324 3
        if (!is_null($scale)) {
325 3
            if ($scale < 1) {
326 1
                throw new IntegrityConstraint(
327
                    '$scale must be at least 1',
328
                    'Provide a scale within range',
329
                    'The natural log of 10 constant cannot have a scale less than 1'
330
                );
331
            }
332
333 3
            if ($scale > 100) {
334
                return self::make(self::IMMUTABLE, ConstantProvider::makeLn10($scale), $scale);
335
            }
336
337 3
            return self::make(self::IMMUTABLE, self::LN_10, $scale)->truncateToScale($scale);
338
        }
339
340 1
        return self::make(self::IMMUTABLE, self::LN_10, 100);
341
342
    }
343
344
    /**
345
     * @param int|null $scale
346
     *
347
     * @return ImmutableDecimal
348
     * @throws IntegrityConstraint
349
     */
350 5
    public static function makeNaturalLog2(?int $scale = null): ImmutableDecimal
351
    {
352
353 5
        if (!is_null($scale)) {
354 5
            if ($scale < 1) {
355
                throw new IntegrityConstraint(
356
                    '$scale must be at least 1',
357
                    'Provide a scale within range',
358
                    'The natural log of 10 constant cannot have a scale less than 1'
359
                );
360
            }
361
362 5
            if ($scale > 100) {
363
                return self::make(self::IMMUTABLE, ConstantProvider::makeLn2($scale), $scale);
364
            }
365
366 5
            return self::make(self::IMMUTABLE, self::LN_2, $scale)->truncateToScale($scale);
367
        }
368
369
        return self::make(self::IMMUTABLE, self::LN_2, 100);
370
371
    }
372
373
    /**
374
     * @param int|null $scale
375
     *
376
     * @return ImmutableDecimal
377
     * @throws IntegrityConstraint
378
     */
379 42
    public static function makeOne(?int $scale = null): ImmutableDecimal
380
    {
381 42
        return self::make(self::IMMUTABLE, 1, $scale);
382
    }
383
384
    /**
385
     * @param int|null $scale
386
     *
387
     * @return ImmutableDecimal
388
     * @throws IntegrityConstraint
389
     */
390 31
    public static function makeZero(?int $scale = null): ImmutableDecimal
391
    {
392 31
        return self::make(self::IMMUTABLE, 0, $scale);
393
    }
394
395
}