PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.9.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.9.16
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / vendor / brick / math / src / BigDecimal.php

BigDecimal.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.9.16, 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