PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / vendor / brick / math / src / BigDecimal.php

BigDecimal.php in Defender Security – Malware Scanner, Login Security & Firewall 6.3.0, at vendor/brick/math/src/BigDecimal.php

896 lines 23.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Brick\Math;
6
7 use Brick\Math\Exception\DivisionByZeroException;
8 use Brick\Math\Exception\MathException;
9 use Brick\Math\Exception\NegativeNumberException;
10 use Brick\Math\Internal\Calculator;
11
12 /**
13 * Immutable, arbitrary-precision signed decimal numbers.
14 *
15 * @psalm-immutable
16 */
17 final class BigDecimal extends BigNumber
18 {
19 /**
20 * The unscaled value of this decimal number.
21 *
22 * This is a string of digits with an optional leading minus sign.
23 * No leading zero must be present.
24 * No leading minus sign must be present if the value is 0.
25 *
26 * @var string
27 */
28 private $value;
29
30 /**
31 * The scale (number of digits after the decimal point) of this decimal number.
32 *
33 * This must be zero or more.
34 *
35 * @var int
36 */
37 private $scale;
38
39 /**
40 * Protected constructor. Use a factory method to obtain an instance.
41 *
42 * @param string $value The unscaled value, validated.
43 * @param int $scale The scale, validated.
44 */
45 protected function __construct(string $value, int $scale = 0)
46 {
47 $this->value = $value;
48 $this->scale = $scale;
49 }
50
51 /**
52 * Creates a BigDecimal of the given value.
53 *
54 * @param BigNumber|int|float|string $value
55 *
56 * @return BigDecimal
57 *
58 * @throws MathException If the value cannot be converted to a BigDecimal.
59 *
60 * @psalm-pure
61 */
62 public static function of($value) : BigNumber
63 {
64 return parent::of($value)->toBigDecimal();
65 }
66
67 /**
68 * Creates a BigDecimal from an unscaled value and a scale.
69 *
70 * Example: `(12345, 3)` will result in the BigDecimal `12.345`.
71 *
72 * @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger.
73 * @param int $scale The scale of the number, positive or zero.
74 *
75 * @return BigDecimal
76 *
77 * @throws \InvalidArgumentException If the scale is negative.
78 *
79 * @psalm-pure
80 */
81 public static function ofUnscaledValue($value, int $scale = 0) : BigDecimal
82 {
83 if ($scale < 0) {
84 throw new \InvalidArgumentException('The scale cannot be negative.');
85 }
86
87 return new BigDecimal((string) BigInteger::of($value), $scale);
88 }
89
90 /**
91 * Returns a BigDecimal representing zero, with a scale of zero.
92 *
93 * @return BigDecimal
94 *
95 * @psalm-pure
96 */
97 public static function zero() : BigDecimal
98 {
99 /**
100 * @psalm-suppress ImpureStaticVariable
101 * @var BigDecimal|null $zero
102 */
103 static $zero;
104
105 if ($zero === null) {
106 $zero = new BigDecimal('0');
107 }
108
109 return $zero;
110 }
111
112 /**
113 * Returns a BigDecimal representing one, with a scale of zero.
114 *
115 * @return BigDecimal
116 *
117 * @psalm-pure
118 */
119 public static function one() : BigDecimal
120 {
121 /**
122 * @psalm-suppress ImpureStaticVariable
123 * @var BigDecimal|null $one
124 */
125 static $one;
126
127 if ($one === null) {
128 $one = new BigDecimal('1');
129 }
130
131 return $one;
132 }
133
134 /**
135 * Returns a BigDecimal representing ten, with a scale of zero.
136 *
137 * @return BigDecimal
138 *
139 * @psalm-pure
140 */
141 public static function ten() : BigDecimal
142 {
143 /**
144 * @psalm-suppress ImpureStaticVariable
145 * @var BigDecimal|null $ten
146 */
147 static $ten;
148
149 if ($ten === null) {
150 $ten = new BigDecimal('10');
151 }
152
153 return $ten;
154 }
155
156 /**
157 * Returns the sum of this number and the given one.
158 *
159 * The result has a scale of `max($this->scale, $that->scale)`.
160 *
161 * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
162 *
163 * @return BigDecimal The result.
164 *
165 * @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
166 */
167 public function plus($that) : BigDecimal
168 {
169 $that = BigDecimal::of($that);
170
171 if ($that->value === '0' && $that->scale <= $this->scale) {
172 return $this;
173 }
174
175 if ($this->value === '0' && $this->scale <= $that->scale) {
176 return $that;
177 }
178
179 [$a, $b] = $this->scaleValues($this, $that);
180
181 $value = Calculator::get()->add($a, $b);
182 $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
183
184 return new BigDecimal($value, $scale);
185 }
186
187 /**
188 * Returns the difference of this number and the given one.
189 *
190 * The result has a scale of `max($this->scale, $that->scale)`.
191 *
192 * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
193 *
194 * @return BigDecimal The result.
195 *
196 * @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
197 */
198 public function minus($that) : BigDecimal
199 {
200 $that = BigDecimal::of($that);
201
202 if ($that->value === '0' && $that->scale <= $this->scale) {
203 return $this;
204 }
205
206 [$a, $b] = $this->scaleValues($this, $that);
207
208 $value = Calculator::get()->sub($a, $b);
209 $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
210
211 return new BigDecimal($value, $scale);
212 }
213
214 /**
215 * Returns the product of this number and the given one.
216 *
217 * The result has a scale of `$this->scale + $that->scale`.
218 *
219 * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
220 *
221 * @return BigDecimal The result.
222 *
223 * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
224 */
225 public function multipliedBy($that) : BigDecimal
226 {
227 $that = BigDecimal::of($that);
228
229 if ($that->value === '1' && $that->scale === 0) {
230 return $this;
231 }
232
233 if ($this->value === '1' && $this->scale === 0) {
234 return $that;
235 }
236
237 $value = Calculator::get()->mul($this->value, $that->value);
238 $scale = $this->scale + $that->scale;
239
240 return new BigDecimal($value, $scale);
241 }
242
243 /**
244 * Returns the result of the division of this number by the given one, at the given scale.
245 *
246 * @param BigNumber|int|float|string $that The divisor.
247 * @param int|null $scale The desired scale, or null to use the scale of this number.
248 * @param int $roundingMode An optional rounding mode.
249 *
250 * @return BigDecimal
251 *
252 * @throws \InvalidArgumentException If the scale or rounding mode is invalid.
253 * @throws MathException If the number is invalid, is zero, or rounding was necessary.
254 */
255 public function dividedBy($that, ?int $scale = null, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
256 {
257 $that = BigDecimal::of($that);
258
259 if ($that->isZero()) {
260 throw DivisionByZeroException::divisionByZero();
261 }
262
263 if ($scale === null) {
264 $scale = $this->scale;
265 } elseif ($scale < 0) {
266 throw new \InvalidArgumentException('Scale cannot be negative.');
267 }
268
269 if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
270 return $this;
271 }
272
273 $p = $this->valueWithMinScale($that->scale + $scale);
274 $q = $that->valueWithMinScale($this->scale - $scale);
275
276 $result = Calculator::get()->divRound($p, $q, $roundingMode);
277
278 return new BigDecimal($result, $scale);
279 }
280
281 /**
282 * Returns the exact result of the division of this number by the given one.
283 *
284 * The scale of the result is automatically calculated to fit all the fraction digits.
285 *
286 * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
287 *
288 * @return BigDecimal The result.
289 *
290 * @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
291 * or the result yields an infinite number of digits.
292 */
293 public function exactlyDividedBy($that) : BigDecimal
294 {
295 $that = BigDecimal::of($that);
296
297 if ($that->value === '0') {
298 throw DivisionByZeroException::divisionByZero();
299 }
300
301 [, $b] = $this->scaleValues($this, $that);
302
303 $d = \rtrim($b, '0');
304 $scale = \strlen($b) - \strlen($d);
305
306 $calculator = Calculator::get();
307
308 foreach ([5, 2] as $prime) {
309 for (;;) {
310 $lastDigit = (int) $d[-1];
311
312 if ($lastDigit % $prime !== 0) {
313 break;
314 }
315
316 $d = $calculator->divQ($d, (string) $prime);
317 $scale++;
318 }
319 }
320
321 return $this->dividedBy($that, $scale)->stripTrailingZeros();
322 }
323
324 /**
325 * Returns this number exponentiated to the given value.
326 *
327 * The result has a scale of `$this->scale * $exponent`.
328 *
329 * @param int $exponent The exponent.
330 *
331 * @return BigDecimal The result.
332 *
333 * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
334 */
335 public function power(int $exponent) : BigDecimal
336 {
337 if ($exponent === 0) {
338 return BigDecimal::one();
339 }
340
341 if ($exponent === 1) {
342 return $this;
343 }
344
345 if ($exponent < 0 || $exponent > Calculator::MAX_POWER) {
346 throw new \InvalidArgumentException(\sprintf(
347 'The exponent %d is not in the range 0 to %d.',
348 $exponent,
349 Calculator::MAX_POWER
350 ));
351 }
352
353 return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent);
354 }
355
356 /**
357 * Returns the quotient of the division of this number by this given one.
358 *
359 * The quotient has a scale of `0`.
360 *
361 * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
362 *
363 * @return BigDecimal The quotient.
364 *
365 * @throws MathException If the divisor is not a valid decimal number, or is zero.
366 */
367 public function quotient($that) : BigDecimal
368 {
369 $that = BigDecimal::of($that);
370
371 if ($that->isZero()) {
372 throw DivisionByZeroException::divisionByZero();
373 }
374
375 $p = $this->valueWithMinScale($that->scale);
376 $q = $that->valueWithMinScale($this->scale);
377
378 $quotient = Calculator::get()->divQ($p, $q);
379
380 return new BigDecimal($quotient, 0);
381 }
382
383 /**
384 * Returns the remainder of the division of this number by this given one.
385 *
386 * The remainder has a scale of `max($this->scale, $that->scale)`.
387 *
388 * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
389 *
390 * @return BigDecimal The remainder.
391 *
392 * @throws MathException If the divisor is not a valid decimal number, or is zero.
393 */
394 public function remainder($that) : BigDecimal
395 {
396 $that = BigDecimal::of($that);
397
398 if ($that->isZero()) {
399 throw DivisionByZeroException::divisionByZero();
400 }
401
402 $p = $this->valueWithMinScale($that->scale);
403 $q = $that->valueWithMinScale($this->scale);
404
405 $remainder = Calculator::get()->divR($p, $q);
406
407 $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
408
409 return new BigDecimal($remainder, $scale);
410 }
411
412 /**
413 * Returns the quotient and remainder of the division of this number by the given one.
414 *
415 * The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
416 *
417 * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
418 *
419 * @return BigDecimal[] An array containing the quotient and the remainder.
420 *
421 * @throws MathException If the divisor is not a valid decimal number, or is zero.
422 */
423 public function quotientAndRemainder($that) : array
424 {
425 $that = BigDecimal::of($that);
426
427 if ($that->isZero()) {
428 throw DivisionByZeroException::divisionByZero();
429 }
430
431 $p = $this->valueWithMinScale($that->scale);
432 $q = $that->valueWithMinScale($this->scale);
433
434 [$quotient, $remainder] = Calculator::get()->divQR($p, $q);
435
436 $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
437
438 $quotient = new BigDecimal($quotient, 0);
439 $remainder = new BigDecimal($remainder, $scale);
440
441 return [$quotient, $remainder];
442 }
443
444 /**
445 * Returns the square root of this number, rounded down to the given number of decimals.
446 *
447 * @param int $scale
448 *
449 * @return BigDecimal
450 *
451 * @throws \InvalidArgumentException If the scale is negative.
452 * @throws NegativeNumberException If this number is negative.
453 */
454 public function sqrt(int $scale) : BigDecimal
455 {
456 if ($scale < 0) {
457 throw new \InvalidArgumentException('Scale cannot be negative.');
458 }
459
460 if ($this->value === '0') {
461 return new BigDecimal('0', $scale);
462 }
463
464 if ($this->value[0] === '-') {
465 throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
466 }
467
468 $value = $this->value;
469 $addDigits = 2 * $scale - $this->scale;
470
471 if ($addDigits > 0) {
472 // add zeros
473 $value .= \str_repeat('0', $addDigits);
474 } elseif ($addDigits < 0) {
475 // trim digits
476 if (-$addDigits >= \strlen($this->value)) {
477 // requesting a scale too low, will always yield a zero result
478 return new BigDecimal('0', $scale);
479 }
480
481 $value = \substr($value, 0, $addDigits);
482 }
483
484 $value = Calculator::get()->sqrt($value);
485
486 return new BigDecimal($value, $scale);
487 }
488
489 /**
490 * Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
491 *
492 * @param int $n
493 *
494 * @return BigDecimal
495 */
496 public function withPointMovedLeft(int $n) : BigDecimal
497 {
498 if ($n === 0) {
499 return $this;
500 }
501
502 if ($n < 0) {
503 return $this->withPointMovedRight(-$n);
504 }
505
506 return new BigDecimal($this->value, $this->scale + $n);
507 }
508
509 /**
510 * Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
511 *
512 * @param int $n
513 *
514 * @return BigDecimal
515 */
516 public function withPointMovedRight(int $n) : BigDecimal
517 {
518 if ($n === 0) {
519 return $this;
520 }
521
522 if ($n < 0) {
523 return $this->withPointMovedLeft(-$n);
524 }
525
526 $value = $this->value;
527 $scale = $this->scale - $n;
528
529 if ($scale < 0) {
530 if ($value !== '0') {
531 $value .= \str_repeat('0', -$scale);
532 }
533 $scale = 0;
534 }
535
536 return new BigDecimal($value, $scale);
537 }
538
539 /**
540 * Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
541 *
542 * @return BigDecimal
543 */
544 public function stripTrailingZeros() : BigDecimal
545 {
546 if ($this->scale === 0) {
547 return $this;
548 }
549
550 $trimmedValue = \rtrim($this->value, '0');
551
552 if ($trimmedValue === '') {
553 return BigDecimal::zero();
554 }
555
556 $trimmableZeros = \strlen($this->value) - \strlen($trimmedValue);
557
558 if ($trimmableZeros === 0) {
559 return $this;
560 }
561
562 if ($trimmableZeros > $this->scale) {
563 $trimmableZeros = $this->scale;
564 }
565
566 $value = \substr($this->value, 0, -$trimmableZeros);
567 $scale = $this->scale - $trimmableZeros;
568
569 return new BigDecimal($value, $scale);
570 }
571
572 /**
573 * Returns the absolute value of this number.
574 *
575 * @return BigDecimal
576 */
577 public function abs() : BigDecimal
578 {
579 return $this->isNegative() ? $this->negated() : $this;
580 }
581
582 /**
583 * Returns the negated value of this number.
584 *
585 * @return BigDecimal
586 */
587 public function negated() : BigDecimal
588 {
589 return new BigDecimal(Calculator::get()->neg($this->value), $this->scale);
590 }
591
592 /**
593 * {@inheritdoc}
594 */
595 public function compareTo($that) : int
596 {
597 $that = BigNumber::of($that);
598
599 if ($that instanceof BigInteger) {
600 $that = $that->toBigDecimal();
601 }
602
603 if ($that instanceof BigDecimal) {
604 [$a, $b] = $this->scaleValues($this, $that);
605
606 return Calculator::get()->cmp($a, $b);
607 }
608
609 return - $that->compareTo($this);
610 }
611
612 /**
613 * {@inheritdoc}
614 */
615 public function getSign() : int
616 {
617 return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
618 }
619
620 /**
621 * @return BigInteger
622 */
623 public function getUnscaledValue() : BigInteger
624 {
625 return BigInteger::create($this->value);
626 }
627
628 /**
629 * @return int
630 */
631 public function getScale() : int
632 {
633 return $this->scale;
634 }
635
636 /**
637 * Returns a string representing the integral part of this decimal number.
638 *
639 * Example: `-123.456` => `-123`.
640 *
641 * @return string
642 */
643 public function getIntegralPart() : string
644 {
645 if ($this->scale === 0) {
646 return $this->value;
647 }
648
649 $value = $this->getUnscaledValueWithLeadingZeros();
650
651 return \substr($value, 0, -$this->scale);
652 }
653
654 /**
655 * Returns a string representing the fractional part of this decimal number.
656 *
657 * If the scale is zero, an empty string is returned.
658 *
659 * Examples: `-123.456` => '456', `123` => ''.
660 *
661 * @return string
662 */
663 public function getFractionalPart() : string
664 {
665 if ($this->scale === 0) {
666 return '';
667 }
668
669 $value = $this->getUnscaledValueWithLeadingZeros();
670
671 return \substr($value, -$this->scale);
672 }
673
674 /**
675 * Returns whether this decimal number has a non-zero fractional part.
676 *
677 * @return bool
678 */
679 public function hasNonZeroFractionalPart() : bool
680 {
681 return $this->getFractionalPart() !== \str_repeat('0', $this->scale);
682 }
683
684 /**
685 * {@inheritdoc}
686 */
687 public function toBigInteger() : BigInteger
688 {
689 $zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
690
691 return BigInteger::create($zeroScaleDecimal->value);
692 }
693
694 /**
695 * {@inheritdoc}
696 */
697 public function toBigDecimal() : BigDecimal
698 {
699 return $this;
700 }
701
702 /**
703 * {@inheritdoc}
704 */
705 public function toBigRational() : BigRational
706 {
707 $numerator = BigInteger::create($this->value);
708 $denominator = BigInteger::create('1' . \str_repeat('0', $this->scale));
709
710 return BigRational::create($numerator, $denominator, false);
711 }
712
713 /**
714 * {@inheritdoc}
715 */
716 public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
717 {
718 if ($scale === $this->scale) {
719 return $this;
720 }
721
722 return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode);
723 }
724
725 /**
726 * {@inheritdoc}
727 */
728 public function toInt() : int
729 {
730 return $this->toBigInteger()->toInt();
731 }
732
733 /**
734 * {@inheritdoc}
735 */
736 public function toFloat() : float
737 {
738 return (float) (string) $this;
739 }
740
741 /**
742 * {@inheritdoc}
743 */
744 public function __toString() : string
745 {
746 if ($this->scale === 0) {
747 return $this->value;
748 }
749
750 $value = $this->getUnscaledValueWithLeadingZeros();
751
752 return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale);
753 }
754
755 /**
756 * This method is required for serializing the object and SHOULD NOT be accessed directly.
757 *
758 * @internal
759 *
760 * @return array{value: string, scale: int}
761 */
762 public function __serialize(): array
763 {
764 return ['value' => $this->value, 'scale' => $this->scale];
765 }
766
767 /**
768 * This method is only here to allow unserializing the object and cannot be accessed directly.
769 *
770 * @internal
771 * @psalm-suppress RedundantPropertyInitializationCheck
772 *
773 * @param array{value: string, scale: int} $data
774 *
775 * @return void
776 *
777 * @throws \LogicException
778 */
779 public function __unserialize(array $data): void
780 {
781 if (isset($this->value)) {
782 throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
783 }
784
785 $this->value = $data['value'];
786 $this->scale = $data['scale'];
787 }
788
789 /**
790 * This method is required by interface Serializable and SHOULD NOT be accessed directly.
791 *
792 * @internal
793 *
794 * @return string
795 */
796 public function serialize() : string
797 {
798 return $this->value . ':' . $this->scale;
799 }
800
801 /**
802 * This method is only here to implement interface Serializable and cannot be accessed directly.
803 *
804 * @internal
805 * @psalm-suppress RedundantPropertyInitializationCheck
806 *
807 * @param string $value
808 *
809 * @return void
810 *
811 * @throws \LogicException
812 */
813 public function unserialize($value) : void
814 {
815 if (isset($this->value)) {
816 throw new \LogicException('unserialize() is an internal function, it must not be called directly.');
817 }
818
819 [$value, $scale] = \explode(':', $value);
820
821 $this->value = $value;
822 $this->scale = (int) $scale;
823 }
824
825 /**
826 * Puts the internal values of the given decimal numbers on the same scale.
827 *
828 * @param BigDecimal $x The first decimal number.
829 * @param BigDecimal $y The second decimal number.
830 *
831 * @return array{string, string} The scaled integer values of $x and $y.
832 */
833 private function scaleValues(BigDecimal $x, BigDecimal $y) : array
834 {
835 $a = $x->value;
836 $b = $y->value;
837
838 if ($b !== '0' && $x->scale > $y->scale) {
839 $b .= \str_repeat('0', $x->scale - $y->scale);
840 } elseif ($a !== '0' && $x->scale < $y->scale) {
841 $a .= \str_repeat('0', $y->scale - $x->scale);
842 }
843
844 return [$a, $b];
845 }
846
847 /**
848 * @param int $scale
849 *
850 * @return string
851 */
852 private function valueWithMinScale(int $scale) : string
853 {
854 $value = $this->value;
855
856 if ($this->value !== '0' && $scale > $this->scale) {
857 $value .= \str_repeat('0', $scale - $this->scale);
858 }
859
860 return $value;
861 }
862
863 /**
864 * Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
865 *
866 * @return string
867 */
868 private function getUnscaledValueWithLeadingZeros() : string
869 {
870 $value = $this->value;
871 $targetLength = $this->scale + 1;
872 $negative = ($value[0] === '-');
873 $length = \strlen($value);
874
875 if ($negative) {
876 $length--;
877 }
878
879 if ($length >= $targetLength) {
880 return $this->value;
881 }
882
883 if ($negative) {
884 $value = \substr($value, 1);
885 }
886
887 $value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT);
888
889 if ($negative) {
890 $value = '-' . $value;
891 }
892
893 return $value;
894 }
895 }
896