aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/brick/math/src/BigNumber.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/brick/math/src/BigNumber.php')
-rw-r--r--vendor/brick/math/src/BigNumber.php243
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();
}