[ 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\Internal; 6 7 use Brick\Math\Exception\RoundingNecessaryException; 8 use Brick\Math\RoundingMode; 9 10 /** 11 * Performs basic operations on arbitrary size integers. 12 * 13 * Unless otherwise specified, all parameters must be validated as non-empty strings of digits, 14 * without leading zero, and with an optional leading minus sign if the number is not zero. 15 * 16 * Any other parameter format will lead to undefined behaviour. 17 * All methods must return strings respecting this format, unless specified otherwise. 18 * 19 * @internal 20 * 21 * @psalm-immutable 22 */ 23 abstract class Calculator 24 { 25 /** 26 * The maximum exponent value allowed for the pow() method. 27 */ 28 public const MAX_POWER = 1000000; 29 30 /** 31 * The alphabet for converting from and to base 2 to 36, lowercase. 32 */ 33 public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'; 34 35 /** 36 * The Calculator instance in use. 37 * 38 * @var Calculator|null 39 */ 40 private static $instance; 41 42 /** 43 * Sets the Calculator instance to use. 44 * 45 * An instance is typically set only in unit tests: the autodetect is usually the best option. 46 * 47 * @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect. 48 * 49 * @return void 50 */ 51 final public static function set(?Calculator $calculator) : void 52 { 53 self::$instance = $calculator; 54 } 55 56 /** 57 * Returns the Calculator instance to use. 58 * 59 * If none has been explicitly set, the fastest available implementation will be returned. 60 * 61 * @return Calculator 62 * 63 * @psalm-pure 64 * @psalm-suppress ImpureStaticProperty 65 */ 66 final public static function get() : Calculator 67 { 68 if (self::$instance === null) { 69 /** @psalm-suppress ImpureMethodCall */ 70 self::$instance = self::detect(); 71 } 72 73 return self::$instance; 74 } 75 76 /** 77 * Returns the fastest available Calculator implementation. 78 * 79 * @codeCoverageIgnore 80 * 81 * @return Calculator 82 */ 83 private static function detect() : Calculator 84 { 85 if (\extension_loaded('gmp')) { 86 return new Calculator\GmpCalculator(); 87 } 88 89 if (\extension_loaded('bcmath')) { 90 return new Calculator\BcMathCalculator(); 91 } 92 93 return new Calculator\NativeCalculator(); 94 } 95 96 /** 97 * Extracts the sign & digits of the operands. 98 * 99 * @param string $a The first operand. 100 * @param string $b The second operand. 101 * 102 * @return array{0: bool, 1: bool, 2: string, 3: string} Whether $a and $b are negative, followed by their digits. 103 */ 104 final protected function init(string $a, string $b) : array 105 { 106 return [ 107 $aNeg = ($a[0] === '-'), 108 $bNeg = ($b[0] === '-'), 109 110 $aNeg ? \substr($a, 1) : $a, 111 $bNeg ? \substr($b, 1) : $b, 112 ]; 113 } 114 115 /** 116 * Returns the absolute value of a number. 117 * 118 * @param string $n The number. 119 * 120 * @return string The absolute value. 121 */ 122 final public function abs(string $n) : string 123 { 124 return ($n[0] === '-') ? \substr($n, 1) : $n; 125 } 126 127 /** 128 * Negates a number. 129 * 130 * @param string $n The number. 131 * 132 * @return string The negated value. 133 */ 134 final public function neg(string $n) : string 135 { 136 if ($n === '0') { 137 return '0'; 138 } 139 140 if ($n[0] === '-') { 141 return \substr($n, 1); 142 } 143 144 return '-' . $n; 145 } 146 147 /** 148 * Compares two numbers. 149 * 150 * @param string $a The first number. 151 * @param string $b The second number. 152 * 153 * @return int [-1, 0, 1] If the first number is less than, equal to, or greater than the second number. 154 */ 155 final public function cmp(string $a, string $b) : int 156 { 157 [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); 158 159 if ($aNeg && ! $bNeg) { 160 return -1; 161 } 162 163 if ($bNeg && ! $aNeg) { 164 return 1; 165 } 166 167 $aLen = \strlen($aDig); 168 $bLen = \strlen($bDig); 169 170 if ($aLen < $bLen) { 171 $result = -1; 172 } elseif ($aLen > $bLen) { 173 $result = 1; 174 } else { 175 $result = $aDig <=> $bDig; 176 } 177 178 return $aNeg ? -$result : $result; 179 } 180 181 /** 182 * Adds two numbers. 183 * 184 * @param string $a The augend. 185 * @param string $b The addend. 186 * 187 * @return string The sum. 188 */ 189 abstract public function add(string $a, string $b) : string; 190 191 /** 192 * Subtracts two numbers. 193 * 194 * @param string $a The minuend. 195 * @param string $b The subtrahend. 196 * 197 * @return string The difference. 198 */ 199 abstract public function sub(string $a, string $b) : string; 200 201 /** 202 * Multiplies two numbers. 203 * 204 * @param string $a The multiplicand. 205 * @param string $b The multiplier. 206 * 207 * @return string The product. 208 */ 209 abstract public function mul(string $a, string $b) : string; 210 211 /** 212 * Returns the quotient of the division of two numbers. 213 * 214 * @param string $a The dividend. 215 * @param string $b The divisor, must not be zero. 216 * 217 * @return string The quotient. 218 */ 219 abstract public function divQ(string $a, string $b) : string; 220 221 /** 222 * Returns the remainder of the division of two numbers. 223 * 224 * @param string $a The dividend. 225 * @param string $b The divisor, must not be zero. 226 * 227 * @return string The remainder. 228 */ 229 abstract public function divR(string $a, string $b) : string; 230 231 /** 232 * Returns the quotient and remainder of the division of two numbers. 233 * 234 * @param string $a The dividend. 235 * @param string $b The divisor, must not be zero. 236 * 237 * @return string[] An array containing the quotient and remainder. 238 */ 239 abstract public function divQR(string $a, string $b) : array; 240 241 /** 242 * Exponentiates a number. 243 * 244 * @param string $a The base number. 245 * @param int $e The exponent, validated as an integer between 0 and MAX_POWER. 246 * 247 * @return string The power. 248 */ 249 abstract public function pow(string $a, int $e) : string; 250 251 /** 252 * @param string $a 253 * @param string $b The modulus; must not be zero. 254 * 255 * @return string 256 */ 257 public function mod(string $a, string $b) : string 258 { 259 return $this->divR($this->add($this->divR($a, $b), $b), $b); 260 } 261 262 /** 263 * Returns the modular multiplicative inverse of $x modulo $m. 264 * 265 * If $x has no multiplicative inverse mod m, this method must return null. 266 * 267 * This method can be overridden by the concrete implementation if the underlying library has built-in support. 268 * 269 * @param string $x 270 * @param string $m The modulus; must not be negative or zero. 271 * 272 * @return string|null 273 */ 274 public function modInverse(string $x, string $m) : ?string 275 { 276 if ($m === '1') { 277 return '0'; 278 } 279 280 $modVal = $x; 281 282 if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) { 283 $modVal = $this->mod($x, $m); 284 } 285 286 $x = '0'; 287 $y = '0'; 288 $g = $this->gcdExtended($modVal, $m, $x, $y); 289 290 if ($g !== '1') { 291 return null; 292 } 293 294 return $this->mod($this->add($this->mod($x, $m), $m), $m); 295 } 296 297 /** 298 * Raises a number into power with modulo. 299 * 300 * @param string $base The base number; must be positive or zero. 301 * @param string $exp The exponent; must be positive or zero. 302 * @param string $mod The modulus; must be strictly positive. 303 * 304 * @return string The power. 305 */ 306 abstract public function modPow(string $base, string $exp, string $mod) : string; 307 308 /** 309 * Returns the greatest common divisor of the two numbers. 310 * 311 * This method can be overridden by the concrete implementation if the underlying library 312 * has built-in support for GCD calculations. 313 * 314 * @param string $a The first number. 315 * @param string $b The second number. 316 * 317 * @return string The GCD, always positive, or zero if both arguments are zero. 318 */ 319 public function gcd(string $a, string $b) : string 320 { 321 if ($a === '0') { 322 return $this->abs($b); 323 } 324 325 if ($b === '0') { 326 return $this->abs($a); 327 } 328 329 return $this->gcd($b, $this->divR($a, $b)); 330 } 331 332 private function gcdExtended(string $a, string $b, string &$x, string &$y) : string 333 { 334 if ($a === '0') { 335 $x = '0'; 336 $y = '1'; 337 338 return $b; 339 } 340 341 $x1 = '0'; 342 $y1 = '0'; 343 344 $gcd = $this->gcdExtended($this->mod($b, $a), $a, $x1, $y1); 345 346 $x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1)); 347 $y = $x1; 348 349 return $gcd; 350 } 351 352 /** 353 * Returns the square root of the given number, rounded down. 354 * 355 * The result is the largest x such that x² ≤ n. 356 * The input MUST NOT be negative. 357 * 358 * @param string $n The number. 359 * 360 * @return string The square root. 361 */ 362 abstract public function sqrt(string $n) : string; 363 364 /** 365 * Converts a number from an arbitrary base. 366 * 367 * This method can be overridden by the concrete implementation if the underlying library 368 * has built-in support for base conversion. 369 * 370 * @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base. 371 * @param int $base The base of the number, validated from 2 to 36. 372 * 373 * @return string The converted number, following the Calculator conventions. 374 */ 375 public function fromBase(string $number, int $base) : string 376 { 377 return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base); 378 } 379 380 /** 381 * Converts a number to an arbitrary base. 382 * 383 * This method can be overridden by the concrete implementation if the underlying library 384 * has built-in support for base conversion. 385 * 386 * @param string $number The number to convert, following the Calculator conventions. 387 * @param int $base The base to convert to, validated from 2 to 36. 388 * 389 * @return string The converted number, lowercase. 390 */ 391 public function toBase(string $number, int $base) : string 392 { 393 $negative = ($number[0] === '-'); 394 395 if ($negative) { 396 $number = \substr($number, 1); 397 } 398 399 $number = $this->toArbitraryBase($number, self::ALPHABET, $base); 400 401 if ($negative) { 402 return '-' . $number; 403 } 404 405 return $number; 406 } 407 408 /** 409 * Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10. 410 * 411 * @param string $number The number to convert, validated as a non-empty string, 412 * containing only chars in the given alphabet/base. 413 * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. 414 * @param int $base The base of the number, validated from 2 to alphabet length. 415 * 416 * @return string The number in base 10, following the Calculator conventions. 417 */ 418 final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string 419 { 420 // remove leading "zeros" 421 $number = \ltrim($number, $alphabet[0]); 422 423 if ($number === '') { 424 return '0'; 425 } 426 427 // optimize for "one" 428 if ($number === $alphabet[1]) { 429 return '1'; 430 } 431 432 $result = '0'; 433 $power = '1'; 434 435 $base = (string) $base; 436 437 for ($i = \strlen($number) - 1; $i >= 0; $i--) { 438 $index = \strpos($alphabet, $number[$i]); 439 440 if ($index !== 0) { 441 $result = $this->add($result, ($index === 1) 442 ? $power 443 : $this->mul($power, (string) $index) 444 ); 445 } 446 447 if ($i !== 0) { 448 $power = $this->mul($power, $base); 449 } 450 } 451 452 return $result; 453 } 454 455 /** 456 * Converts a non-negative number to an arbitrary base using a custom alphabet. 457 * 458 * @param string $number The number to convert, positive or zero, following the Calculator conventions. 459 * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. 460 * @param int $base The base to convert to, validated from 2 to alphabet length. 461 * 462 * @return string The converted number in the given alphabet. 463 */ 464 final public function toArbitraryBase(string $number, string $alphabet, int $base) : string 465 { 466 if ($number === '0') { 467 return $alphabet[0]; 468 } 469 470 $base = (string) $base; 471 $result = ''; 472 473 while ($number !== '0') { 474 [$number, $remainder] = $this->divQR($number, $base); 475 $remainder = (int) $remainder; 476 477 $result .= $alphabet[$remainder]; 478 } 479 480 return \strrev($result); 481 } 482 483 /** 484 * Performs a rounded division. 485 * 486 * Rounding is performed when the remainder of the division is not zero. 487 * 488 * @param string $a The dividend. 489 * @param string $b The divisor. 490 * @param int $roundingMode The rounding mode. 491 * 492 * @return string 493 * 494 * @throws \InvalidArgumentException If the rounding mode is invalid. 495 * @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary. 496 */ 497 final public function divRound(string $a, string $b, int $roundingMode) : string 498 { 499 [$quotient, $remainder] = $this->divQR($a, $b); 500 501 $hasDiscardedFraction = ($remainder !== '0'); 502 $isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-'); 503 504 $discardedFractionSign = function() use ($remainder, $b) : int { 505 $r = $this->abs($this->mul($remainder, '2')); 506 $b = $this->abs($b); 507 508 return $this->cmp($r, $b); 509 }; 510 511 $increment = false; 512 513 switch ($roundingMode) { 514 case RoundingMode::UNNECESSARY: 515 if ($hasDiscardedFraction) { 516 throw RoundingNecessaryException::roundingNecessary(); 517 } 518 break; 519 520 case RoundingMode::UP: 521 $increment = $hasDiscardedFraction; 522 break; 523 524 case RoundingMode::DOWN: 525 break; 526 527 case RoundingMode::CEILING: 528 $increment = $hasDiscardedFraction && $isPositiveOrZero; 529 break; 530 531 case RoundingMode::FLOOR: 532 $increment = $hasDiscardedFraction && ! $isPositiveOrZero; 533 break; 534 535 case RoundingMode::HALF_UP: 536 $increment = $discardedFractionSign() >= 0; 537 break; 538 539 case RoundingMode::HALF_DOWN: 540 $increment = $discardedFractionSign() > 0; 541 break; 542 543 case RoundingMode::HALF_CEILING: 544 $increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0; 545 break; 546 547 case RoundingMode::HALF_FLOOR: 548 $increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; 549 break; 550 551 case RoundingMode::HALF_EVEN: 552 $lastDigit = (int) $quotient[-1]; 553 $lastDigitIsEven = ($lastDigit % 2 === 0); 554 $increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; 555 break; 556 557 default: 558 throw new \InvalidArgumentException('Invalid rounding mode.'); 559 } 560 561 if ($increment) { 562 return $this->add($quotient, $isPositiveOrZero ? '1' : '-1'); 563 } 564 565 return $quotient; 566 } 567 568 /** 569 * Calculates bitwise AND of two numbers. 570 * 571 * This method can be overridden by the concrete implementation if the underlying library 572 * has built-in support for bitwise operations. 573 * 574 * @param string $a 575 * @param string $b 576 * 577 * @return string 578 */ 579 public function and(string $a, string $b) : string 580 { 581 return $this->bitwise('and', $a, $b); 582 } 583 584 /** 585 * Calculates bitwise OR of two numbers. 586 * 587 * This method can be overridden by the concrete implementation if the underlying library 588 * has built-in support for bitwise operations. 589 * 590 * @param string $a 591 * @param string $b 592 * 593 * @return string 594 */ 595 public function or(string $a, string $b) : string 596 { 597 return $this->bitwise('or', $a, $b); 598 } 599 600 /** 601 * Calculates bitwise XOR of two numbers. 602 * 603 * This method can be overridden by the concrete implementation if the underlying library 604 * has built-in support for bitwise operations. 605 * 606 * @param string $a 607 * @param string $b 608 * 609 * @return string 610 */ 611 public function xor(string $a, string $b) : string 612 { 613 return $this->bitwise('xor', $a, $b); 614 } 615 616 /** 617 * Performs a bitwise operation on a decimal number. 618 * 619 * @param string $operator The operator to use, must be "and", "or" or "xor". 620 * @param string $a The left operand. 621 * @param string $b The right operand. 622 * 623 * @return string 624 */ 625 private function bitwise(string $operator, string $a, string $b) : string 626 { 627 [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); 628 629 $aBin = $this->toBinary($aDig); 630 $bBin = $this->toBinary($bDig); 631 632 $aLen = \strlen($aBin); 633 $bLen = \strlen($bBin); 634 635 if ($aLen > $bLen) { 636 $bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin; 637 } elseif ($bLen > $aLen) { 638 $aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin; 639 } 640 641 if ($aNeg) { 642 $aBin = $this->twosComplement($aBin); 643 } 644 if ($bNeg) { 645 $bBin = $this->twosComplement($bBin); 646 } 647 648 switch ($operator) { 649 case 'and': 650 $value = $aBin & $bBin; 651 $negative = ($aNeg and $bNeg); 652 break; 653 654 case 'or': 655 $value = $aBin | $bBin; 656 $negative = ($aNeg or $bNeg); 657 break; 658 659 case 'xor': 660 $value = $aBin ^ $bBin; 661 $negative = ($aNeg xor $bNeg); 662 break; 663 664 // @codeCoverageIgnoreStart 665 default: 666 throw new \InvalidArgumentException('Invalid bitwise operator.'); 667 // @codeCoverageIgnoreEnd 668 } 669 670 if ($negative) { 671 $value = $this->twosComplement($value); 672 } 673 674 $result = $this->toDecimal($value); 675 676 return $negative ? $this->neg($result) : $result; 677 } 678 679 /** 680 * @param string $number A positive, binary number. 681 * 682 * @return string 683 */ 684 private function twosComplement(string $number) : string 685 { 686 $xor = \str_repeat("\xff", \strlen($number)); 687 688 $number = $number ^ $xor; 689 690 for ($i = \strlen($number) - 1; $i >= 0; $i--) { 691 $byte = \ord($number[$i]); 692 693 if (++$byte !== 256) { 694 $number[$i] = \chr($byte); 695 break; 696 } 697 698 $number[$i] = "\x00"; 699 700 if ($i === 0) { 701 $number = "\x01" . $number; 702 } 703 } 704 705 return $number; 706 } 707 708 /** 709 * Converts a decimal number to a binary string. 710 * 711 * @param string $number The number to convert, positive or zero, only digits. 712 * 713 * @return string 714 */ 715 private function toBinary(string $number) : string 716 { 717 $result = ''; 718 719 while ($number !== '0') { 720 [$number, $remainder] = $this->divQR($number, '256'); 721 $result .= \chr((int) $remainder); 722 } 723 724 return \strrev($result); 725 } 726 727 /** 728 * Returns the positive decimal representation of a binary number. 729 * 730 * @param string $bytes The bytes representing the number. 731 * 732 * @return string 733 */ 734 private function toDecimal(string $bytes) : string 735 { 736 $result = '0'; 737 $power = '1'; 738 739 for ($i = \strlen($bytes) - 1; $i >= 0; $i--) { 740 $index = \ord($bytes[$i]); 741 742 if ($index !== 0) { 743 $result = $this->add($result, ($index === 1) 744 ? $power 745 : $this->mul($power, (string) $index) 746 ); 747 } 748 749 if ($i !== 0) { 750 $power = $this->mul($power, '256'); 751 } 752 } 753 754 return $result; 755 } 756 }
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 |