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