PluginProbe
Media Cloud Sync / 1.3.11
Media Cloud Sync v1.3.11
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.3.11, at includes/sdk/google/brick/math/src/BigDecimal.php

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