[ 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\NumberFormatException; 10 use Brick\Math\Exception\RoundingNecessaryException; 11 12 /** 13 * Common interface for arbitrary-precision rational numbers. 14 * 15 * @psalm-immutable 16 */ 17 abstract class BigNumber implements \Serializable, \JsonSerializable 18 { 19 /** 20 * The regular expression used to parse integer, decimal and rational numbers. 21 * 22 * @var string 23 */ 24 private const PARSE_REGEXP = 25 '/^' . 26 '(?<integral>[\-\+]?[0-9]+)' . 27 '(?:' . 28 '(?:' . 29 '(?:\.(?<fractional>[0-9]+))?' . 30 '(?:[eE](?<exponent>[\-\+]?[0-9]+))?' . 31 ')' . '|' . '(?:' . 32 '(?:\/(?<denominator>[0-9]+))?' . 33 ')' . 34 ')?' . 35 '$/'; 36 37 /** 38 * Creates a BigNumber of the given value. 39 * 40 * The concrete return type is dependent on the given value, with the following rules: 41 * 42 * - BigNumber instances are returned as is 43 * - integer numbers are returned as BigInteger 44 * - floating point numbers are converted to a string then parsed as such 45 * - strings containing a `/` character are returned as BigRational 46 * - strings containing a `.` character or using an exponential notation are returned as BigDecimal 47 * - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger 48 * 49 * @param BigNumber|int|float|string $value 50 * 51 * @return BigNumber 52 * 53 * @throws NumberFormatException If the format of the number is not valid. 54 * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. 55 * 56 * @psalm-pure 57 */ 58 public static function of($value) : BigNumber 59 { 60 if ($value instanceof BigNumber) { 61 return $value; 62 } 63 64 if (\is_int($value)) { 65 return new BigInteger((string) $value); 66 } 67 68 if (\is_float($value)) { 69 $value = self::floatToString($value); 70 } else { 71 $value = (string) $value; 72 } 73 74 if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) { 75 throw new NumberFormatException(\sprintf('The given value "%s" does not represent a valid number.', $value)); 76 } 77 78 if (isset($matches['denominator'])) { 79 $numerator = self::cleanUp($matches['integral']); 80 $denominator = \ltrim($matches['denominator'], '0'); 81 82 if ($denominator === '') { 83 throw DivisionByZeroException::denominatorMustNotBeZero(); 84 } 85 86 return new BigRational(new BigInteger($numerator), new BigInteger($denominator), false); 87 } 88 89 if (isset($matches['fractional']) || isset($matches['exponent'])) { 90 $fractional = isset($matches['fractional']) ? $matches['fractional'] : ''; 91 $exponent = isset($matches['exponent']) ? (int) $matches['exponent'] : 0; 92 93 $unscaledValue = self::cleanUp($matches['integral'] . $fractional); 94 95 $scale = \strlen($fractional) - $exponent; 96 97 if ($scale < 0) { 98 if ($unscaledValue !== '0') { 99 $unscaledValue .= \str_repeat('0', - $scale); 100 } 101 $scale = 0; 102 } 103 104 return new BigDecimal($unscaledValue, $scale); 105 } 106 107 $integral = self::cleanUp($matches['integral']); 108 109 return new BigInteger($integral); 110 } 111 112 /** 113 * Safely converts float to string, avoiding locale-dependent issues. 114 * 115 * @see https://github.com/brick/math/pull/20 116 * 117 * @param float $float 118 * 119 * @return string 120 * 121 * @psalm-pure 122 * @psalm-suppress ImpureFunctionCall 123 */ 124 private static function floatToString(float $float) : string 125 { 126 $currentLocale = \setlocale(LC_NUMERIC, '0'); 127 \setlocale(LC_NUMERIC, 'C'); 128 129 $result = (string) $float; 130 131 \setlocale(LC_NUMERIC, $currentLocale); 132 133 return $result; 134 } 135 136 /** 137 * Proxy method to access protected constructors from sibling classes. 138 * 139 * @internal 140 * 141 * @param mixed ...$args The arguments to the constructor. 142 * 143 * @return static 144 * 145 * @psalm-pure 146 */ 147 protected static function create(... $args) : BigNumber 148 { 149 /** @psalm-suppress TooManyArguments */ 150 return new static(... $args); 151 } 152 153 /** 154 * Returns the minimum of the given values. 155 * 156 * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible 157 * to an instance of the class this method is called on. 158 * 159 * @return static The minimum value. 160 * 161 * @throws \InvalidArgumentException If no values are given. 162 * @throws MathException If an argument is not valid. 163 * 164 * @psalm-pure 165 */ 166 public static function min(...$values) : BigNumber 167 { 168 $min = null; 169 170 foreach ($values as $value) { 171 $value = static::of($value); 172 173 if ($min === null || $value->isLessThan($min)) { 174 $min = $value; 175 } 176 } 177 178 if ($min === null) { 179 throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); 180 } 181 182 return $min; 183 } 184 185 /** 186 * Returns the maximum of the given values. 187 * 188 * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible 189 * to an instance of the class this method is called on. 190 * 191 * @return static The maximum value. 192 * 193 * @throws \InvalidArgumentException If no values are given. 194 * @throws MathException If an argument is not valid. 195 * 196 * @psalm-pure 197 */ 198 public static function max(...$values) : BigNumber 199 { 200 $max = null; 201 202 foreach ($values as $value) { 203 $value = static::of($value); 204 205 if ($max === null || $value->isGreaterThan($max)) { 206 $max = $value; 207 } 208 } 209 210 if ($max === null) { 211 throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); 212 } 213 214 return $max; 215 } 216 217 /** 218 * Returns the sum of the given values. 219 * 220 * @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible 221 * to an instance of the class this method is called on. 222 * 223 * @return static The sum. 224 * 225 * @throws \InvalidArgumentException If no values are given. 226 * @throws MathException If an argument is not valid. 227 * 228 * @psalm-pure 229 */ 230 public static function sum(...$values) : BigNumber 231 { 232 /** @var BigNumber|null $sum */ 233 $sum = null; 234 235 foreach ($values as $value) { 236 $value = static::of($value); 237 238 if ($sum === null) { 239 $sum = $value; 240 } else { 241 $sum = self::add($sum, $value); 242 } 243 } 244 245 if ($sum === null) { 246 throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); 247 } 248 249 return $sum; 250 } 251 252 /** 253 * Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException. 254 * 255 * @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to 256 * concrete classes the responsibility to perform the addition themselves or delegate it to the given number, 257 * depending on their ability to perform the operation. This will also require a version bump because we're 258 * potentially breaking custom BigNumber implementations (if any...) 259 * 260 * @param BigNumber $a 261 * @param BigNumber $b 262 * 263 * @return BigNumber 264 * 265 * @psalm-pure 266 */ 267 private static function add(BigNumber $a, BigNumber $b) : BigNumber 268 { 269 if ($a instanceof BigRational) { 270 return $a->plus($b); 271 } 272 273 if ($b instanceof BigRational) { 274 return $b->plus($a); 275 } 276 277 if ($a instanceof BigDecimal) { 278 return $a->plus($b); 279 } 280 281 if ($b instanceof BigDecimal) { 282 return $b->plus($a); 283 } 284 285 /** @var BigInteger $a */ 286 287 return $a->plus($b); 288 } 289 290 /** 291 * Removes optional leading zeros and + sign from the given number. 292 * 293 * @param string $number The number, validated as a non-empty string of digits with optional sign. 294 * 295 * @return string 296 * 297 * @psalm-pure 298 */ 299 private static function cleanUp(string $number) : string 300 { 301 $firstChar = $number[0]; 302 303 if ($firstChar === '+' || $firstChar === '-') { 304 $number = \substr($number, 1); 305 } 306 307 $number = \ltrim($number, '0'); 308 309 if ($number === '') { 310 return '0'; 311 } 312 313 if ($firstChar === '-') { 314 return '-' . $number; 315 } 316 317 return $number; 318 } 319 320 /** 321 * Checks if this number is equal to the given one. 322 * 323 * @param BigNumber|int|float|string $that 324 * 325 * @return bool 326 */ 327 public function isEqualTo($that) : bool 328 { 329 return $this->compareTo($that) === 0; 330 } 331 332 /** 333 * Checks if this number is strictly lower than the given one. 334 * 335 * @param BigNumber|int|float|string $that 336 * 337 * @return bool 338 */ 339 public function isLessThan($that) : bool 340 { 341 return $this->compareTo($that) < 0; 342 } 343 344 /** 345 * Checks if this number is lower than or equal to the given one. 346 * 347 * @param BigNumber|int|float|string $that 348 * 349 * @return bool 350 */ 351 public function isLessThanOrEqualTo($that) : bool 352 { 353 return $this->compareTo($that) <= 0; 354 } 355 356 /** 357 * Checks if this number is strictly greater than the given one. 358 * 359 * @param BigNumber|int|float|string $that 360 * 361 * @return bool 362 */ 363 public function isGreaterThan($that) : bool 364 { 365 return $this->compareTo($that) > 0; 366 } 367 368 /** 369 * Checks if this number is greater than or equal to the given one. 370 * 371 * @param BigNumber|int|float|string $that 372 * 373 * @return bool 374 */ 375 public function isGreaterThanOrEqualTo($that) : bool 376 { 377 return $this->compareTo($that) >= 0; 378 } 379 380 /** 381 * Checks if this number equals zero. 382 * 383 * @return bool 384 */ 385 public function isZero() : bool 386 { 387 return $this->getSign() === 0; 388 } 389 390 /** 391 * Checks if this number is strictly negative. 392 * 393 * @return bool 394 */ 395 public function isNegative() : bool 396 { 397 return $this->getSign() < 0; 398 } 399 400 /** 401 * Checks if this number is negative or zero. 402 * 403 * @return bool 404 */ 405 public function isNegativeOrZero() : bool 406 { 407 return $this->getSign() <= 0; 408 } 409 410 /** 411 * Checks if this number is strictly positive. 412 * 413 * @return bool 414 */ 415 public function isPositive() : bool 416 { 417 return $this->getSign() > 0; 418 } 419 420 /** 421 * Checks if this number is positive or zero. 422 * 423 * @return bool 424 */ 425 public function isPositiveOrZero() : bool 426 { 427 return $this->getSign() >= 0; 428 } 429 430 /** 431 * Returns the sign of this number. 432 * 433 * @return int -1 if the number is negative, 0 if zero, 1 if positive. 434 */ 435 abstract public function getSign() : int; 436 437 /** 438 * Compares this number to the given one. 439 * 440 * @param BigNumber|int|float|string $that 441 * 442 * @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`. 443 * 444 * @throws MathException If the number is not valid. 445 */ 446 abstract public function compareTo($that) : int; 447 448 /** 449 * Converts this number to a BigInteger. 450 * 451 * @return BigInteger The converted number. 452 * 453 * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding. 454 */ 455 abstract public function toBigInteger() : BigInteger; 456 457 /** 458 * Converts this number to a BigDecimal. 459 * 460 * @return BigDecimal The converted number. 461 * 462 * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding. 463 */ 464 abstract public function toBigDecimal() : BigDecimal; 465 466 /** 467 * Converts this number to a BigRational. 468 * 469 * @return BigRational The converted number. 470 */ 471 abstract public function toBigRational() : BigRational; 472 473 /** 474 * Converts this number to a BigDecimal with the given scale, using rounding if necessary. 475 * 476 * @param int $scale The scale of the resulting `BigDecimal`. 477 * @param int $roundingMode A `RoundingMode` constant. 478 * 479 * @return BigDecimal 480 * 481 * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. 482 * This only applies when RoundingMode::UNNECESSARY is used. 483 */ 484 abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; 485 486 /** 487 * Returns the exact value of this number as a native integer. 488 * 489 * If this number cannot be converted to a native integer without losing precision, an exception is thrown. 490 * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit. 491 * 492 * @return int The converted value. 493 * 494 * @throws MathException If this number cannot be exactly converted to a native integer. 495 */ 496 abstract public function toInt() : int; 497 498 /** 499 * Returns an approximation of this number as a floating-point value. 500 * 501 * Note that this method can discard information as the precision of a floating-point value 502 * is inherently limited. 503 * 504 * If the number is greater than the largest representable floating point number, positive infinity is returned. 505 * If the number is less than the smallest representable floating point number, negative infinity is returned. 506 * 507 * @return float The converted value. 508 */ 509 abstract public function toFloat() : float; 510 511 /** 512 * Returns a string representation of this number. 513 * 514 * The output of this method can be parsed by the `of()` factory method; 515 * this will yield an object equal to this one, without any information loss. 516 * 517 * @return string 518 */ 519 abstract public function __toString() : string; 520 521 /** 522 * {@inheritdoc} 523 */ 524 public function jsonSerialize() : string 525 { 526 return $this->__toString(); 527 } 528 }
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 |