diff options
Diffstat (limited to 'vendor/brick/math/src/BigNumber.php')
-rw-r--r-- | vendor/brick/math/src/BigNumber.php | 243 |
1 files changed, 123 insertions, 120 deletions
diff --git a/vendor/brick/math/src/BigNumber.php b/vendor/brick/math/src/BigNumber.php index 80146d207..5dabd314b 100644 --- a/vendor/brick/math/src/BigNumber.php +++ b/vendor/brick/math/src/BigNumber.php @@ -8,32 +8,36 @@ use Brick\Math\Exception\DivisionByZeroException; use Brick\Math\Exception\MathException; use Brick\Math\Exception\NumberFormatException; use Brick\Math\Exception\RoundingNecessaryException; +use Override; /** * Common interface for arbitrary-precision rational numbers. * * @psalm-immutable */ -abstract class BigNumber implements \Serializable, \JsonSerializable +abstract class BigNumber implements \JsonSerializable { /** - * The regular expression used to parse integer, decimal and rational numbers. + * The regular expression used to parse integer or decimal numbers. */ - private const PARSE_REGEXP = + private const PARSE_REGEXP_NUMERICAL = '/^' . '(?<sign>[\-\+])?' . - '(?:' . - '(?:' . - '(?<integral>[0-9]+)?' . - '(?<point>\.)?' . - '(?<fractional>[0-9]+)?' . - '(?:[eE](?<exponent>[\-\+]?[0-9]+))?' . - ')|(?:' . - '(?<numerator>[0-9]+)' . - '\/?' . - '(?<denominator>[0-9]+)' . - ')' . - ')' . + '(?<integral>[0-9]+)?' . + '(?<point>\.)?' . + '(?<fractional>[0-9]+)?' . + '(?:[eE](?<exponent>[\-\+]?[0-9]+))?' . + '$/'; + + /** + * The regular expression used to parse rational numbers. + */ + private const PARSE_REGEXP_RATIONAL = + '/^' . + '(?<sign>[\-\+])?' . + '(?<numerator>[0-9]+)' . + '\/?' . + '(?<denominator>[0-9]+)' . '$/'; /** @@ -48,12 +52,33 @@ abstract class BigNumber implements \Serializable, \JsonSerializable * - strings containing a `.` character or using an exponential notation are returned as BigDecimal * - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger * - * @throws NumberFormatException If the format of the number is not valid. + * @throws NumberFormatException If the format of the number is not valid. + * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. + * @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding. + * + * @psalm-pure + */ + final public static function of(BigNumber|int|float|string $value) : static + { + $value = self::_of($value); + + if (static::class === BigNumber::class) { + // https://github.com/vimeo/psalm/issues/10309 + assert($value instanceof static); + + return $value; + } + + return static::from($value); + } + + /** + * @throws NumberFormatException If the format of the number is not valid. * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. * * @psalm-pure */ - public static function of(BigNumber|int|float|string $value) : BigNumber + private static function _of(BigNumber|int|float|string $value) : BigNumber { if ($value instanceof BigNumber) { return $value; @@ -63,34 +88,25 @@ abstract class BigNumber implements \Serializable, \JsonSerializable return new BigInteger((string) $value); } - $value = \is_float($value) ? self::floatToString($value) : $value; - - $throw = static function() use ($value) : void { - throw new NumberFormatException(\sprintf( - 'The given value "%s" does not represent a valid number.', - $value - )); - }; - - if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) { - $throw(); + if (is_float($value)) { + $value = (string) $value; } - $getMatch = static fn(string $value): ?string => (($matches[$value] ?? '') !== '') ? $matches[$value] : null; + if (str_contains($value, '/')) { + // Rational number + if (\preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { + throw NumberFormatException::invalidFormat($value); + } - $sign = $getMatch('sign'); - $numerator = $getMatch('numerator'); - $denominator = $getMatch('denominator'); + $sign = $matches['sign']; + $numerator = $matches['numerator']; + $denominator = $matches['denominator']; - if ($numerator !== null) { + assert($numerator !== null); assert($denominator !== null); - if ($sign !== null) { - $numerator = $sign . $numerator; - } - - $numerator = self::cleanUp($numerator); - $denominator = self::cleanUp($denominator); + $numerator = self::cleanUp($sign, $numerator); + $denominator = self::cleanUp(null, $denominator); if ($denominator === '0') { throw DivisionByZeroException::denominatorMustNotBeZero(); @@ -101,67 +117,62 @@ abstract class BigNumber implements \Serializable, \JsonSerializable new BigInteger($denominator), false ); - } + } else { + // Integer or decimal number + if (\preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { + throw NumberFormatException::invalidFormat($value); + } - $point = $getMatch('point'); - $integral = $getMatch('integral'); - $fractional = $getMatch('fractional'); - $exponent = $getMatch('exponent'); + $sign = $matches['sign']; + $point = $matches['point']; + $integral = $matches['integral']; + $fractional = $matches['fractional']; + $exponent = $matches['exponent']; - if ($integral === null && $fractional === null) { - $throw(); - } + if ($integral === null && $fractional === null) { + throw NumberFormatException::invalidFormat($value); + } - if ($integral === null) { - $integral = '0'; - } + if ($integral === null) { + $integral = '0'; + } - if ($point !== null || $exponent !== null) { - $fractional = ($fractional ?? ''); - $exponent = ($exponent !== null) ? (int) $exponent : 0; + if ($point !== null || $exponent !== null) { + $fractional = ($fractional ?? ''); + $exponent = ($exponent !== null) ? (int)$exponent : 0; - if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { - throw new NumberFormatException('Exponent too large.'); - } + if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { + throw new NumberFormatException('Exponent too large.'); + } - $unscaledValue = self::cleanUp(($sign ?? ''). $integral . $fractional); + $unscaledValue = self::cleanUp($sign, $integral . $fractional); - $scale = \strlen($fractional) - $exponent; + $scale = \strlen($fractional) - $exponent; - if ($scale < 0) { - if ($unscaledValue !== '0') { - $unscaledValue .= \str_repeat('0', - $scale); + if ($scale < 0) { + if ($unscaledValue !== '0') { + $unscaledValue .= \str_repeat('0', -$scale); + } + $scale = 0; } - $scale = 0; - } - return new BigDecimal($unscaledValue, $scale); - } + return new BigDecimal($unscaledValue, $scale); + } - $integral = self::cleanUp(($sign ?? '') . $integral); + $integral = self::cleanUp($sign, $integral); - return new BigInteger($integral); + return new BigInteger($integral); + } } /** - * Safely converts float to string, avoiding locale-dependent issues. + * Overridden by subclasses to convert a BigNumber to an instance of the subclass. * - * @see https://github.com/brick/math/pull/20 + * @throws RoundingNecessaryException If the value cannot be converted. * * @psalm-pure - * @psalm-suppress ImpureFunctionCall */ - private static function floatToString(float $float) : string - { - $currentLocale = \setlocale(LC_NUMERIC, '0'); - \setlocale(LC_NUMERIC, 'C'); - - $result = (string) $float; - - \setlocale(LC_NUMERIC, $currentLocale); - - return $result; - } + abstract protected static function from(BigNumber $number): static; /** * Proxy method to access BigInteger's protected constructor from sibling classes. @@ -169,7 +180,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable * @internal * @psalm-pure */ - protected function newBigInteger(string $value) : BigInteger + final protected function newBigInteger(string $value) : BigInteger { return new BigInteger($value); } @@ -180,7 +191,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable * @internal * @psalm-pure */ - protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal + final protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal { return new BigDecimal($value, $scale); } @@ -191,7 +202,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable * @internal * @psalm-pure */ - protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational + final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational { return new BigRational($numerator, $denominator, $checkDenominator); } @@ -205,11 +216,9 @@ abstract class BigNumber implements \Serializable, \JsonSerializable * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * - * @psalm-suppress LessSpecificReturnStatement - * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ - public static function min(BigNumber|int|float|string ...$values) : static + final public static function min(BigNumber|int|float|string ...$values) : static { $min = null; @@ -237,11 +246,9 @@ abstract class BigNumber implements \Serializable, \JsonSerializable * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * - * @psalm-suppress LessSpecificReturnStatement - * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ - public static function max(BigNumber|int|float|string ...$values) : static + final public static function max(BigNumber|int|float|string ...$values) : static { $max = null; @@ -271,7 +278,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable * * @psalm-pure */ - public static function sum(BigNumber|int|float|string ...$values) : static + final public static function sum(BigNumber|int|float|string ...$values) : static { /** @var static|null $sum */ $sum = null; @@ -323,37 +330,28 @@ abstract class BigNumber implements \Serializable, \JsonSerializable } /** - * Removes optional leading zeros and + sign from the given number. + * Removes optional leading zeros and applies sign. * - * @param string $number The number, validated as a non-empty string of digits with optional leading sign. + * @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'. + * @param string $number The number, validated as a non-empty string of digits. * * @psalm-pure */ - private static function cleanUp(string $number) : string + private static function cleanUp(string|null $sign, string $number) : string { - $firstChar = $number[0]; - - if ($firstChar === '+' || $firstChar === '-') { - $number = \substr($number, 1); - } - $number = \ltrim($number, '0'); if ($number === '') { return '0'; } - if ($firstChar === '-') { - return '-' . $number; - } - - return $number; + return $sign === '-' ? '-' . $number : $number; } /** * Checks if this number is equal to the given one. */ - public function isEqualTo(BigNumber|int|float|string $that) : bool + final public function isEqualTo(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) === 0; } @@ -361,7 +359,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Checks if this number is strictly lower than the given one. */ - public function isLessThan(BigNumber|int|float|string $that) : bool + final public function isLessThan(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) < 0; } @@ -369,7 +367,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Checks if this number is lower than or equal to the given one. */ - public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool + final public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) <= 0; } @@ -377,7 +375,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Checks if this number is strictly greater than the given one. */ - public function isGreaterThan(BigNumber|int|float|string $that) : bool + final public function isGreaterThan(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) > 0; } @@ -385,7 +383,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Checks if this number is greater than or equal to the given one. */ - public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool + final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) >= 0; } @@ -393,7 +391,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Checks if this number equals zero. */ - public function isZero() : bool + final public function isZero() : bool { return $this->getSign() === 0; } @@ -401,7 +399,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Checks if this number is strictly negative. */ - public function isNegative() : bool + final public function isNegative() : bool { return $this->getSign() < 0; } @@ -409,7 +407,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Checks if this number is negative or zero. */ - public function isNegativeOrZero() : bool + final public function isNegativeOrZero() : bool { return $this->getSign() <= 0; } @@ -417,7 +415,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Checks if this number is strictly positive. */ - public function isPositive() : bool + final public function isPositive() : bool { return $this->getSign() > 0; } @@ -425,7 +423,7 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Checks if this number is positive or zero. */ - public function isPositiveOrZero() : bool + final public function isPositiveOrZero() : bool { return $this->getSign() >= 0; } @@ -433,6 +431,8 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Returns the sign of this number. * + * @psalm-return -1|0|1 + * * @return int -1 if the number is negative, 0 if zero, 1 if positive. */ abstract public function getSign() : int; @@ -440,7 +440,9 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Compares this number to the given one. * - * @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`. + * @psalm-return -1|0|1 + * + * @return int -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`. * * @throws MathException If the number is not valid. */ @@ -468,13 +470,13 @@ abstract class BigNumber implements \Serializable, \JsonSerializable /** * Converts this number to a BigDecimal with the given scale, using rounding if necessary. * - * @param int $scale The scale of the resulting `BigDecimal`. - * @param int $roundingMode A `RoundingMode` constant. + * @param int $scale The scale of the resulting `BigDecimal`. + * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY. * * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. * This only applies when RoundingMode::UNNECESSARY is used. */ - abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; + abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; /** * Returns the exact value of this number as a native integer. @@ -505,7 +507,8 @@ abstract class BigNumber implements \Serializable, \JsonSerializable */ abstract public function __toString() : string; - public function jsonSerialize() : string + #[Override] + final public function jsonSerialize() : string { return $this->__toString(); } |