Passed
Pull Request — master (#132)
by Jordan
17:22
created

Numbers::makeNaturalLog2()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.25

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 4
nop 1
dl 0
loc 20
ccs 6
cts 8
cp 0.75
crap 4.25
rs 9.9332
c 0
b 0
f 0
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 714
    public static function make(mixed $type, mixed $value, ?int $scale = null, NumberBase $base = NumberBase::Ten)
60
    {
61
62 714
        if (is_object($type)) {
63 140
            $type = get_class($type);
64
        }
65
66 714
        if ($type === static::IMMUTABLE) {
67 714
            return new ImmutableDecimal(trim($value), $scale, $base, true);
68
        }
69
70 254
        if ($type === static::MUTABLE) {
71 149
            return new MutableDecimal(trim($value), $scale, $base, true);
72
        }
73
74 106
        if ($type === static::IMMUTABLE_FRACTION) {
75 104
            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 709
    public static function makeOrDont(string|object $type, mixed $value, ?int $scale = null, NumberBase $base = NumberBase::Ten)
118
    {
119
120 709
        if (is_object($value)) {
121 684
            if ($value instanceof $type) {
122 665
                return $value;
123
            }
124
125 254
            if ($value instanceof NumberInterface) {
126 254
                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 707
        } 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 707
        } elseif (is_string($value) || is_int($value) || is_float($value)) {
137 707
            $isImaginary = str_contains($value, 'i');
138
139 707
            if (is_numeric($value) || $isImaginary) {
140 707
                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 107
    public static function makeFractionFromString(string $type, string $value, NumberBase $base = NumberBase::Ten): FractionInterface
161
    {
162 107
        $parts = explode('/', $value);
163
164 107
        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 107
        $numerator = self::make(self::IMMUTABLE, trim(ltrim($parts[0])));
174
        /** @var ImmutableDecimal $denominator */
175 107
        $denominator = isset($parts[1]) ? self::make(self::IMMUTABLE, trim(ltrim($parts[1]))) : self::makeOne();
176
177 107
        if ($type === self::IMMUTABLE_FRACTION) {
178 105
            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 69
    public static function makePi(int $scale = null): ImmutableDecimal
199
    {
200
201 69
        if (!is_null($scale)) {
202 68
            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 68
            if ($scale > 100) {
211 2
                return self::make(self::IMMUTABLE, ConstantProvider::makePi($scale), $scale);
212
            }
213
214 68
            return self::make(self::IMMUTABLE, self::PI, $scale+1)->truncateToScale($scale);
215
        }
216
217 5
        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 62
    public static function makeTau($scale = null): ImmutableDecimal
228
    {
229 62
        if (!is_null($scale)) {
0 ignored issues
show
introduced by
The condition is_null($scale) is always true.
Loading history...
230 62
            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 62
            if ($scale > 100) {
239 2
                $pi = self::make(self::IMMUTABLE, ConstantProvider::makePi($scale+2), $scale + 2);
240
                /** @var ImmutableDecimal */
241 2
                return $pi->multiply(2)->truncateToScale($scale);
242
            }
243
244 62
            return self::make(self::IMMUTABLE, self::TAU, $scale+1)->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 60
    public static function make2Pi(int $scale = null): ImmutableDecimal
257
    {
258 60
        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+1)->truncateToScale($scale);
0 ignored issues
show
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

283
            return self::make(self::IMMUTABLE, self::E, $scale+1)->/** @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\ImmutableFraction. ( Ignorable by Annotation )

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

283
            return self::make(self::IMMUTABLE, self::E, $scale+1)->/** @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...
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+1)->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+1)->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 19
    public static function makeNaturalLog2(?int $scale = null): ImmutableDecimal
351
    {
352
353 19
        if (!is_null($scale)) {
354 19
            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 19
            if ($scale > 100) {
363 3
                return self::make(self::IMMUTABLE, ConstantProvider::makeLn2($scale), $scale);
364
            }
365
366 19
            return self::make(self::IMMUTABLE, self::LN_2, $scale+1)->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 183
    public static function makeOne(?int $scale = null): ImmutableDecimal
380
    {
381 183
        return self::make(self::IMMUTABLE, 1, $scale);
382
    }
383
384
    /**
385
     * @param int|null $scale
386
     *
387
     * @return ImmutableDecimal
388
     * @throws IntegrityConstraint
389
     */
390 75
    public static function makeZero(?int $scale = null): ImmutableDecimal
391
    {
392 75
        return self::make(self::IMMUTABLE, 0, $scale);
393
    }
394
395
}