Failed Conditions
Pull Request — dev (#50)
by Jordan
06:20
created

Numbers   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 379
Duplicated Lines 0 %

Test Coverage

Coverage 69.06%

Importance

Changes 0
Metric Value
eloc 135
dl 0
loc 379
ccs 96
cts 139
cp 0.6906
rs 5.04
c 0
b 0
f 0
wmc 57

12 Methods

Rating   Name   Duplication   Size   Complexity  
A makeOne() 0 3 1
C make() 0 67 15
A makePi() 0 16 4
A makeGoldenRatio() 0 16 4
A makeTau() 0 15 4
A makeZero() 0 3 1
B makeFractionFromString() 0 41 7
A makeFromBase10() 0 8 1
A make2Pi() 0 3 1
B makeOrDont() 0 31 11
A makeE() 0 16 4
A makeNaturalLog10() 0 16 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 ReflectionException;
6
use Samsara\Exceptions\UsageError\IntegrityConstraint;
7
use Samsara\Fermat\Types\Base\Interfaces\Coordinates\CoordinateInterface;
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\Types\Fraction;
12
use Samsara\Fermat\Values\Geometry\CoordinateSystems\CartesianCoordinate;
13
use Samsara\Fermat\Values\ImmutableFraction;
14
use Samsara\Fermat\Values\ImmutableDecimal;
15
use Samsara\Fermat\Values\MutableFraction;
16
use Samsara\Fermat\Values\MutableDecimal;
17
18
class Numbers
19
{
20
21
    public const MUTABLE = MutableDecimal::class;
22
    public const IMMUTABLE = ImmutableDecimal::class;
23
    public const MUTABLE_FRACTION = MutableFraction::class;
24
    public const IMMUTABLE_FRACTION = ImmutableFraction::class;
25
    public const CARTESIAN_COORDINATE = CartesianCoordinate::class;
26
    /* 105 digits after decimal, which is going to be overkill in almost all places */
27
    public const PI = '3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679';
28
    /* Tau (2pi) to 100 digits */
29
    public const TAU = '6.283185307179586476925286766559005768394338798750211641949889184615632812572417997256069650684234136';
30
    /* Euler's Number to 100 digits */
31
    public const E = '2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427';
32
    /* Golden Ratio to 100 digits */
33
    public const GOLDEN_RATIO = '1.618033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137';
34
    /* Natural log of 10 to 100 digits */
35
    public const LN_10 = '2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298';
36
    /* The value of i^i */
37
    public const I_POW_I = '0.2078795763507619085469556198349787700338778416317696080751358830554198772854821397886002778654260353';
38
39
    /**
40
     * @param $type
41
     * @param $value
42
     * @param int|null $precision
43
     * @param int $base
44
     *
45
     * @return ImmutableDecimal|MutableDecimal|ImmutableFraction|MutableFraction|CartesianCoordinate|NumberInterface|FractionInterface|CoordinateInterface
46
     * @throws IntegrityConstraint
47 87
     * @throws ReflectionException
48
     */
49
    public static function make($type, $value, $precision = null, $base = 10)
50 87
    {
51 70
52
        if (is_object($type)) {
53
            $type = get_class($type);
54 87
        }
55 87
56 11
        if ($type === static::IMMUTABLE) {
57 5
            return new ImmutableDecimal(trim($value), $precision, $base);
58 9
        }
59 8
60 4
        if ($type === static::MUTABLE) {
61 3
            return new MutableDecimal(trim($value), $precision, $base);
62 1
        }
63 1
64
        if ($type === static::IMMUTABLE_FRACTION) {
65 1
            return self::makeFractionFromString($type, $value, $base);
66 1
        }
67
68
        if ($type === static::MUTABLE_FRACTION) {
69
            return self::makeFractionFromString($type, $value, $base);
70
        }
71 1
72
        if ($type === static::CARTESIAN_COORDINATE && is_array($value)) {
73
            $x = $value[0];
74 1
            $y = $value[1] ?? null;
75
            $z = $value[2] ?? null;
76
77 1
            return new CartesianCoordinate($x, $y, $z);
78
        }
79
80
        $reflector = new \ReflectionClass($type);
81
82
        if ($reflector->implementsInterface(FractionInterface::class) && $reflector->isSubclassOf(Fraction::class)) {
83
            return self::makeFractionFromString($reflector->getName(), $value, $base);
84
        }
85
86
        if ($reflector->implementsInterface(CoordinateInterface::class) && is_array($value)) {
87
            /** @var CoordinateInterface $customCoordinate */
88
            $customCoordinate = $reflector->newInstance([
89
                $value
90
            ]);
91
            return $customCoordinate;
92
        }
93
94
        if ($reflector->implementsInterface(NumberInterface::class)) {
95
            /** @var NumberInterface $customNumber */
96
            $customNumber = $reflector->newInstance([
97
                trim($value),
98
                $precision,
99
                $base
100
            ]);
101
            return $customNumber;
102
        }
103
104
        if ($reflector->implementsInterface(CoordinateInterface::class) && !is_array($value)) {
105
            throw new IntegrityConstraint(
106
                'The $value for a CoordinateInterface must be an array',
107
                'Provide an array for the $value',
108
                'A CoordinateInterface expects the value to be an array of axes and values'
109
            );
110
        }
111
112
        throw new IntegrityConstraint(
113
            '$type must be an implementation of NumberInterface or CoordinateInterface',
114
            'Provide a type that implements NumberInterface or CoordinateInterface (the Numbers class contains constants for the built in ones)',
115
            'The $type argument was not an implementation of NumberInterface or CoordinateInterface'
116
        );
117
    }
118
119
    /**
120
     * @param $type
121
     * @param $value
122
     * @param int|null $precision
123
     * @param int $base
124
     *
125
     * @return NumberInterface
126
     * @throws IntegrityConstraint
127 1
     * @throws ReflectionException
128
     */
129
    public static function makeFromBase10($type, $value, $precision = null, $base = 10): NumberInterface
130
    {
131
        /**
132 1
         * @var ImmutableDecimal|MutableDecimal
133
         */
134 1
        $number = self::make($type, $value, $precision, 10);
135
136
        return $number->convertToBase($base);
0 ignored issues
show
Bug introduced by
The method convertToBase() 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\Decimal 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

136
        return $number->/** @scrutinizer ignore-call */ convertToBase($base);
Loading history...
Bug introduced by
The method convertToBase() does not exist on Samsara\Fermat\Values\Ge...ems\CartesianCoordinate. ( Ignorable by Annotation )

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

136
        return $number->/** @scrutinizer ignore-call */ convertToBase($base);

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 convertToBase() does not exist on Samsara\Fermat\Types\Bas...tes\CoordinateInterface. ( Ignorable by Annotation )

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

136
        return $number->/** @scrutinizer ignore-call */ convertToBase($base);

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

136
        return $number->/** @scrutinizer ignore-call */ convertToBase($base);

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...
137
    }
138
139
    /**
140
     * @param string|object $type
141
     * @param int|float|string|array|NumberInterface|DecimalInterface|FractionInterface $value
142
     * @param int|null $precision
143
     * @param int $base
144
     *
145
     * @return ImmutableDecimal|MutableDecimal|NumberInterface|ImmutableDecimal[]|MutableDecimal[]|NumberInterface[]
146 78
     * @throws IntegrityConstraint|ReflectionException
147
     */
148
    public static function makeOrDont($type, $value, $precision = null, $base = 10)
149 78
    {
150 54
151 54
        if (is_object($value)) {
152
            if ($value instanceof $type) {
153
                return $value;
154 2
            }
155 2
156
            if ($value instanceof NumberInterface) {
0 ignored issues
show
introduced by
$value is always a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface.
Loading history...
157 78
                return static::make($type, $value->getValue(), $precision, $base);
0 ignored issues
show
Bug introduced by
The method getValue() 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

157
                return static::make($type, $value->/** @scrutinizer ignore-call */ getValue(), $precision, $base);
Loading history...
158 13
            }
159
        } elseif (is_array($value)) {
160 13
            $newInput = [];
161 13
162
            foreach ($value as $key => $item) {
163
                $newInput[$key] = static::makeOrDont($type, $item, $precision, $base);
164 13
            }
165 78
166 78
            return $newInput;
167
        } elseif (is_string($value) || is_int($value) || is_float($value)) {
0 ignored issues
show
introduced by
The condition is_float($value) is always true.
Loading history...
168 78
            $isImaginary = strpos($value, 'i') !== false;
169 78
170
            if (is_numeric($value) || $isImaginary) {
171
                return static::make($type, $value, $precision, $base);
172
            }
173
        }
174
175
        throw new IntegrityConstraint(
176
            '$input must be an int, float, numeric string, or an implementation of NumberInterface',
177
            'Provide any of the MANY valid inputs',
178
            'The $input argument was not numeric or an implementation of NumberInterface. Given value: '.$value
179
        );
180
181
    }
182
183
    /**
184
     * @param     $type
185
     * @param     $value
186
     * @param int $base
187
     *
188
     * @return FractionInterface|ImmutableFraction|MutableFraction
189 8
     * @throws IntegrityConstraint|ReflectionException
190
     */
191 8
    public static function makeFractionFromString($type, $value, int $base = 10): FractionInterface
192
    {
193 8
        $parts = explode('/', $value);
194
195
        if (count($parts) > 2) {
196
            throw new IntegrityConstraint(
197
                'Only one division symbol (/) can be used',
198
                'Change the calling code to not provide more than one division symbol',
199
                'makeFractionFromString needs either one or zero division symbols in the $value argument; '.$value.' given'
200
            );
201
        }
202 8
203
        /** @var ImmutableDecimal $numerator */
204 8
        $numerator = self::make(self::IMMUTABLE, trim(ltrim($parts[0])))->round();
205
        /** @var ImmutableDecimal $denominator */
206 8
        $denominator = isset($parts[1]) ? self::make(self::IMMUTABLE, trim(ltrim($parts[1])))->round() : self::makeOne();
207 8
208 3
        if ($type === self::IMMUTABLE_FRACTION) {
209 3
            return new ImmutableFraction($numerator, $denominator, $base);
210
        }
211
212
        if ($type === self::MUTABLE_FRACTION) {
213
            return new MutableFraction($numerator, $denominator, $base);
214
        }
215
216
        $reflector = new \ReflectionClass($type);
217
218
        if ($reflector->implementsInterface(FractionInterface::class) && $reflector->isSubclassOf(Fraction::class)) {
219
            /** @var FractionInterface|Fraction $customFraction */
220
            $customFraction = $reflector->newInstance([
221
                $numerator,
222
                $denominator,
223
                $base
224
            ]);
225
            return $customFraction;
226
        }
227
228
        throw new IntegrityConstraint(
229
            'Type must be an implementation of FractionInterface',
230
            'Alter to calling code to use the correct type',
231
            'makeFractionFromString can only make objects which implement the FractionInterface; '.$type.' given'
232
        );
233
    }
234
235
    /**
236
     * @param int|null $precision
237 23
     *
238
     * @return NumberInterface
239
     * @throws ReflectionException
240 23
     * @throws IntegrityConstraint
241 1
     */
242 1
    public static function makePi(int $precision = null)
243 1
    {
244 1
245
        if (!is_null($precision)) {
246
            if ($precision > 100 || $precision < 1) {
247
                throw new IntegrityConstraint(
248 23
                    '$precision must be between 1 and 100 inclusive',
249 14
                    'Provide a precision within range',
250
                    'The PI constant cannot have a precision higher than the constant stored (100)'
251 23
                );
252
            }
253
254
            return self::make(self::IMMUTABLE, self::PI, $precision)->truncateToPrecision($precision);
0 ignored issues
show
Bug introduced by
The method truncateToPrecision() 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

254
            return self::make(self::IMMUTABLE, self::PI, $precision)->/** @scrutinizer ignore-call */ truncateToPrecision($precision);

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

254
            return self::make(self::IMMUTABLE, self::PI, $precision)->/** @scrutinizer ignore-call */ truncateToPrecision($precision);
Loading history...
255
        }
256
257
        return self::make(self::IMMUTABLE, self::PI, 100);
258
259
    }
260
261
    /**
262 22
     * @param int|null $precision
263
     *
264 22
     * @return NumberInterface
265 1
     * @throws ReflectionException
266 1
     * @throws IntegrityConstraint
267 1
     */
268 1
    public static function makeTau($precision = null)
269
    {
270
        if (!is_null($precision)) {
271
            if ($precision > 100 || $precision < 1) {
272 22
                throw new IntegrityConstraint(
273 6
                    '$precision must be between 1 and 100 inclusive',
274
                    'Provide a precision within range',
275 22
                    'The TAU constant cannot have a precision higher than the constant stored (100)'
276
                );
277
            }
278
279
            return self::make(self::IMMUTABLE, self::TAU, $precision)->truncateToPrecision($precision);
280
        }
281
282
        return self::make(self::IMMUTABLE, self::TAU, 100);
283
    }
284
285 22
    /**
286
     * @param int|null $precision
287 22
     *
288
     * @return NumberInterface
289
     * @throws IntegrityConstraint
290
     * @throws ReflectionException
291
     */
292
    public static function make2Pi($precision = null)
293
    {
294
        return self::makeTau($precision);
295
    }
296 7
297
    /**
298
     * @param int|null $precision
299 7
     *
300 1
     * @return NumberInterface
301 1
     * @throws ReflectionException
302 1
     * @throws IntegrityConstraint
303 1
     */
304
    public static function makeE($precision = null)
305
    {
306
307 7
        if (!is_null($precision)) {
308 5
            if ($precision > 100 || $precision < 1) {
309
                throw new IntegrityConstraint(
310 4
                    '$precision must be between 1 and 100 inclusive',
311
                    'Provide a precision within range',
312
                    'The E constant cannot have a precision higher than the constant stored (100)'
313
                );
314
            }
315
316
            return self::make(self::IMMUTABLE, self::E, $precision)->truncateToPrecision($precision);
317
        }
318
319
        return self::make(self::IMMUTABLE, self::E, 100);
320
321 1
    }
322
323
    /**
324 1
     * @param int|null $precision
325 1
     *
326 1
     * @return NumberInterface
327 1
     * @throws ReflectionException
328 1
     * @throws IntegrityConstraint
329
     */
330
    public static function makeGoldenRatio($precision = null)
331
    {
332 1
333 1
        if (!is_null($precision)) {
334
            if ($precision > 100 || $precision < 1) {
335 1
                throw new IntegrityConstraint(
336
                    '$precision must be between 1 and 100 inclusive',
337
                    'Provide a precision within range',
338
                    'The Golden Ratio constant cannot have a precision higher than the constant stored (100)'
339
                );
340
            }
341
342
            return self::make(self::IMMUTABLE, self::GOLDEN_RATIO, $precision)->truncateToPrecision($precision);
343
        }
344
345
        return self::make(self::IMMUTABLE, self::GOLDEN_RATIO, 100);
346 2
347
    }
348
349 2
    /**
350 1
     * @param int|null $precision
351 1
     *
352 1
     * @return NumberInterface
353 1
     * @throws ReflectionException
354
     * @throws IntegrityConstraint
355
     */
356
    public static function makeNaturalLog10($precision = null)
357 2
    {
358 1
359
        if (!is_null($precision)) {
360 2
            if ($precision > 100 || $precision < 1) {
361
                throw new IntegrityConstraint(
362
                    '$precision must be between 1 and 100 inclusive',
363
                    'Provide a precision within range',
364
                    'The natural log of 10 constant cannot have a precision higher than the constant stored (100)'
365
                );
366
            }
367
368
            return self::make(self::IMMUTABLE, self::LN_10, $precision)->truncateToPrecision($precision);
369
        }
370
371 41
        return self::make(self::IMMUTABLE, self::LN_10, 100);
372
373 41
    }
374
375
    /**
376
     * @param int|null $precision
377
     *
378
     * @return ImmutableDecimal
379
     * @throws IntegrityConstraint
380
     * @throws ReflectionException
381
     */
382 42
    public static function makeOne($precision = null)
383
    {
384 42
        return self::make(self::IMMUTABLE, 1, $precision);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::make(self::IMMUTABLE, 1, $precision) also could return the type Samsara\Fermat\Types\Bas...t\Values\MutableDecimal which is incompatible with the documented return type Samsara\Fermat\Values\ImmutableDecimal.
Loading history...
385
    }
386
387
    /**
388
     * @param int|null $precision
389
     *
390
     * @return ImmutableDecimal
391
     * @throws IntegrityConstraint
392
     * @throws ReflectionException
393
     */
394
    public static function makeZero($precision = null)
395
    {
396
        return self::make(self::IMMUTABLE, 0, $precision);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::make(self::IMMUTABLE, 0, $precision) also could return the type Samsara\Fermat\Types\Bas...t\Values\MutableDecimal which is incompatible with the documented return type Samsara\Fermat\Values\ImmutableDecimal.
Loading history...
397
    }
398
399
}