1
|
|
|
<?php
|
2
|
|
|
|
3
|
|
|
namespace Samsara\Fermat;
|
4
|
|
|
|
5
|
|
|
use Samsara\Exceptions\UsageError\IntegrityConstraint;
|
6
|
|
|
use Samsara\Fermat\Types\Base\DecimalInterface;
|
7
|
|
|
use Samsara\Fermat\Types\Base\FractionInterface;
|
8
|
|
|
use Samsara\Fermat\Values\Currency;
|
9
|
|
|
use Samsara\Fermat\Values\ImmutableFraction;
|
10
|
|
|
use Samsara\Fermat\Values\ImmutableNumber;
|
11
|
|
|
use Samsara\Fermat\Values\MutableFraction;
|
12
|
|
|
use Samsara\Fermat\Values\MutableNumber;
|
13
|
|
|
use Samsara\Fermat\Types\Base\NumberInterface;
|
14
|
|
|
|
15
|
|
|
class Numbers
|
16
|
|
|
{
|
17
|
|
|
|
18
|
|
|
const MUTABLE = MutableNumber::class;
|
19
|
|
|
const IMMUTABLE = ImmutableNumber::class;
|
20
|
|
|
const MUTABLE_FRACTION = MutableFraction::class;
|
21
|
|
|
const IMMUTABLE_FRACTION = ImmutableFraction::class;
|
22
|
|
|
const CURRENCY = Currency::class;
|
23
|
|
|
/* 105 digits after decimal, which is going to be overkill in almost all places */
|
24
|
|
|
const PI = '3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679';
|
25
|
|
|
/* Tau (2pi) to 100 digits */
|
26
|
|
|
const TAU = '6.283185307179586476925286766559005768394338798750211641949889184615632812572417997256069650684234136';
|
27
|
|
|
/* Euler's Number to 100 digits */
|
28
|
|
|
const E = '2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427';
|
29
|
|
|
/* Golden Ratio to 100 digits */
|
30
|
|
|
const GOLDEN_RATIO = '1.618033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137';
|
31
|
|
|
/* Natural log of 10 to 100 digits */
|
32
|
|
|
const LN_10 = '2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298';
|
33
|
|
|
|
34
|
|
|
/**
|
35
|
|
|
* @param $type
|
36
|
|
|
* @param $value
|
37
|
|
|
* @param int|null $precision
|
38
|
|
|
* @param int $base
|
39
|
|
|
*
|
40
|
|
|
* @throws IntegrityConstraint
|
41
|
|
|
* @return ImmutableNumber|MutableNumber|ImmutableFraction|MutableFraction|NumberInterface
|
42
|
|
|
*/
|
43
|
|
|
public static function make($type, $value, $precision = null, $base = 10)
|
44
|
|
|
{
|
45
|
|
|
|
46
|
|
|
if (is_object($type)) {
|
47
|
|
|
$type = get_class($type);
|
48
|
|
|
}
|
49
|
|
|
|
50
|
|
|
if ($type == static::IMMUTABLE) {
|
51
|
|
|
return new ImmutableNumber(trim($value), $precision, $base);
|
52
|
|
|
} elseif ($type == static::MUTABLE) {
|
53
|
|
|
return new MutableNumber(trim($value), $precision, $base);
|
54
|
|
|
} elseif ($type == static::IMMUTABLE_FRACTION) {
|
55
|
|
|
return self::makeFractionFromString($value, $type)->convertToBase($base);
|
56
|
|
|
} elseif ($type == static::MUTABLE_FRACTION) {
|
57
|
|
|
return self::makeFractionFromString($value, $type)->convertToBase($base);
|
58
|
|
|
} else {
|
59
|
|
|
$reflector = new \ReflectionClass($type);
|
60
|
|
|
|
61
|
|
|
if ($reflector->implementsInterface(NumberInterface::class)) {
|
62
|
|
|
/** @var NumberInterface $customNumber */
|
63
|
|
|
$customNumber = $reflector->newInstance([
|
64
|
|
|
trim($value),
|
65
|
|
|
$precision,
|
66
|
|
|
$base
|
67
|
|
|
]);
|
68
|
|
|
return $customNumber;
|
69
|
|
|
}
|
70
|
|
|
}
|
71
|
|
|
|
72
|
|
|
throw new IntegrityConstraint(
|
73
|
|
|
'$type must be an implementation of NumberInterface',
|
74
|
|
|
'Provide a type that implements NumberInterface (the Numbers class contains constants for the built in ones)',
|
75
|
|
|
'The $type argument was not an implementation of NumberInterface'
|
76
|
|
|
);
|
77
|
|
|
}
|
78
|
|
|
|
79
|
|
|
/**
|
80
|
|
|
* @param $type
|
81
|
|
|
* @param $value
|
82
|
|
|
* @param int|null $precision
|
83
|
|
|
* @param int $base
|
84
|
|
|
* @return NumberInterface
|
85
|
|
|
*/
|
86
|
|
|
public static function makeFromBase10($type, $value, $precision = null, $base = 10)
|
87
|
|
|
{
|
88
|
|
|
/**
|
89
|
|
|
* @var ImmutableNumber|MutableNumber
|
90
|
|
|
*/
|
91
|
|
|
$number = self::make($type, $value, $precision, 10);
|
92
|
|
|
|
93
|
|
|
return $number->convertToBase($base);
|
|
|
|
|
94
|
|
|
}
|
95
|
|
|
|
96
|
|
|
/**
|
97
|
|
|
* @param $type
|
98
|
|
|
* @param int|float|string|NumberInterface|DecimalInterface|FractionInterface $value
|
99
|
|
|
* @param int|null $precision
|
100
|
|
|
* @param int $base
|
101
|
|
|
*
|
102
|
|
|
* @throws IntegrityConstraint
|
103
|
|
|
* @return ImmutableNumber|MutableNumber|NumberInterface|ImmutableNumber[]|MutableNumber[]|NumberInterface[]
|
104
|
|
|
*/
|
105
|
|
|
public static function makeOrDont($type, $value, $precision = null, $base = 10)
|
106
|
|
|
{
|
107
|
|
|
|
108
|
|
|
if (is_object($value)) {
|
109
|
|
|
$reflector = new \ReflectionClass($value);
|
110
|
|
|
|
111
|
|
|
if ($value instanceof $type) {
|
112
|
|
|
return $value;
|
113
|
|
|
}
|
114
|
|
|
|
115
|
|
|
if ($reflector->implementsInterface(NumberInterface::class)) {
|
116
|
|
|
return static::make($type, $value->getValue(), $precision, $base);
|
|
|
|
|
117
|
|
|
}
|
118
|
|
|
} elseif (is_numeric($value)) {
|
119
|
|
|
return static::make($type, $value, $precision, $base);
|
120
|
|
|
} elseif (is_array($value)) {
|
121
|
|
|
$newInput = [];
|
122
|
|
|
|
123
|
|
|
foreach ($value as $key => $item) {
|
124
|
|
|
$newInput[$key] = static::makeOrDont($type, $item, $precision, $base);
|
125
|
|
|
}
|
126
|
|
|
|
127
|
|
|
return $newInput;
|
128
|
|
|
}
|
129
|
|
|
|
130
|
|
|
throw new IntegrityConstraint(
|
131
|
|
|
'$input must be an int, float, numeric string, or an implementation of NumberInterface',
|
132
|
|
|
'Provide any of the MANY valid inputs',
|
133
|
|
|
'The $input argument was not numeric or an implementation of NumberInterface.'
|
134
|
|
|
);
|
135
|
|
|
|
136
|
|
|
}
|
137
|
|
|
|
138
|
|
|
/**
|
139
|
|
|
* @param $value
|
140
|
|
|
* @param $type
|
141
|
|
|
*
|
142
|
|
|
* @return ImmutableFraction|MutableFraction
|
143
|
|
|
* @throws IntegrityConstraint
|
144
|
|
|
*/
|
145
|
|
|
public static function makeFractionFromString($value, $type = self::IMMUTABLE_FRACTION)
|
146
|
|
|
{
|
147
|
|
|
$parts = explode('/', $value);
|
148
|
|
|
|
149
|
|
|
if (count($parts) > 2) {
|
150
|
|
|
throw new IntegrityConstraint(
|
151
|
|
|
'Only one division symbol (/) can be used',
|
152
|
|
|
'Change the calling code to not provide more than one division symbol',
|
153
|
|
|
'makeFractionFromString needs either one or zero division symbols in the $value argument; '.$value.' given'
|
154
|
|
|
);
|
155
|
|
|
}
|
156
|
|
|
|
157
|
|
|
/** @var ImmutableNumber $numerator */
|
158
|
|
|
$numerator = Numbers::make(Numbers::IMMUTABLE, trim(ltrim($parts[0])))->round();
|
|
|
|
|
159
|
|
|
/** @var ImmutableNumber $denominator */
|
160
|
|
|
$denominator = isset($parts[1]) ? Numbers::make(Numbers::IMMUTABLE, trim(ltrim($parts[1])))->round() : Numbers::makeOne();
|
|
|
|
|
161
|
|
|
|
162
|
|
|
if ($type == self::IMMUTABLE_FRACTION) {
|
163
|
|
|
return new ImmutableFraction($numerator, $denominator);
|
164
|
|
|
} elseif ($type == self::MUTABLE_FRACTION) {
|
165
|
|
|
return new MutableFraction($numerator, $denominator);
|
166
|
|
|
} else {
|
167
|
|
|
throw new IntegrityConstraint(
|
168
|
|
|
'Type must be ImmutableFraction or MutableFraction',
|
169
|
|
|
'Alter to calling code to use the correct type',
|
170
|
|
|
'makeFractionFromString can only make objects of type ImmutableFraction or MutableFraction; '.$type.' given'
|
171
|
|
|
);
|
172
|
|
|
}
|
173
|
|
|
}
|
174
|
|
|
|
175
|
|
|
/**
|
176
|
|
|
* @param int|null $precision
|
177
|
|
|
*
|
178
|
|
|
* @throws IntegrityConstraint
|
179
|
|
|
* @return NumberInterface
|
180
|
|
|
*/
|
181
|
|
View Code Duplication |
public static function makePi($precision = null)
|
|
|
|
|
182
|
|
|
{
|
183
|
|
|
|
184
|
|
|
if (!is_null($precision) && ($precision > 100 || $precision < 1)) {
|
185
|
|
|
throw new IntegrityConstraint(
|
186
|
|
|
'$precision must be between 1 and 100 inclusive',
|
187
|
|
|
'Provide a precision within range',
|
188
|
|
|
'The PI constant cannot have a precision higher than the constant stored (100)'
|
189
|
|
|
);
|
190
|
|
|
}
|
191
|
|
|
|
192
|
|
|
if (!is_null($precision)) {
|
193
|
|
|
return self::make(self::IMMUTABLE, self::PI, $precision)->roundToPrecision($precision);
|
|
|
|
|
194
|
|
|
} else {
|
195
|
|
|
return self::make(self::IMMUTABLE, self::PI, 100);
|
196
|
|
|
}
|
197
|
|
|
|
198
|
|
|
}
|
199
|
|
|
|
200
|
|
|
/**
|
201
|
|
|
* @param int|null $precision
|
202
|
|
|
*
|
203
|
|
|
* @throws IntegrityConstraint
|
204
|
|
|
* @return NumberInterface
|
205
|
|
|
*/
|
206
|
|
View Code Duplication |
public static function makeTau($precision = null)
|
|
|
|
|
207
|
|
|
{
|
208
|
|
|
if (!is_null($precision) && ($precision > 100 || $precision < 1)) {
|
209
|
|
|
throw new IntegrityConstraint(
|
210
|
|
|
'$precision must be between 1 and 100 inclusive',
|
211
|
|
|
'Provide a precision within range',
|
212
|
|
|
'The TAU constant cannot have a precision higher than the constant stored (100)'
|
213
|
|
|
);
|
214
|
|
|
}
|
215
|
|
|
|
216
|
|
|
if (!is_null($precision)) {
|
217
|
|
|
return self::make(self::IMMUTABLE, self::TAU, $precision)->roundToPrecision($precision);
|
|
|
|
|
218
|
|
|
} else {
|
219
|
|
|
return self::make(self::IMMUTABLE, self::TAU, 100);
|
220
|
|
|
}
|
221
|
|
|
}
|
222
|
|
|
|
223
|
|
|
/**
|
224
|
|
|
* @param int|null $precision
|
225
|
|
|
*
|
226
|
|
|
* @return NumberInterface
|
227
|
|
|
*/
|
228
|
|
|
public static function make2Pi($precision = null)
|
229
|
|
|
{
|
230
|
|
|
return self::makeTau($precision);
|
231
|
|
|
}
|
232
|
|
|
|
233
|
|
|
/**
|
234
|
|
|
* @param int|null $precision
|
235
|
|
|
*
|
236
|
|
|
* @throws IntegrityConstraint
|
237
|
|
|
* @return NumberInterface
|
238
|
|
|
*/
|
239
|
|
View Code Duplication |
public static function makeE($precision = null)
|
|
|
|
|
240
|
|
|
{
|
241
|
|
|
|
242
|
|
|
if (!is_null($precision) && ($precision > 100 || $precision < 1)) {
|
243
|
|
|
throw new IntegrityConstraint(
|
244
|
|
|
'$precision must be between 1 and 100 inclusive',
|
245
|
|
|
'Provide a precision within range',
|
246
|
|
|
'The E constant cannot have a precision higher than the constant stored (100)'
|
247
|
|
|
);
|
248
|
|
|
}
|
249
|
|
|
|
250
|
|
|
if (!is_null($precision)) {
|
251
|
|
|
return self::make(self::IMMUTABLE, self::E, $precision)->roundToPrecision($precision);
|
|
|
|
|
252
|
|
|
} else {
|
253
|
|
|
return self::make(self::IMMUTABLE, self::E, 100);
|
254
|
|
|
}
|
255
|
|
|
|
256
|
|
|
}
|
257
|
|
|
|
258
|
|
|
/**
|
259
|
|
|
* @param int|null $precision
|
260
|
|
|
*
|
261
|
|
|
* @throws IntegrityConstraint
|
262
|
|
|
* @return NumberInterface
|
263
|
|
|
*/
|
264
|
|
View Code Duplication |
public static function makeGoldenRatio($precision = null)
|
|
|
|
|
265
|
|
|
{
|
266
|
|
|
|
267
|
|
|
if (!is_null($precision) && ($precision > 100 || $precision < 1)) {
|
268
|
|
|
throw new IntegrityConstraint(
|
269
|
|
|
'$precision must be between 1 and 100 inclusive',
|
270
|
|
|
'Provide a precision within range',
|
271
|
|
|
'The Golden Ratio constant cannot have a precision higher than the constant stored (100)'
|
272
|
|
|
);
|
273
|
|
|
}
|
274
|
|
|
|
275
|
|
|
if (!is_null($precision)) {
|
276
|
|
|
return self::make(self::IMMUTABLE, self::GOLDEN_RATIO, $precision)->roundToPrecision($precision);
|
|
|
|
|
277
|
|
|
} else {
|
278
|
|
|
return self::make(self::IMMUTABLE, self::GOLDEN_RATIO, 100);
|
279
|
|
|
}
|
280
|
|
|
|
281
|
|
|
}
|
282
|
|
|
|
283
|
|
|
/**
|
284
|
|
|
* @param int|null $precision
|
285
|
|
|
*
|
286
|
|
|
* @throws IntegrityConstraint
|
287
|
|
|
* @return NumberInterface
|
288
|
|
|
*/
|
289
|
|
View Code Duplication |
public static function makeNaturalLog10($precision = null)
|
|
|
|
|
290
|
|
|
{
|
291
|
|
|
|
292
|
|
|
if (!is_null($precision) && ($precision > 100 || $precision < 1)) {
|
293
|
|
|
throw new IntegrityConstraint(
|
294
|
|
|
'$precision must be between 1 and 100 inclusive',
|
295
|
|
|
'Provide a precision within range',
|
296
|
|
|
'The natural log of 10 constant cannot have a precision higher than the constant stored (100)'
|
297
|
|
|
);
|
298
|
|
|
}
|
299
|
|
|
|
300
|
|
|
if (!is_null($precision)) {
|
301
|
|
|
return self::make(self::IMMUTABLE, self::LN_10, $precision)->roundToPrecision($precision);
|
|
|
|
|
302
|
|
|
} else {
|
303
|
|
|
return self::make(self::IMMUTABLE, self::LN_10, 100);
|
304
|
|
|
}
|
305
|
|
|
|
306
|
|
|
}
|
307
|
|
|
|
308
|
|
|
/**
|
309
|
|
|
* @return ImmutableNumber
|
310
|
|
|
*/
|
311
|
|
|
public static function makeOne($precision = null)
|
312
|
|
|
{
|
313
|
|
|
return self::make(self::IMMUTABLE, 1, $precision);
|
314
|
|
|
}
|
315
|
|
|
|
316
|
|
|
/**
|
317
|
|
|
* @return ImmutableNumber
|
318
|
|
|
*/
|
319
|
|
|
public static function makeZero($precision = null)
|
320
|
|
|
{
|
321
|
|
|
return self::make(self::IMMUTABLE, 0, $precision);
|
322
|
|
|
}
|
323
|
|
|
|
324
|
|
|
} |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.