Passed
Pull Request — dev (#56)
by Jordan
08:50
created

Numbers::makeFractionFromString()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7.6055

Importance

Changes 0
Metric Value
cc 5
eloc 16
c 0
b 0
f 0
nc 7
nop 3
dl 0
loc 29
ccs 9
cts 17
cp 0.5294
crap 7.6055
rs 9.4222
1
<?php
2
3
namespace Samsara\Fermat;
4
5
use ReflectionException;
6
use Samsara\Exceptions\UsageError\IntegrityConstraint;
7
use Samsara\Fermat\Provider\ConstantProvider;
8
use Samsara\Fermat\Types\Base\Interfaces\Coordinates\CoordinateInterface;
9
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
10
use Samsara\Fermat\Types\Base\Interfaces\Numbers\FractionInterface;
11
use Samsara\Fermat\Types\Base\Interfaces\Numbers\NumberInterface;
12
use Samsara\Fermat\Types\Base\Selectable;
13
use Samsara\Fermat\Types\Fraction;
14
use Samsara\Fermat\Values\Geometry\CoordinateSystems\CartesianCoordinate;
15
use Samsara\Fermat\Values\ImmutableFraction;
16
use Samsara\Fermat\Values\ImmutableDecimal;
17
use Samsara\Fermat\Values\MutableFraction;
18
use Samsara\Fermat\Values\MutableDecimal;
19
20
class Numbers
21
{
22
23
    public const MUTABLE = MutableDecimal::class;
24
    public const IMMUTABLE = ImmutableDecimal::class;
25
    public const MUTABLE_FRACTION = MutableFraction::class;
26
    public const IMMUTABLE_FRACTION = ImmutableFraction::class;
27
    public const CARTESIAN_COORDINATE = CartesianCoordinate::class;
28
    /* 105 digits after decimal, which is going to be overkill in almost all places */
29
    public const PI = '3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679';
30
    /* Tau (2pi) to 100 digits */
31
    public const TAU = '6.283185307179586476925286766559005768394338798750211641949889184615632812572417997256069650684234136';
32
    /* Euler's Number to 100 digits */
33
    public const E = '2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427';
34
    /* Golden Ratio to 100 digits */
35
    public const GOLDEN_RATIO = '1.618033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137';
36
    /* Natural log of 10 to 100 digits */
37
    public const LN_10 = '2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298';
38
    /* The value of i^i */
39
    public const I_POW_I = '0.2078795763507619085469556198349787700338778416317696080751358830554198772854821397886002778654260353';
40
41
    protected static $defaultCalcMode = Selectable::CALC_MODE_PRECISION;
42
43
    /**
44
     * @param $type
45
     * @param $value
46
     * @param int|null $precision
47
     * @param int $base
48
     *
49
     * @return ImmutableDecimal|MutableDecimal|ImmutableFraction|MutableFraction|CartesianCoordinate|NumberInterface|FractionInterface|CoordinateInterface
50
     * @throws IntegrityConstraint
51
     */
52 135
    public static function make($type, $value, $precision = null, $base = 10)
53
    {
54
55 135
        if (is_object($type)) {
56 103
            $type = get_class($type);
57
        }
58
59 135
        if ($type === static::IMMUTABLE) {
60 135
            return new ImmutableDecimal(trim($value), $precision, $base);
61
        }
62
63 21
        if ($type === static::MUTABLE) {
64 12
            return new MutableDecimal(trim($value), $precision, $base);
65
        }
66
67 12
        if ($type === static::IMMUTABLE_FRACTION) {
68 11
            return self::makeFractionFromString($type, $value, $base);
69
        }
70
71 4
        if ($type === static::MUTABLE_FRACTION) {
72 3
            return self::makeFractionFromString($type, $value, $base);
73
        }
74
75 1
        if ($type === static::CARTESIAN_COORDINATE && is_array($value)) {
76 1
            $x = $value[0];
77 1
            $y = $value[1] ?? null;
78 1
            $z = $value[2] ?? null;
79
80 1
            return new CartesianCoordinate($x, $y, $z);
81
        }
82
83
        throw new IntegrityConstraint(
84
            '$type must be an implemented concrete class that is supported',
85
            'Provide a type that implements NumberInterface or CoordinateInterface (the Numbers class contains constants for the built in ones)',
86
            'The $type argument was not an implementation of NumberInterface or CoordinateInterface'
87
        );
88
    }
89
90
    /**
91
     * @param $type
92
     * @param $value
93
     * @param int|null $precision
94
     * @param int $base
95
     *
96
     * @return NumberInterface
97
     * @throws IntegrityConstraint
98
     */
99 1
    public static function makeFromBase10($type, $value, $precision = null, $base = 10): NumberInterface
100
    {
101
        /**
102
         * @var ImmutableDecimal|MutableDecimal
103
         */
104 1
        $number = self::make($type, $value, $precision, 10);
105
106 1
        return $number->convertToBase($base);
0 ignored issues
show
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

106
        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\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

106
        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\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

106
        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...
107
    }
108
109
    /**
110
     * @param string|object $type
111
     * @param int|float|string|array|NumberInterface|DecimalInterface|FractionInterface $value
112
     * @param int|null $precision
113
     * @param int $base
114
     *
115
     * @return ImmutableDecimal|MutableDecimal|NumberInterface|ImmutableDecimal[]|MutableDecimal[]|NumberInterface[]
116
     * @throws IntegrityConstraint
117
     */
118 126
    public static function makeOrDont($type, $value, $precision = null, $base = 10)
119
    {
120
121 126
        if (is_object($value)) {
122 97
            if ($value instanceof $type) {
123 97
                return $value;
124
            }
125
126 4
            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...
127 4
                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

127
                return static::make($type, $value->/** @scrutinizer ignore-call */ getValue(), $precision, $base);
Loading history...
128
            }
129 116
        } elseif (is_array($value)) {
130 14
            $newInput = [];
131
132 14
            foreach ($value as $key => $item) {
133 14
                $newInput[$key] = static::makeOrDont($type, $item, $precision, $base);
134
            }
135
136 14
            return $newInput;
137 116
        } 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...
138 116
            $isImaginary = strpos($value, 'i') !== false;
139
140 116
            if (is_numeric($value) || $isImaginary) {
141 116
                return static::make($type, $value, $precision, $base);
142
            }
143
        }
144
145
        throw new IntegrityConstraint(
146
            '$input must be an int, float, numeric string, or an implementation of NumberInterface',
147
            'Provide any of the MANY valid inputs',
148
            'The $input argument was not numeric or an implementation of NumberInterface. Given value: '.$value
149
        );
150
151
    }
152
153
    /**
154
     * @param string $type
155
     * @param string $value
156
     * @param int $base
157
     *
158
     * @return FractionInterface|ImmutableFraction|MutableFraction
159
     * @throws IntegrityConstraint
160
     */
161 11
    public static function makeFractionFromString(string $type, string $value, int $base = 10): FractionInterface
162
    {
163 11
        $parts = explode('/', $value);
164
165 11
        if (count($parts) > 2) {
166
            throw new IntegrityConstraint(
167
                'Only one division symbol (/) can be used',
168
                'Change the calling code to not provide more than one division symbol',
169
                'makeFractionFromString needs either one or zero division symbols in the $value argument; '.$value.' given'
170
            );
171
        }
172
173
        /** @var ImmutableDecimal $numerator */
174 11
        $numerator = self::make(self::IMMUTABLE, trim(ltrim($parts[0])))->round();
175
        /** @var ImmutableDecimal $denominator */
176 11
        $denominator = isset($parts[1]) ? self::make(self::IMMUTABLE, trim(ltrim($parts[1])))->round() : self::makeOne();
177
178 11
        if ($type === self::IMMUTABLE_FRACTION) {
179 11
            return new ImmutableFraction($numerator, $denominator, $base);
180
        }
181
182 3
        if ($type === self::MUTABLE_FRACTION) {
183 3
            return new MutableFraction($numerator, $denominator, $base);
184
        }
185
186
        throw new IntegrityConstraint(
187
            'Type must be an implementation of FractionInterface',
188
            'Alter to calling code to use the correct type',
189
            'makeFractionFromString can only make objects which implement the FractionInterface; '.$type.' given'
190
        );
191
    }
192
193
    /**
194
     * @param int|null $precision
195
     *
196
     * @return DecimalInterface
197
     * @throws IntegrityConstraint
198
     */
199 34
    public static function makePi(int $precision = null)
200
    {
201
202 34
        if (!is_null($precision)) {
203 26
            if ($precision < 1) {
204 1
                throw new IntegrityConstraint(
205 1
                    '$precision must be at least 1',
206 1
                    'Provide a precision within range',
207 1
                    'The pi constant cannot have a precision less than 1'
208
                );
209
            }
210
211 26
            if ($precision > 100) {
212 1
                return self::make(self::IMMUTABLE, ConstantProvider::makePi($precision), $precision);
213
            }
214
215 26
            return self::make(self::IMMUTABLE, self::PI, $precision)->truncateToPrecision($precision);
216
        }
217
218 23
        return self::make(self::IMMUTABLE, self::PI, 100);
1 ignored issue
show
Bug Best Practice introduced by
The expression return self::make(self::IMMUTABLE, self::PI, 100) also could return the type Samsara\Fermat\Values\Im...\Values\MutableFraction which is incompatible with the documented return type Samsara\Fermat\Types\Bas...umbers\DecimalInterface.
Loading history...
219
220
    }
221
222
    /**
223
     * @param int|null $precision
224
     *
225
     * @return DecimalInterface
226
     * @throws IntegrityConstraint
227
     */
228 23
    public static function makeTau($precision = null)
229
    {
230 23
        if (!is_null($precision)) {
231 10
            if ($precision < 1) {
232 1
                throw new IntegrityConstraint(
233 1
                    '$precision must be at least 1',
234 1
                    'Provide a precision within range',
235 1
                    'The E constant cannot have a precision less than 1'
236
                );
237
            }
238
239 10
            if ($precision > 100) {
240 1
                $pi = self::make(self::IMMUTABLE, ConstantProvider::makePi($precision), $precision + 2);
241 1
                return $pi->multiply(2)->truncateToPrecision($precision);
0 ignored issues
show
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

241
                return $pi->multiply(2)->/** @scrutinizer ignore-call */ truncateToPrecision($precision);
Loading history...
Bug introduced by
The method truncateToPrecision() 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

241
                return $pi->multiply(2)->/** @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...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

241
                return $pi->multiply(2)->/** @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\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

241
                return $pi->multiply(2)->/** @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...
242
            }
243
244 10
            return self::make(self::IMMUTABLE, self::TAU, $precision)->truncateToPrecision($precision);
245
        }
246
247 23
        return self::make(self::IMMUTABLE, self::TAU, 100);
1 ignored issue
show
Bug Best Practice introduced by
The expression return self::make(self::IMMUTABLE, self::TAU, 100) also could return the type Samsara\Fermat\Values\Im...\Values\MutableFraction which is incompatible with the documented return type Samsara\Fermat\Types\Bas...umbers\DecimalInterface.
Loading history...
248
    }
249
250
    /**
251
     * @param int|null $precision
252
     *
253
     * @return DecimalInterface
254
     * @throws IntegrityConstraint
255
     */
256 23
    public static function make2Pi($precision = null)
257
    {
258 23
        return self::makeTau($precision);
259
    }
260
261
    /**
262
     * @param int|null $precision
263
     *
264
     * @return DecimalInterface
265
     * @throws IntegrityConstraint
266
     */
267 9
    public static function makeE(int $precision = null)
268
    {
269
270 9
        if (!is_null($precision)) {
271 7
            if ($precision < 1) {
272 1
                throw new IntegrityConstraint(
273 1
                    '$precision must be at least 1',
274 1
                    'Provide a precision within range',
275 1
                    'The E constant cannot have a precision less than 1'
276
                );
277
            }
278
279 7
            if ($precision > 100) {
280 1
                return self::make(self::IMMUTABLE, ConstantProvider::makeE($precision), $precision);
281
            }
282
283 7
            return self::make(self::IMMUTABLE, self::E, $precision)->truncateToPrecision($precision);
284
        }
285
286 4
        return self::make(self::IMMUTABLE, self::E, 100);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::make(self::IMMUTABLE, self::E, 100) also could return the type Samsara\Fermat\Values\Im...\Values\MutableFraction which is incompatible with the documented return type Samsara\Fermat\Types\Bas...umbers\DecimalInterface.
Loading history...
287
288
    }
289
290
    /**
291
     * @param int|null $precision
292
     *
293
     * @return NumberInterface
294
     * @throws IntegrityConstraint
295
     */
296 1
    public static function makeGoldenRatio($precision = null)
297
    {
298
299 1
        if (!is_null($precision)) {
300 1
            if ($precision > 100 || $precision < 1) {
301 1
                throw new IntegrityConstraint(
302 1
                    '$precision must be between 1 and 100 inclusive',
303 1
                    'Provide a precision within range',
304 1
                    'The Golden Ratio constant cannot have a precision higher than the constant stored (100)'
305
                );
306
            }
307
308 1
            return self::make(self::IMMUTABLE, self::GOLDEN_RATIO, $precision)->truncateToPrecision($precision);
309
        }
310
311 1
        return self::make(self::IMMUTABLE, self::GOLDEN_RATIO, 100);
312
313
    }
314
315
    /**
316
     * @param int|null $precision
317
     *
318
     * @return NumberInterface
319
     * @throws IntegrityConstraint
320
     */
321 2
    public static function makeNaturalLog10($precision = null)
322
    {
323
324 2
        if (!is_null($precision)) {
325 1
            if ($precision > 100 || $precision < 1) {
326 1
                throw new IntegrityConstraint(
327 1
                    '$precision must be between 1 and 100 inclusive',
328 1
                    'Provide a precision within range',
329 1
                    'The natural log of 10 constant cannot have a precision higher than the constant stored (100)'
330
                );
331
            }
332
333 1
            return self::make(self::IMMUTABLE, self::LN_10, $precision)->truncateToPrecision($precision);
334
        }
335
336 2
        return self::make(self::IMMUTABLE, self::LN_10, 100);
337
338
    }
339
340
    /**
341
     * @param int|null $precision
342
     *
343
     * @return ImmutableDecimal
344
     * @throws IntegrityConstraint
345
     */
346 68
    public static function makeOne($precision = null)
347
    {
348 68
        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\Values\Im...\Values\MutableFraction which is incompatible with the documented return type Samsara\Fermat\Values\ImmutableDecimal.
Loading history...
349
    }
350
351
    /**
352
     * @param int|null $precision
353
     *
354
     * @return ImmutableDecimal
355
     * @throws IntegrityConstraint
356
     */
357 58
    public static function makeZero($precision = null)
358
    {
359 58
        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\Values\Im...\Values\MutableFraction which is incompatible with the documented return type Samsara\Fermat\Values\ImmutableDecimal.
Loading history...
360
    }
361
362 141
    public static function getDefaultCalcMode(): int
363
    {
364 141
        return self::$defaultCalcMode;
365
    }
366
367
    public static function setDefaultCalcMode(int $mode): void
368
    {
369
        self::$defaultCalcMode = $mode;
370
    }
371
372
}