1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace Sphpeme\Env; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* @SuppressWarnings(PHPMD.ShortVariableName) |
8
|
|
|
*/ |
9
|
|
|
class MathEnv implements EnvInterface |
10
|
|
|
{ |
11
|
|
|
use MappedEnvTrait; |
12
|
|
|
|
13
|
|
|
const INVALID_ARG_REQUIRES_NUMERIC = 'All arguments must be numeric'; |
14
|
|
|
const INVALID_ARG_TYPE_PRED_MATCH = 'Supplied arguments do not match the first'; |
15
|
|
|
|
16
|
|
|
const MAPPING = [ |
17
|
|
|
'+' => 'add', |
18
|
|
|
'-' => 'subtract', |
19
|
|
|
'*' => 'multiply', |
20
|
|
|
'/' => 'divide', |
21
|
|
|
'>' => 'isGreaterThan', |
22
|
|
|
'>=' => 'isGreaterThanOrEqual', |
23
|
|
|
'<' => 'isSmallerThan', |
24
|
|
|
'<=' => 'isSmallerThanOrEqual', |
25
|
|
|
'=' => 'isEqual', |
26
|
|
|
'number?' => 'isNumber', |
27
|
|
|
'integer?' => 'isInteger', |
28
|
|
|
'real?' => 'isReal', |
29
|
|
|
'complex?' => 'isComplex', |
30
|
|
|
'exact?' => 'isExact', |
31
|
|
|
'inexact?' => 'isInexact', |
32
|
|
|
'positive?' => 'isPositive', |
33
|
|
|
'negative?' => 'isNegative', |
34
|
|
|
'odd?' => 'isOdd', |
35
|
|
|
'even?' => 'isEven', |
36
|
|
|
]; |
37
|
|
|
|
38
|
|
|
/** @var float */ |
39
|
|
|
public $pi = M_PI; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Sum the args |
43
|
|
|
* |
44
|
|
|
* @param int[]|float[] $args |
45
|
|
|
* @return int|float |
46
|
|
|
* @throws \InvalidArgumentException |
47
|
|
|
*/ |
48
|
1 |
|
public function add(...$args) |
49
|
|
|
{ |
50
|
1 |
|
array_reduce($args, [$this, 'enforcePredicate'], [$this, 'isNumber']); |
51
|
1 |
|
return array_sum($args); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Subtract the args |
56
|
|
|
* |
57
|
|
|
* @param int[]|float[] $args |
58
|
|
|
* @return int|float |
59
|
|
|
* @throws \InvalidArgumentException |
60
|
|
|
*/ |
61
|
1 |
View Code Duplication |
public function subtract(...$args) |
|
|
|
|
62
|
|
|
{ |
63
|
1 |
|
array_reduce($args, [$this, 'enforcePredicate'], [$this, 'isNumber']); |
64
|
|
|
|
65
|
1 |
|
$arg = $args[0]; |
66
|
1 |
|
$args = \array_slice($args, 1); |
67
|
|
|
|
68
|
1 |
|
return $arg - array_sum($args); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Multiply the args |
73
|
|
|
* |
74
|
|
|
* @param int[]|float[] $args |
75
|
|
|
* @return int|float |
76
|
|
|
* @throws \InvalidArgumentException |
77
|
|
|
*/ |
78
|
1 |
|
public function multiply(...$args) |
79
|
|
|
{ |
80
|
1 |
|
array_reduce($args, [$this, 'enforcePredicate'], [$this, 'isNumber']); |
81
|
1 |
|
return array_product($args); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Divide the args |
86
|
|
|
* |
87
|
|
|
* @param int[]|float[] $args |
88
|
|
|
* @return int|float |
89
|
|
|
* @throws \InvalidArgumentException |
90
|
|
|
*/ |
91
|
1 |
View Code Duplication |
public function divide(...$args) |
|
|
|
|
92
|
|
|
{ |
93
|
1 |
|
array_reduce($args, [$this, 'enforcePredicate'], [$this, 'isNumber']); |
94
|
|
|
|
95
|
1 |
|
$arg = $args[0]; |
96
|
1 |
|
$args = \array_slice($args, 1); |
97
|
|
|
|
98
|
1 |
|
return $arg / array_product($args); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Returns true if $a is greater than $b, otherwise false |
103
|
|
|
* |
104
|
|
|
* @SuppressWarnings(PHPMD.ShortVariable) |
105
|
|
|
* @param int|float $a |
106
|
|
|
* @param int|float $b |
107
|
|
|
* @return bool |
108
|
|
|
*/ |
109
|
1 |
|
public function isGreaterThan($a, $b): bool |
110
|
|
|
{ |
111
|
1 |
|
array_reduce([$a, $b], [$this, 'enforcePredicate'], [$this, 'isNumber']); |
112
|
1 |
|
return $a > $b; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Returns true if $a is greater than or equal to $b, otherwise false |
117
|
|
|
* |
118
|
|
|
* @SuppressWarnings(PHPMD.ShortVariable) |
119
|
|
|
* @param int|float $a |
120
|
|
|
* @param int|float $b |
121
|
|
|
* @return bool |
122
|
|
|
*/ |
123
|
1 |
|
public function isGreaterThanOrEqual($a, $b): bool |
124
|
|
|
{ |
125
|
1 |
|
array_reduce([$a, $b], [$this, 'enforcePredicate'], [$this, 'isNumber']); |
126
|
1 |
|
return $a >= $b; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Returns true if $a is smaller than or equal to $b, otherwise false |
131
|
|
|
* |
132
|
|
|
* @SuppressWarnings(PHPMD.ShortVariable) |
133
|
|
|
* @param int|float $a |
134
|
|
|
* @param int|float $b |
135
|
|
|
* @return bool |
136
|
|
|
*/ |
137
|
1 |
|
public function isSmallerThanOrEqual($a, $b): bool |
138
|
|
|
{ |
139
|
1 |
|
array_reduce([$a, $b], [$this, 'enforcePredicate'], [$this, 'isNumber']); |
140
|
1 |
|
return $a <= $b; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Returns true if $a is smaller than $b, otherwise false |
145
|
|
|
* |
146
|
|
|
* @SuppressWarnings(PHPMD.ShortVariable) |
147
|
|
|
* @param int|float $a |
148
|
|
|
* @param int|float $b |
149
|
|
|
* @return bool |
150
|
|
|
*/ |
151
|
1 |
|
public function isSmallerThan($a, $b): bool |
152
|
|
|
{ |
153
|
1 |
|
array_reduce([$a, $b], [$this, 'enforcePredicate'], [$this, 'isNumber']); |
154
|
1 |
|
return $a < $b; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* @param int[]|float[] ...$args |
159
|
|
|
* @return bool |
160
|
|
|
* @throws \InvalidArgumentException |
161
|
|
|
*/ |
162
|
1 |
|
public function isEqual(...$args): bool |
163
|
|
|
{ |
164
|
1 |
|
array_reduce($args, [$this, 'enforcePredicate'], [$this, 'isNumber']); |
165
|
|
|
|
166
|
1 |
|
$arg = $args[0]; |
167
|
1 |
|
$args = \array_slice($args, 1); |
168
|
|
|
|
169
|
1 |
|
foreach ($args as $other) { |
170
|
1 |
|
if ($arg != $other) { |
171
|
1 |
|
return false; |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|
175
|
1 |
|
return true; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* @param mixed $num |
180
|
|
|
* @return bool |
181
|
|
|
*/ |
182
|
21 |
|
public function isNumber($num): bool |
183
|
|
|
{ |
184
|
21 |
|
return !\is_string($num) && is_numeric($num); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Scheme's rules for integerness are slightly different than PHP's: |
189
|
|
|
* An integer can be a "float" but it must have `.0` |
190
|
|
|
* |
191
|
|
|
* @param mixed $num |
192
|
|
|
* @return bool |
193
|
|
|
*/ |
194
|
1 |
|
public function isInteger($num): bool |
195
|
|
|
{ |
196
|
1 |
|
return \is_int($num) ?: $this->isNumber($num) && $num == (int)$num; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Not implemented yet. |
201
|
|
|
* Requires complex number support |
202
|
|
|
* |
203
|
|
|
* @param $num |
204
|
|
|
* @return bool |
205
|
|
|
* @throws \Error |
206
|
|
|
* @SuppressWarnings(PHPMD) |
207
|
|
|
*/ |
208
|
1 |
|
public function isComplex($num): bool |
|
|
|
|
209
|
|
|
{ |
210
|
1 |
|
throw new \Error('Not implemented'); |
|
|
|
|
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Not implemented yet. |
215
|
|
|
* Requires complex number support |
216
|
|
|
* |
217
|
|
|
* @param $num |
218
|
|
|
* @return bool |
219
|
|
|
* @throws \Error |
220
|
|
|
* @SuppressWarnings(PHPMD) |
221
|
|
|
*/ |
222
|
1 |
|
public function isReal($num): bool |
|
|
|
|
223
|
|
|
{ |
224
|
1 |
|
throw new \Error('Not implemented'); |
|
|
|
|
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @param $num |
229
|
|
|
* @return bool |
230
|
|
|
* @throws \InvalidArgumentException |
231
|
|
|
*/ |
232
|
2 |
|
public function isExact($num): bool |
233
|
|
|
{ |
234
|
2 |
|
$this->enforcePredicate([$this, 'isNumber'], $num); |
235
|
|
|
|
236
|
2 |
|
return !\is_float($num); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @param $num |
241
|
|
|
* @return bool |
242
|
|
|
* @throws \InvalidArgumentException |
243
|
|
|
*/ |
244
|
1 |
|
public function isInexact($num): bool |
245
|
|
|
{ |
246
|
1 |
|
return !$this->isExact($num); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* @param $num |
251
|
|
|
* @return bool |
252
|
|
|
* @throws \InvalidArgumentException |
253
|
|
|
*/ |
254
|
1 |
|
public function isZero($num): bool |
255
|
|
|
{ |
256
|
1 |
|
$this->enforcePredicate([$this, 'isNumber'], $num); |
257
|
|
|
|
258
|
1 |
|
return $num == 0; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* @param $num |
263
|
|
|
* @return bool |
264
|
|
|
* @throws \InvalidArgumentException |
265
|
|
|
*/ |
266
|
2 |
|
public function isPositive($num): bool |
267
|
|
|
{ |
268
|
2 |
|
$this->enforcePredicate([$this, 'isNumber'], $num); |
269
|
|
|
|
270
|
2 |
|
return $num > 0; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* @param $num |
275
|
|
|
* @return bool |
276
|
|
|
* @throws \InvalidArgumentException |
277
|
|
|
*/ |
278
|
1 |
|
public function isNegative($num): bool |
279
|
|
|
{ |
280
|
1 |
|
return !$this->isPositive($num); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* @param $num |
285
|
|
|
* @return bool |
286
|
|
|
* @throws \InvalidArgumentException |
287
|
|
|
*/ |
288
|
2 |
|
public function isOdd($num): bool |
289
|
|
|
{ |
290
|
2 |
|
$this->enforcePredicate([$this, 'isNumber'], $num); |
291
|
|
|
|
292
|
2 |
|
return $num % 2 != 0; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* @param $num |
297
|
|
|
* @return bool |
298
|
|
|
* @throws \InvalidArgumentException |
299
|
|
|
*/ |
300
|
1 |
|
public function isEven($num): bool |
301
|
|
|
{ |
302
|
1 |
|
return !$this->isOdd($num); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* @param int[]|float[] ...$args |
307
|
|
|
* @return int|float |
308
|
|
|
* @throws \InvalidArgumentException |
309
|
|
|
*/ |
310
|
1 |
|
public function max(...$args) |
311
|
|
|
{ |
312
|
1 |
|
array_reduce($args, [$this, 'enforcePredicate'], [$this, 'isNumber']); |
313
|
|
|
|
314
|
1 |
|
return max(...$args); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* @param int[]|float[] ...$args |
319
|
|
|
* @return int|float |
320
|
|
|
* @throws \InvalidArgumentException |
321
|
|
|
*/ |
322
|
1 |
|
public function min(...$args) |
323
|
|
|
{ |
324
|
1 |
|
array_reduce($args, [$this, 'enforcePredicate'], [$this, 'isNumber']); |
325
|
|
|
|
326
|
1 |
|
return min(...$args); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* @param int|float $arg |
331
|
|
|
* @return int|float |
332
|
|
|
*/ |
333
|
1 |
|
public function abs($arg) |
334
|
|
|
{ |
335
|
1 |
|
$this->enforcePredicate([$this, 'isNumber'], $arg); |
336
|
|
|
|
337
|
1 |
|
return abs($arg); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Used as the callback to array_reduce, with the "carry" argument being the predicate to enforce |
342
|
|
|
* |
343
|
|
|
* @param callable $pred |
344
|
|
|
* @param $thingToCheck |
345
|
|
|
* @return callable |
346
|
|
|
* @throws \InvalidArgumentException |
347
|
|
|
* @SuppressWarnings(PHPMD.UnusedPrivateMethod) |
348
|
|
|
*/ |
349
|
19 |
|
private function enforcePredicate(callable $pred, $thingToCheck): callable |
350
|
|
|
{ |
351
|
19 |
|
if (!$pred($thingToCheck)) { |
352
|
16 |
|
throw new \InvalidArgumentException(static::INVALID_ARG_REQUIRES_NUMERIC); |
353
|
|
|
} |
354
|
|
|
|
355
|
19 |
|
return $pred; |
356
|
|
|
} |
357
|
|
|
} |
358
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.