Completed
Pull Request — master (#25)
by
unknown
03:41
created

BC::isNegative()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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
    const DEFAULT_SCALE = 100;
15
16
    /**
17
     * @param null|int $scale
18
     */
19 323
    public static function setScale($scale)
20
    {
21 323
        bcscale($scale);
22 323
    }
23
24
    /**
25
     * @param int|string $number
26
     * @param int $precision
27
     * @return string
28
     */
29 47
    public static function round($number, $precision = 0)
30
    {
31 47
        $number = self::convertScientificNotationToString($number);
32 47
        if (self::checkIsFloat($number)) {
33 36
            if (self::isNegative($number)) {
34 4
                return self::sub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
35
            }
36
37 32
            return self::add($number, '0.' . str_repeat('0', $precision) . '5', $precision);
38
        }
39
40 11
        return self::checkNumber($number);
41
    }
42
43
    /**
44
     * @param int|string|float $number
45
     * @return string
46
     */
47 323
    public static function convertScientificNotationToString($number)
48
    {
49
        // check if number is in scientific notation, first use stripos as is faster then preg_match
50 323
        if (false !== stripos($number, 'E') && preg_match('/(-?(\d+\.)?\d+)E([+-]?)(\d+)/i', $number, $regs)) {
51
            // calculate final scale of number
52 59
            $scale = $regs[4] + self::getDecimalsLengthFromNumber($regs[1]);
53 59
            $pow = self::pow(10, $regs[4], $scale);
54 59
            if ('-' === $regs[3]) {
55 25
                $number = self::div($regs[1], $pow, $scale);
56
            } else {
57 34
                $number = self::mul($pow, $regs[1], $scale);
58
            }
59
            // remove unnecessary 0 from 0.000 is a 0
60 59
            $number = rtrim($number, '0');
61
            // if you remove 0 you must clean dot
62 59
            $number = rtrim($number, '.');
63
        }
64
65 323
        return self::checkNumber($number);
66
    }
67
68
    /**
69
     * @param int|string|float $number
70
     * @return int
71
     */
72 91
    public static function getDecimalsLengthFromNumber($number)
73
    {
74 91
        $check = explode('.', $number);
75 91
        if (!empty($check[1])) {
76 62
            return strlen($check[1]);
77
        }
78
79 29
        return 0;
80
    }
81
82
    /**
83
     * @param string $leftOperand
84
     * @param string $rightOperand
85
     * @param null|int $scale
86
     * @return string
87
     */
88 111
    public static function pow($leftOperand, $rightOperand, $scale = null)
89
    {
90 111
        $leftOperand = self::convertScientificNotationToString($leftOperand);
91 111
        $rightOperand = self::convertScientificNotationToString($rightOperand);
92
93 111
        if (self::checkIsFloat($rightOperand)) {
94 7
            if (null === $scale) {
95 2
                return self::powFractional($leftOperand, $rightOperand);
96
            }
97
98 5
            return self::powFractional($leftOperand, $rightOperand, $scale);
99
        }
100
101 111
        if (null === $scale) {
102 39
            return bcpow($leftOperand, $rightOperand);
103
        }
104
105 75
        return bcpow($leftOperand, $rightOperand, $scale);
106
    }
107
108
    /**
109
     * @param int|string $number
110
     * @return bool
111
     */
112 221
    private static function checkIsFloat($number)
113
    {
114 221
        return false !== strpos($number, '.');
115
    }
116
117
    /**
118
     * @param string $leftOperand
119
     * @param string $rightOperand
120
     * @param null|int $scale
121
     * @return string
122
     */
123 7
    private static function powFractional($leftOperand, $rightOperand, $scale = null)
124
    {
125
        // we need to increased scale to get correct results and avoid rounding error
126 7
        $increasedScale = null === $scale ? self::getScale() : $scale;
127 7
        $increasedScale *= 2;
128 7
        $decimals = explode('.', $rightOperand);
129
130 7
        return self::checkNumber(
131 7
            self::mul(
132 7
                self::exp(
133 7
                    self::mul(
134 7
                        self::log($leftOperand),
135 7
                        '0.' . $decimals[1],
136 7
                        $increasedScale
137
                    )
138
                ),
139 7
                self::pow($leftOperand, $decimals[0], $increasedScale),
140 7
                $scale
141
            )
142
        );
143
    }
144
145
    /**
146
     * @param int|string $number
147
     * @return int|string
148
     */
149 323
    private static function checkNumber($number)
150
    {
151 323
        $number = str_replace('+', '', filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
152 323
        if ('-0' === $number || !is_numeric($number)) {
153 28
            return '0';
154
        }
155
156 320
        return $number;
157
    }
158
159
    /**
160
     * @param string $leftOperand
161
     * @param string $rightOperand
162
     * @param null|int $scale
163
     * @return string
164
     */
165 131 View Code Duplication
    public static function mul($leftOperand, $rightOperand, $scale = null)
166
    {
167 131
        $leftOperand = self::convertScientificNotationToString($leftOperand);
168 131
        $rightOperand = self::convertScientificNotationToString($rightOperand);
169
170 131
        if (null === $scale) {
171 25
            return bcmul($leftOperand, $rightOperand);
172
        }
173
174 108
        return bcmul($leftOperand, $rightOperand, $scale);
175
    }
176
177
    /**
178
     * @param string $arg
179
     * @return string
180
     */
181 15
    public static function exp($arg)
182
    {
183 15
        $scale = self::DEFAULT_SCALE;
184 15
        $result = '1';
185 15
        for ($i = 299; $i > 0; $i--) {
186 15
            $result = self::add(self::mul(self::div($result, $i, $scale), $arg, $scale), 1, $scale);
187
        }
188
189 15
        return $result;
190
    }
191
192
    /**
193
     * @param string $leftOperand
194
     * @param string $rightOperand
195
     * @param null|int $scale
196
     * @return string
197
     */
198 125 View Code Duplication
    public static function add($leftOperand, $rightOperand, $scale = null)
199
    {
200 125
        $leftOperand = self::convertScientificNotationToString($leftOperand);
201 125
        $rightOperand = self::convertScientificNotationToString($rightOperand);
202
203 125
        if (null === $scale) {
204 6
            return bcadd($leftOperand, $rightOperand);
205
        }
206
207 122
        return bcadd($leftOperand, $rightOperand, $scale);
208
    }
209
210
    /**
211
     * @param string $leftOperand
212
     * @param string $rightOperand
213
     * @param null|int $scale
214
     * @return string
215
     */
216 97 View Code Duplication
    public static function div($leftOperand, $rightOperand, $scale = null)
217
    {
218 97
        $leftOperand = self::convertScientificNotationToString($leftOperand);
219 97
        $rightOperand = self::convertScientificNotationToString($rightOperand);
220
221 97
        if (null === $scale) {
222 12
            return bcdiv($leftOperand, $rightOperand);
223
        }
224
225 93
        return bcdiv($leftOperand, $rightOperand, $scale);
226
    }
227
228
    /**
229
     * @param string $arg
230
     * @return string
231
     */
232 12
    public static function log($arg)
233
    {
234 12
        $arg = self::convertScientificNotationToString($arg);
235 12
        if ($arg === '0') {
236 1
            return '-INF';
237
        }
238 11
        if (self::COMPARE_RIGHT_GRATER === self::comp($arg, '0')) {
239 1
            return 'NAN';
240
        }
241 10
        $scale = self::DEFAULT_SCALE;
242 10
        $m = (string)log($arg);
243 10
        $x = self::sub(self::div($arg, self::exp($m), $scale), '1', $scale);
244 10
        $res = '0';
245 10
        $pow = '1';
246 10
        $i = 1;
247
        do {
248 10
            $pow = self::mul($pow, $x, $scale);
249 10
            $sum = self::div($pow, $i, $scale);
250 10
            if ($i % 2 === 1) {
251 10
                $res = self::add($res, $sum, $scale);
252
            } else {
253 9
                $res = self::sub($res, $sum, $scale);
254
            }
255 10
            $i++;
256 10
        } while (self::comp($sum, '0', $scale));
257
258 10
        return self::add($res, $m, $scale);
259
    }
260
261
    /**
262
     * @param string $leftOperand
263
     * @param string $rightOperand
264
     * @param null|int $scale
265
     * @return int
266
     */
267 31
    public static function comp($leftOperand, $rightOperand, $scale = null)
268
    {
269 31
        $leftOperand = self::convertScientificNotationToString($leftOperand);
270 31
        $rightOperand = self::convertScientificNotationToString($rightOperand);
271
272 31
        if (null === $scale) {
273 20
            return bccomp($leftOperand, $rightOperand, max(strlen($leftOperand), strlen($rightOperand)));
274
        }
275
276 21
        return bccomp(
277 21
            $leftOperand,
278 21
            $rightOperand,
279 21
            $scale
280
        );
281
    }
282
283
    /**
284
     * @param string $leftOperand
285
     * @param string $rightOperand
286
     * @param null|int $scale
287
     * @return string
288
     */
289 45 View Code Duplication
    public static function sub($leftOperand, $rightOperand, $scale = null)
290
    {
291 45
        $leftOperand = self::convertScientificNotationToString($leftOperand);
292 45
        $rightOperand = self::convertScientificNotationToString($rightOperand);
293
294 45
        if (null === $scale) {
295 14
            return bcsub($leftOperand, $rightOperand);
296
        }
297
298 33
        return bcsub($leftOperand, $rightOperand, $scale);
299
    }
300
301
    /**
302
     * @param $number
303
     * @return bool
304
     */
305 118
    private static function isNegative($number)
306
    {
307 118
        return 0 === strncmp('-', $number, 1);
308
    }
309
310
    /**
311
     * @param int|string $number
312
     * @return string
313
     */
314 15
    public static function abs($number)
315
    {
316 15
        $number = self::convertScientificNotationToString($number);
317
318 15
        if (self::isNegative($number)) {
319 8
            $number = (string)substr($number, 1);
320
        }
321
322 15
        return self::checkNumber($number);
323
    }
324
325
    /**
326
     * @param int|string $min
327
     * @param int|string $max
328
     * @return string
329
     */
330 2
    public static function rand($min, $max)
331
    {
332 2
        $max = self::convertScientificNotationToString($max);
333 2
        $min = self::convertScientificNotationToString($min);
334
335 2
        $difference = self::add(self::sub($max, $min), 1);
336 2
        $randPercent = self::div(mt_rand(), mt_getrandmax(), 8);
337
338 2
        return self::add($min, self::mul($difference, $randPercent, 8), 0);
339
    }
340
341
    /**
342
     * @param array|int|string,...
343
     * @return null|string
344
     */
345 1 View Code Duplication
    public static function max()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
346
    {
347 1
        $max = null;
348 1
        $args = func_get_args();
349 1
        if (is_array($args[0])) {
350 1
            $args = $args[0];
351
        }
352 1
        foreach ($args as $number) {
353 1
            $number = self::convertScientificNotationToString($number);
354 1
            if (null === $max) {
355 1
                $max = $number;
356 1
            } else if (self::comp($max, $number) === self::COMPARE_RIGHT_GRATER) {
357 1
                $max = $number;
358
            }
359
        }
360
361 1
        return $max;
362
    }
363
364
    /**
365
     * @param array|int|string,...
366
     * @return null|string
367
     */
368 1 View Code Duplication
    public static function min()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
369
    {
370 1
        $min = null;
371 1
        $args = func_get_args();
372 1
        if (is_array($args[0])) {
373 1
            $args = $args[0];
374
        }
375 1
        foreach ($args as $number) {
376 1
            $number = self::convertScientificNotationToString($number);
377 1
            if (null === $min) {
378 1
                $min = $number;
379 1
            } else if (self::comp($min, $number) === self::COMPARE_LEFT_GRATER) {
380 1
                $min = $number;
381
            }
382
        }
383
384 1
        return $min;
385
    }
386
387
    /**
388
     * @param int|string $number
389
     * @param int $precision
390
     * @return string
391
     */
392 17 View Code Duplication
    public static function roundDown($number, $precision = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
393
    {
394 17
        $number = self::convertScientificNotationToString($number);
395 17
        $multiply = self::pow(10, (string)abs($precision));
396
397 17
        return $precision < 0 ?
398 4
            self::mul(
399 4
                self::floor(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
400 4
                $precision
401
            ) :
402 13
            self::div(
403 13
                self::floor(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
404 17
                $precision
405
            );
406
    }
407
408
    /**
409
     * @param int|string $number
410
     * @return string
411
     */
412 65 View Code Duplication
    public static function floor($number)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
413
    {
414 65
        $number = self::convertScientificNotationToString($number);
415 65
        if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
416 38
            $result = 0;
417 38
            if (self::isNegative($number)) {
418 7
                --$result;
419
            }
420 38
            $number = self::add($number, $result, 0);
421
        }
422
423 65
        return self::checkNumber($number);
424
    }
425
426
    /**
427
     * @param int|string $number
428
     * @return bool
429
     */
430 68
    private static function checkIsFloatCleanZeros(&$number)
431
    {
432 68
        return false !== strpos($number = rtrim(rtrim($number, '0'), '.'), '.');
433
    }
434
435
    /**
436
     * @param int|string $number
437
     * @param int $precision
438
     * @return string
439
     */
440 17 View Code Duplication
    public static function roundUp($number, $precision = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
441
    {
442 17
        $number = self::convertScientificNotationToString($number);
443 17
        $multiply = self::pow(10, (string)abs($precision));
444
445 17
        return $precision < 0 ?
446 4
            self::mul(
447 4
                self::ceil(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
448 4
                $precision
449
            ) :
450 13
            self::div(
451 13
                self::ceil(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
452 17
                $precision
453
            );
454
    }
455
456
    /**
457
     * @param int|string $number
458
     * @return string
459
     */
460 41 View Code Duplication
    public static function ceil($number)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
461
    {
462 41
        $number = self::convertScientificNotationToString($number);
463 41
        if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
464 22
            $result = 1;
465 22
            if (self::isNegative($number)) {
466 5
                --$result;
467
            }
468 22
            $number = self::add($number, $result, 0);
469
        }
470
471 41
        return self::checkNumber($number);
472
    }
473
474
    /**
475
     * @return int
476
     */
477 5
    public static function getScale()
478
    {
479 5
        $sqrt = self::sqrt('2');
480
481 5
        return strlen(substr($sqrt, strpos($sqrt, '.') + 1));
482
    }
483
484
    /**
485
     * @param string $operand
486
     * @param null|int $scale
487
     * @return string
488
     */
489 10
    public static function sqrt($operand, $scale = null)
490
    {
491 10
        $operand = self::convertScientificNotationToString($operand);
492
493 10
        if (null === $scale) {
494 6
            return bcsqrt($operand);
495
        }
496
497 5
        return bcsqrt($operand, $scale);
498
    }
499
500
    /**
501
     * @param string $leftOperand
502
     * @param string $modulus
503
     * @param null $scale
504
     * @return string
505
     */
506 23
    public static function mod($leftOperand, $modulus, $scale = null)
507
    {
508 23
        $leftOperand = self::convertScientificNotationToString($leftOperand);
509
510
        // bcmod in 7.2 is not working properly - for example bcmod(9.9999E-10, -0.00056, 9) should return '-0.000559999' but returns 0.0000000
511
512
        // bcmod in php 5.6< don't support scale and floats
513
        // let use this $x - floor($x/$y) * $y;
514 23
        if (null === $scale) {
515 11
            return self::sub($leftOperand, self::mul(self::floor(self::div($leftOperand, $modulus)), $modulus));
516
        }
517
518 12
        return self::sub(
519 12
            $leftOperand, self::mul(self::floor(self::div($leftOperand, $modulus, $scale)), $modulus, $scale), $scale
520
        );
521
    }
522
523
    /**
524
     * @param string $leftOperand
525
     * @param string $rightOperand
526
     * @param string $modulus
527
     * @param null|int $scale
528
     * @return string
529
     */
530 10
    public static function powMod($leftOperand, $rightOperand, $modulus, $scale = null)
531
    {
532 10
        $leftOperand = self::convertScientificNotationToString($leftOperand);
533 10
        $rightOperand = self::convertScientificNotationToString($rightOperand);
534
535
        // bcpowmod in 5.6 have don't calculate correct results if scale is empty
536 10
        if (null === $scale) {
537 4
            return self::mod(self::pow($leftOperand, $rightOperand), $modulus);
538
        }
539
540
        // cant use bcpowmod here as it don't support floats
541 6
        if (self::checkIsFloat($leftOperand) || self::checkIsFloat($rightOperand) || self::checkIsFloat($modulus)) {
542 2
            return self::mod(self::pow($leftOperand, $rightOperand, $scale), $modulus, $scale);
543
        }
544
545 4
        return bcpowmod($leftOperand, $rightOperand, $modulus, $scale);
546
    }
547
548
    /**
549
     * @param string $arg
550
     * @return string
551
     * @throws \InvalidArgumentException
552
     */
553 8
    public static function fact($arg)
554
    {
555 8
        $arg = self::convertScientificNotationToString($arg);
556
557 8
        if (self::checkIsFloat($arg)) {
558 1
            throw new \InvalidArgumentException('Number has to be an integer');
559
        }
560 7
        if (self::isNegative($arg)) {
561 1
            throw new \InvalidArgumentException('Number has to be greater than or equal to 0');
562
        }
563
564 6
        $return = '1';
565 6
        for ($i = 2; $i <= $arg; ++$i) {
566 5
            $return = self::mul($return, $i);
567
        }
568
569 6
        return $return;
570
    }
571
572
    /**
573
     * @param string $hex
574
     * @return string
575
     */
576 5
    public static function hexdec($hex) {
577 5
        $remainingDigits = substr($hex, 0, -1);
578 5
        $lastDigitToDecimal = \hexdec(substr($hex, -1));
579
580 5
        if (strlen($remainingDigits) === 0) {
581 5
            return $lastDigitToDecimal;
582
        }
583
584 5
        return self::add(self::mul(16, self::hexdec($remainingDigits)), $lastDigitToDecimal, 0);
585
    }
586
587
    /**
588
     * @param int $decimal
589
     * @return string
590
     */
591 6
    public static function dechex($decimal) {
592 6
        $quotient = self::div($decimal, 16, 0);
593 6
        $remainderToHex = \dechex(self::mod($decimal, 16));
594
595 6
        if (self::comp($quotient, 0) === 0) {
596 6
            return $remainderToHex;
597
        }
598
599 6
        return self::dechex($quotient) . $remainderToHex;
600
    }
601
}
602