1
|
|
|
<?php
|
2
|
|
|
|
3
|
|
|
namespace BCMathExtended;
|
4
|
|
|
|
5
|
|
|
/**
|
6
|
|
|
* Class BC
|
7
|
|
|
* @package BCMathExtended
|
8
|
|
|
*/
|
9
|
|
|
class BC
|
10
|
|
|
{
|
11
|
|
|
const COMPARE_EQUAL = 0;
|
12
|
|
|
const COMPARE_LEFT_GRATER = 1;
|
13
|
|
|
const COMPARE_RIGHT_GRATER = -1;
|
14
|
|
|
|
15
|
|
|
/**
|
16
|
|
|
* @param int $scale
|
17
|
|
|
*/
|
18
|
13 |
|
public static function setScale($scale)
|
19
|
|
|
{
|
20
|
13 |
|
bcscale($scale);
|
21
|
13 |
|
}
|
22
|
|
|
|
23
|
|
|
/**
|
24
|
|
|
* @param int|string $number
|
25
|
|
|
* @param int $precision
|
26
|
|
|
* @return string
|
27
|
|
|
*/
|
28
|
47 |
|
public static function round($number, $precision = 0)
|
29
|
|
|
{
|
30
|
47 |
|
$number = self::convertScientificNotationToString($number);
|
31
|
47 |
|
if (self::checkIsFloat($number)) {
|
32
|
36 |
|
if (self::isNegative($number)) {
|
33
|
4 |
|
return self::sub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
|
34
|
|
|
}
|
35
|
|
|
|
36
|
32 |
|
return self::add($number, '0.' . str_repeat('0', $precision) . '5', $precision);
|
37
|
|
|
}
|
38
|
|
|
|
39
|
11 |
|
return self::checkNumber($number);
|
40
|
|
|
}
|
41
|
|
|
|
42
|
|
|
/**
|
43
|
|
|
* @param int|string|float $number
|
44
|
|
|
* @return string
|
45
|
|
|
*/
|
46
|
190 |
|
public static function convertScientificNotationToString($number)
|
47
|
|
|
{
|
48
|
|
|
// check if number is in scientific notation, first use stripos as is faster then preg_match
|
49
|
190 |
|
if (false !== stripos($number, 'E') && preg_match('/(-?\d+\.\d+)E([+|-])(\d+)/i', $number, $regs)) {
|
50
|
|
|
// calculate final scale of number
|
51
|
38 |
|
$scale = $regs[3] + self::getDecimalsLengthFromNumber($regs[1]);
|
52
|
38 |
|
$pow = self::pow(10, $regs[3], $regs[3]);
|
53
|
38 |
|
if ('+' === $regs[2]) {
|
54
|
17 |
|
$number = self::mul($pow, $regs[1], $scale);
|
55
|
21 |
|
} else if ('-' === $regs[2]) {
|
56
|
21 |
|
$number = self::div($regs[1], $pow, $scale);
|
57
|
|
|
}
|
58
|
|
|
// remove unnecessary 0 from 0.000 is a 0
|
59
|
38 |
|
$number = rtrim($number, '0');
|
60
|
|
|
// if you remove 0 you must clean dot
|
61
|
38 |
|
$number = rtrim($number, '.');
|
62
|
|
|
}
|
63
|
|
|
|
64
|
190 |
|
return self::checkNumber($number);
|
65
|
|
|
}
|
66
|
|
|
|
67
|
|
|
/**
|
68
|
|
|
* @param int|string|float $number
|
69
|
|
|
* @return int
|
70
|
|
|
*/
|
71
|
38 |
|
private static function getDecimalsLengthFromNumber($number)
|
72
|
|
|
{
|
73
|
38 |
|
$check = explode('.', $number);
|
74
|
38 |
|
if (!empty($check[1])) {
|
75
|
21 |
|
return strlen($check[1]);
|
76
|
|
|
}
|
77
|
|
|
|
78
|
19 |
|
return 0;
|
79
|
|
|
}
|
80
|
|
|
|
81
|
|
|
|
82
|
|
|
/**
|
83
|
|
|
* @param string $leftOperand
|
84
|
|
|
* @param string $rightOperand
|
85
|
|
|
* @param int $scale
|
86
|
|
|
* @return string
|
87
|
|
|
*/
|
88
|
39 |
View Code Duplication |
public static function mul($leftOperand, $rightOperand, $scale = null)
|
89
|
|
|
{
|
90
|
39 |
|
$leftOperand = self::convertScientificNotationToString($leftOperand);
|
91
|
39 |
|
$rightOperand = self::convertScientificNotationToString($rightOperand);
|
92
|
|
|
|
93
|
39 |
|
if (null === $scale) {
|
94
|
5 |
|
return bcmul($leftOperand, $rightOperand);
|
95
|
|
|
}
|
96
|
|
|
|
97
|
36 |
|
return bcmul($leftOperand, $rightOperand, $scale);
|
98
|
|
|
}
|
99
|
|
|
|
100
|
|
|
/**
|
101
|
|
|
* @param string $leftOperand
|
102
|
|
|
* @param string $rightOperand
|
103
|
|
|
* @param int $scale
|
104
|
|
|
* @return string
|
105
|
|
|
*/
|
106
|
43 |
View Code Duplication |
public static function pow($leftOperand, $rightOperand, $scale = null)
|
107
|
|
|
{
|
108
|
43 |
|
$leftOperand = self::convertScientificNotationToString($leftOperand);
|
109
|
43 |
|
$rightOperand = self::convertScientificNotationToString($rightOperand);
|
110
|
|
|
|
111
|
43 |
|
if (null === $scale) {
|
112
|
4 |
|
return bcpow($leftOperand, $rightOperand);
|
113
|
|
|
}
|
114
|
|
|
|
115
|
42 |
|
return bcpow($leftOperand, $rightOperand, $scale);
|
116
|
|
|
}
|
117
|
|
|
|
118
|
|
|
/**
|
119
|
|
|
* @param string $leftOperand
|
120
|
|
|
* @param string $rightOperand
|
121
|
|
|
* @param int $scale
|
122
|
|
|
* @return string
|
123
|
|
|
*/
|
124
|
26 |
View Code Duplication |
public static function div($leftOperand, $rightOperand, $scale = null)
|
125
|
|
|
{
|
126
|
26 |
|
$leftOperand = self::convertScientificNotationToString($leftOperand);
|
127
|
26 |
|
$rightOperand = self::convertScientificNotationToString($rightOperand);
|
128
|
|
|
|
129
|
26 |
|
if (null === $scale) {
|
130
|
3 |
|
return bcdiv($leftOperand, $rightOperand);
|
131
|
|
|
}
|
132
|
|
|
|
133
|
26 |
|
return bcdiv($leftOperand, $rightOperand, $scale);
|
134
|
|
|
}
|
135
|
|
|
|
136
|
|
|
/**
|
137
|
|
|
* @param int|string $number
|
138
|
|
|
* @return int|string
|
139
|
|
|
*/
|
140
|
190 |
|
private static function checkNumber($number)
|
141
|
|
|
{
|
142
|
190 |
|
$number = str_replace('+', '', filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
|
143
|
190 |
|
if ('-0' === $number || !is_numeric($number)) {
|
144
|
20 |
|
return '0';
|
145
|
|
|
}
|
146
|
|
|
|
147
|
188 |
|
return $number;
|
148
|
|
|
}
|
149
|
|
|
|
150
|
|
|
/**
|
151
|
|
|
* @param int|string $number
|
152
|
|
|
* @return bool
|
153
|
|
|
*/
|
154
|
101 |
|
private static function checkIsFloat($number)
|
155
|
|
|
{
|
156
|
101 |
|
return false !== strpos($number, '.');
|
157
|
|
|
}
|
158
|
|
|
|
159
|
|
|
/**
|
160
|
|
|
* @param $number
|
161
|
|
|
* @return bool
|
162
|
|
|
*/
|
163
|
77 |
|
private static function isNegative($number)
|
164
|
|
|
{
|
165
|
77 |
|
return 0 === strncmp('-', $number, 1);
|
166
|
|
|
}
|
167
|
|
|
|
168
|
|
|
/**
|
169
|
|
|
* @param string $leftOperand
|
170
|
|
|
* @param string $rightOperand
|
171
|
|
|
* @param int $scale
|
172
|
|
|
* @return string
|
173
|
|
|
*/
|
174
|
11 |
View Code Duplication |
public static function sub($leftOperand, $rightOperand, $scale = null)
|
175
|
|
|
{
|
176
|
11 |
|
$leftOperand = self::convertScientificNotationToString($leftOperand);
|
177
|
11 |
|
$rightOperand = self::convertScientificNotationToString($rightOperand);
|
178
|
|
|
|
179
|
11 |
|
if (null === $scale) {
|
180
|
5 |
|
return bcsub($leftOperand, $rightOperand);
|
181
|
|
|
}
|
182
|
|
|
|
183
|
9 |
|
return bcsub($leftOperand, $rightOperand, $scale);
|
184
|
|
|
}
|
185
|
|
|
|
186
|
|
|
/**
|
187
|
|
|
* @param string $leftOperand
|
188
|
|
|
* @param string $rightOperand
|
189
|
|
|
* @param int $scale
|
190
|
|
|
* @return string
|
191
|
|
|
*/
|
192
|
65 |
View Code Duplication |
public static function add($leftOperand, $rightOperand, $scale = null)
|
193
|
|
|
{
|
194
|
65 |
|
$leftOperand = self::convertScientificNotationToString($leftOperand);
|
195
|
65 |
|
$rightOperand = self::convertScientificNotationToString($rightOperand);
|
196
|
|
|
|
197
|
65 |
|
if (null === $scale) {
|
198
|
6 |
|
return bcadd($leftOperand, $rightOperand);
|
199
|
|
|
}
|
200
|
|
|
|
201
|
62 |
|
return bcadd($leftOperand, $rightOperand, $scale);
|
202
|
|
|
}
|
203
|
|
|
|
204
|
|
|
/**
|
205
|
|
|
* @param int|string $number
|
206
|
|
|
* @return string
|
207
|
|
|
*/
|
208
|
15 |
|
public static function abs($number)
|
209
|
|
|
{
|
210
|
15 |
|
$number = self::convertScientificNotationToString($number);
|
211
|
|
|
|
212
|
15 |
|
if (self::isNegative($number)) {
|
213
|
8 |
|
$number = (string)substr($number, 1);
|
214
|
|
|
}
|
215
|
|
|
|
216
|
15 |
|
return self::checkNumber($number);
|
217
|
|
|
}
|
218
|
|
|
|
219
|
|
|
/**
|
220
|
|
|
* @param int|string $min
|
221
|
|
|
* @param int|string $max
|
222
|
|
|
* @return string
|
223
|
|
|
*/
|
224
|
2 |
|
public static function rand($min, $max)
|
225
|
|
|
{
|
226
|
2 |
|
$max = self::convertScientificNotationToString($max);
|
227
|
2 |
|
$min = self::convertScientificNotationToString($min);
|
228
|
|
|
|
229
|
2 |
|
$difference = self::add(self::sub($max, $min), 1);
|
230
|
2 |
|
$randPercent = self::div(mt_rand(), mt_getrandmax(), 8);
|
231
|
|
|
|
232
|
2 |
|
return self::add($min, self::mul($difference, $randPercent, 8), 0);
|
233
|
|
|
}
|
234
|
|
|
|
235
|
|
|
/**
|
236
|
|
|
* @param array|int|string,...
|
237
|
|
|
* @return null|string
|
238
|
|
|
*/
|
239
|
1 |
View Code Duplication |
public static function max()
|
|
|
|
|
240
|
|
|
{
|
241
|
1 |
|
$max = null;
|
242
|
1 |
|
$args = func_get_args();
|
243
|
1 |
|
if (is_array($args[0])) {
|
244
|
1 |
|
$args = $args[0];
|
245
|
|
|
}
|
246
|
1 |
|
foreach ($args as $number) {
|
247
|
1 |
|
$number = self::convertScientificNotationToString($number);
|
248
|
1 |
|
if (null === $max) {
|
249
|
1 |
|
$max = $number;
|
250
|
1 |
|
} else if (self::comp($max, $number) === self::COMPARE_RIGHT_GRATER) {
|
251
|
1 |
|
$max = $number;
|
252
|
|
|
}
|
253
|
|
|
}
|
254
|
|
|
|
255
|
1 |
|
return $max;
|
256
|
|
|
}
|
257
|
|
|
|
258
|
|
|
/**
|
259
|
|
|
* @param string $leftOperand
|
260
|
|
|
* @param string $rightOperand
|
261
|
|
|
* @param int $scale
|
262
|
|
|
* @return int
|
263
|
|
|
*/
|
264
|
14 |
|
public static function comp($leftOperand, $rightOperand, $scale = null)
|
265
|
|
|
{
|
266
|
14 |
|
$leftOperand = self::convertScientificNotationToString($leftOperand);
|
267
|
14 |
|
$rightOperand = self::convertScientificNotationToString($rightOperand);
|
268
|
|
|
|
269
|
14 |
|
if (null === $scale) {
|
270
|
3 |
|
return bccomp($leftOperand, $rightOperand, max(strlen($leftOperand), strlen($rightOperand)));
|
271
|
|
|
}
|
272
|
|
|
|
273
|
11 |
|
return bccomp(
|
274
|
11 |
|
$leftOperand,
|
275
|
11 |
|
$rightOperand,
|
276
|
11 |
|
$scale
|
277
|
|
|
);
|
278
|
|
|
}
|
279
|
|
|
|
280
|
|
|
/**
|
281
|
|
|
* @param array|int|string,...
|
282
|
|
|
* @return null|string
|
283
|
|
|
*/
|
284
|
1 |
View Code Duplication |
public static function min()
|
|
|
|
|
285
|
|
|
{
|
286
|
1 |
|
$min = null;
|
287
|
1 |
|
$args = func_get_args();
|
288
|
1 |
|
if (is_array($args[0])) {
|
289
|
1 |
|
$args = $args[0];
|
290
|
|
|
}
|
291
|
1 |
|
foreach ($args as $number) {
|
292
|
1 |
|
$number = self::convertScientificNotationToString($number);
|
293
|
1 |
|
if (null === $min) {
|
294
|
1 |
|
$min = $number;
|
295
|
1 |
|
} else if (self::comp($min, $number) === self::COMPARE_LEFT_GRATER) {
|
296
|
1 |
|
$min = $number;
|
297
|
|
|
}
|
298
|
|
|
}
|
299
|
|
|
|
300
|
1 |
|
return $min;
|
301
|
|
|
}
|
302
|
|
|
|
303
|
|
|
/**
|
304
|
|
|
* @param int|string $number
|
305
|
|
|
* @param int $precision
|
306
|
|
|
* @return string
|
307
|
|
|
*/
|
308
|
1 |
View Code Duplication |
public static function roundDown($number, $precision = 0)
|
|
|
|
|
309
|
|
|
{
|
310
|
1 |
|
$number = self::convertScientificNotationToString($number);
|
311
|
1 |
|
$multiply = self::pow(10, (string)abs($precision));
|
312
|
|
|
|
313
|
1 |
|
return $precision < 0 ?
|
314
|
1 |
|
self::mul(
|
315
|
1 |
|
self::floor(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
|
316
|
1 |
|
$precision
|
317
|
|
|
) :
|
318
|
1 |
|
self::div(
|
319
|
1 |
|
self::floor(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
|
320
|
1 |
|
$precision
|
321
|
|
|
);
|
322
|
|
|
}
|
323
|
|
|
|
324
|
|
|
/**
|
325
|
|
|
* @param int|string $number
|
326
|
|
|
* @return string
|
327
|
|
|
*/
|
328
|
29 |
View Code Duplication |
public static function floor($number)
|
|
|
|
|
329
|
|
|
{
|
330
|
29 |
|
$number = self::convertScientificNotationToString($number);
|
331
|
29 |
|
if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
|
332
|
15 |
|
$result = 0;
|
333
|
15 |
|
if (self::isNegative($number)) {
|
334
|
6 |
|
--$result;
|
335
|
|
|
}
|
336
|
15 |
|
$number = self::add($number, $result, 0);
|
337
|
|
|
}
|
338
|
|
|
|
339
|
29 |
|
return self::checkNumber($number);
|
340
|
|
|
}
|
341
|
|
|
|
342
|
|
|
/**
|
343
|
|
|
* @param int|string $number
|
344
|
|
|
* @return bool
|
345
|
|
|
*/
|
346
|
28 |
|
private static function checkIsFloatCleanZeros(&$number)
|
347
|
|
|
{
|
348
|
28 |
|
return false !== strpos($number = rtrim(rtrim($number, '0'), '.'), '.');
|
349
|
|
|
}
|
350
|
|
|
|
351
|
|
|
/**
|
352
|
|
|
* @param int|string $number
|
353
|
|
|
* @param int $precision
|
354
|
|
|
* @return string
|
355
|
|
|
*/
|
356
|
1 |
View Code Duplication |
public static function roundUp($number, $precision = 0)
|
|
|
|
|
357
|
|
|
{
|
358
|
1 |
|
$number = self::convertScientificNotationToString($number);
|
359
|
1 |
|
$multiply = self::pow(10, (string)abs($precision));
|
360
|
|
|
|
361
|
1 |
|
return $precision < 0 ?
|
362
|
1 |
|
self::mul(
|
363
|
1 |
|
self::ceil(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
|
364
|
1 |
|
$precision
|
365
|
|
|
) :
|
366
|
1 |
|
self::div(
|
367
|
1 |
|
self::ceil(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
|
368
|
1 |
|
$precision
|
369
|
|
|
);
|
370
|
|
|
}
|
371
|
|
|
|
372
|
|
|
/**
|
373
|
|
|
* @param int|string $number
|
374
|
|
|
* @return string
|
375
|
|
|
*/
|
376
|
25 |
View Code Duplication |
public static function ceil($number)
|
|
|
|
|
377
|
|
|
{
|
378
|
25 |
|
$number = self::convertScientificNotationToString($number);
|
379
|
25 |
|
if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
|
380
|
11 |
|
$result = 1;
|
381
|
11 |
|
if (self::isNegative($number)) {
|
382
|
4 |
|
--$result;
|
383
|
|
|
}
|
384
|
11 |
|
$number = self::add($number, $result, 0);
|
385
|
|
|
}
|
386
|
|
|
|
387
|
25 |
|
return self::checkNumber($number);
|
388
|
|
|
}
|
389
|
|
|
|
390
|
|
|
/**
|
391
|
|
|
* @return int
|
392
|
|
|
*/
|
393
|
3 |
|
public static function getScale()
|
394
|
|
|
{
|
395
|
3 |
|
$sqrt = self::sqrt('2');
|
396
|
|
|
|
397
|
3 |
|
return strlen(substr($sqrt, strpos($sqrt, '.') + 1));
|
398
|
|
|
}
|
399
|
|
|
|
400
|
|
|
/**
|
401
|
|
|
* @param string $operand
|
402
|
|
|
* @param int $scale
|
403
|
|
|
* @return string
|
404
|
|
|
*/
|
405
|
5 |
|
public static function sqrt($operand, $scale = null)
|
406
|
|
|
{
|
407
|
5 |
|
$operand = self::convertScientificNotationToString($operand);
|
408
|
|
|
|
409
|
5 |
|
if (null === $scale) {
|
410
|
4 |
|
return bcsqrt($operand);
|
411
|
|
|
}
|
412
|
|
|
|
413
|
2 |
|
return bcsqrt($operand, $scale);
|
414
|
|
|
}
|
415
|
|
|
|
416
|
|
|
/**
|
417
|
|
|
* @param string $leftOperand
|
418
|
|
|
* @param string $modulus
|
419
|
|
|
* @param int $scale
|
420
|
|
|
* @return string
|
421
|
|
|
*/
|
422
|
3 |
|
public static function fmod($leftOperand, $modulus, $scale = null)
|
423
|
|
|
{
|
424
|
3 |
|
$leftOperand = self::convertScientificNotationToString($leftOperand);
|
425
|
|
|
|
426
|
|
|
// From PHP 7.2 on, bcmod can handle real numbers
|
427
|
3 |
|
if (version_compare(PHP_VERSION, '7.2.0') >= 0) {
|
428
|
|
|
return self::mod($leftOperand, $modulus);
|
429
|
|
|
}
|
430
|
|
|
|
431
|
|
|
// mod(a, b) = a - b * floor(a/b)
|
432
|
3 |
|
return self::sub(
|
433
|
3 |
|
$leftOperand,
|
434
|
3 |
|
self::mul(
|
435
|
3 |
|
$modulus,
|
436
|
3 |
|
self::floor(self::div($leftOperand, $modulus, $scale)),
|
437
|
3 |
|
$scale
|
438
|
|
|
),
|
439
|
3 |
|
$scale
|
440
|
|
|
);
|
441
|
|
|
}
|
442
|
|
|
|
443
|
|
|
/**
|
444
|
|
|
* @param string $leftOperand
|
445
|
|
|
* @param string $modulus
|
446
|
|
|
* @return string
|
447
|
|
|
*/
|
448
|
1 |
|
public static function mod($leftOperand, $modulus)
|
449
|
|
|
{
|
450
|
1 |
|
return bcmod(
|
451
|
1 |
|
self::convertScientificNotationToString($leftOperand),
|
452
|
1 |
|
$modulus
|
453
|
|
|
);
|
454
|
|
|
}
|
455
|
|
|
|
456
|
|
|
/**
|
457
|
|
|
* @param string $leftOperand
|
458
|
|
|
* @param string $rightOperand
|
459
|
|
|
* @param string $modulus
|
460
|
|
|
* @param int $scale
|
461
|
|
|
* @return string
|
462
|
|
|
*/
|
463
|
1 |
View Code Duplication |
public static function powMod($leftOperand, $rightOperand, $modulus, $scale = null)
|
464
|
|
|
{
|
465
|
1 |
|
$leftOperand = self::convertScientificNotationToString($leftOperand);
|
466
|
1 |
|
$rightOperand = self::convertScientificNotationToString($rightOperand);
|
467
|
|
|
|
468
|
1 |
|
if (null === $scale) {
|
469
|
|
|
return bcpowmod($leftOperand, $rightOperand, $modulus);
|
470
|
|
|
}
|
471
|
|
|
|
472
|
1 |
|
return bcpowmod($leftOperand, $rightOperand, $modulus, $scale);
|
473
|
|
|
}
|
474
|
|
|
}
|
475
|
|
|
|
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.