PluginProbe
Media Cloud Sync / 1.1.1
Media Cloud Sync v1.1.1
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / sdk / google / brick / math / src / BigDecimal.php

BigDecimal.php in Media Cloud Sync 1.1.1, at includes/sdk/google/brick/math/src/BigDecimal.php

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