class BigNumber
Common interface for arbitrary-precision rational numbers.
@psalm-immutable
Hierarchy
- class \Brick\Math\BigNumber implements \Brick\Math\JsonSerializable
Expanded class hierarchy of BigNumber
File
-
vendor/
brick/ math/ src/ BigNumber.php, line 17
Namespace
Brick\MathView source
abstract class BigNumber implements \JsonSerializable {
/**
* The regular expression used to parse integer or decimal numbers.
*/
private const PARSE_REGEXP_NUMERICAL = '/^' . '(?<sign>[\\-\\+])?' . '(?<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]+)' . '$/';
/**
* Creates a BigNumber of the given value.
*
* The concrete return type is dependent on the given value, with the following rules:
*
* - BigNumber instances are returned as is
* - integer numbers are returned as BigInteger
* - floating point numbers are converted to a string then parsed as such
* - strings containing a `/` character are returned as BigRational
* - 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 DivisionByZeroException If the value represents a rational number with a denominator of zero.
*
* @psalm-pure
*/
public static final 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);
}
/**
* @psalm-pure
*/
private static function _of(BigNumber|int|float|string $value) : BigNumber {
if ($value instanceof BigNumber) {
return $value;
}
if (\is_int($value)) {
return new BigInteger((string) $value);
}
if (is_float($value)) {
$value = (string) $value;
}
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 = $matches['sign'];
$numerator = $matches['numerator'];
$denominator = $matches['denominator'];
assert($numerator !== null);
assert($denominator !== null);
$numerator = self::cleanUp($sign, $numerator);
$denominator = self::cleanUp(null, $denominator);
if ($denominator === '0') {
throw DivisionByZeroException::denominatorMustNotBeZero();
}
return new BigRational(new BigInteger($numerator), 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);
}
$sign = $matches['sign'];
$point = $matches['point'];
$integral = $matches['integral'];
$fractional = $matches['fractional'];
$exponent = $matches['exponent'];
if ($integral === null && $fractional === null) {
throw NumberFormatException::invalidFormat($value);
}
if ($integral === null) {
$integral = '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.');
}
$unscaledValue = self::cleanUp($sign, $integral . $fractional);
$scale = \strlen($fractional) - $exponent;
if ($scale < 0) {
if ($unscaledValue !== '0') {
$unscaledValue .= \str_repeat('0', -$scale);
}
$scale = 0;
}
return new BigDecimal($unscaledValue, $scale);
}
$integral = self::cleanUp($sign, $integral);
return new BigInteger($integral);
}
}
/**
* Overridden by subclasses to convert a BigNumber to an instance of the subclass.
*
* @throws MathException If the value cannot be converted.
*
* @psalm-pure
*/
protected static abstract function from(BigNumber $number) : static;
/**
* Proxy method to access BigInteger's protected constructor from sibling classes.
*
* @internal
* @psalm-pure
*/
protected final function newBigInteger(string $value) : BigInteger {
return new BigInteger($value);
}
/**
* Proxy method to access BigDecimal's protected constructor from sibling classes.
*
* @internal
* @psalm-pure
*/
protected final function newBigDecimal(string $value, int $scale = 0) : BigDecimal {
return new BigDecimal($value, $scale);
}
/**
* Proxy method to access BigRational's protected constructor from sibling classes.
*
* @internal
* @psalm-pure
*/
protected final function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational {
return new BigRational($numerator, $denominator, $checkDenominator);
}
/**
* Returns the minimum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
* to an instance of the class this method is called on.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
*/
public static final function min(BigNumber|int|float|string ...$values) : static {
$min = null;
foreach ($values as $value) {
$value = static::of($value);
if ($min === null || $value->isLessThan($min)) {
$min = $value;
}
}
if ($min === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
return $min;
}
/**
* Returns the maximum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
* to an instance of the class this method is called on.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
*/
public static final function max(BigNumber|int|float|string ...$values) : static {
$max = null;
foreach ($values as $value) {
$value = static::of($value);
if ($max === null || $value->isGreaterThan($max)) {
$max = $value;
}
}
if ($max === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
return $max;
}
/**
* Returns the sum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible
* to an instance of the class this method is called on.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
*/
public static final function sum(BigNumber|int|float|string ...$values) : static {
/** @var static|null $sum */
$sum = null;
foreach ($values as $value) {
$value = static::of($value);
$sum = $sum === null ? $value : self::add($sum, $value);
}
if ($sum === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
return $sum;
}
/**
* Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
*
* @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to
* concrete classes the responsibility to perform the addition themselves or delegate it to the given number,
* depending on their ability to perform the operation. This will also require a version bump because we're
* potentially breaking custom BigNumber implementations (if any...)
*
* @psalm-pure
*/
private static function add(BigNumber $a, BigNumber $b) : BigNumber {
if ($a instanceof BigRational) {
return $a->plus($b);
}
if ($b instanceof BigRational) {
return $b->plus($a);
}
if ($a instanceof BigDecimal) {
return $a->plus($b);
}
if ($b instanceof BigDecimal) {
return $b->plus($a);
}
/** @var BigInteger $a */
return $a->plus($b);
}
/**
* Removes optional leading zeros and applies 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|null $sign, string $number) : string {
$number = \ltrim($number, '0');
if ($number === '') {
return '0';
}
return $sign === '-' ? '-' . $number : $number;
}
/**
* Checks if this number is equal to the given one.
*/
public final function isEqualTo(BigNumber|int|float|string $that) : bool {
return $this->compareTo($that) === 0;
}
/**
* Checks if this number is strictly lower than the given one.
*/
public final function isLessThan(BigNumber|int|float|string $that) : bool {
return $this->compareTo($that) < 0;
}
/**
* Checks if this number is lower than or equal to the given one.
*/
public final function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool {
return $this->compareTo($that) <= 0;
}
/**
* Checks if this number is strictly greater than the given one.
*/
public final function isGreaterThan(BigNumber|int|float|string $that) : bool {
return $this->compareTo($that) > 0;
}
/**
* Checks if this number is greater than or equal to the given one.
*/
public final function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool {
return $this->compareTo($that) >= 0;
}
/**
* Checks if this number equals zero.
*/
public final function isZero() : bool {
return $this->getSign() === 0;
}
/**
* Checks if this number is strictly negative.
*/
public final function isNegative() : bool {
return $this->getSign() < 0;
}
/**
* Checks if this number is negative or zero.
*/
public final function isNegativeOrZero() : bool {
return $this->getSign() <= 0;
}
/**
* Checks if this number is strictly positive.
*/
public final function isPositive() : bool {
return $this->getSign() > 0;
}
/**
* Checks if this number is positive or zero.
*/
public final function isPositiveOrZero() : bool {
return $this->getSign() >= 0;
}
/**
* 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.
*/
public abstract function getSign() : int;
/**
* Compares this number to the given one.
*
* @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.
*/
public abstract function compareTo(BigNumber|int|float|string $that) : int;
/**
* Converts this number to a BigInteger.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
*/
public abstract function toBigInteger() : BigInteger;
/**
* Converts this number to a BigDecimal.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
*/
public abstract function toBigDecimal() : BigDecimal;
/**
* Converts this number to a BigRational.
*/
public abstract function toBigRational() : BigRational;
/**
* Converts this number to a BigDecimal with the given scale, using rounding if necessary.
*
* @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.
*/
public abstract function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal;
/**
* Returns the exact value of this number as a native integer.
*
* If this number cannot be converted to a native integer without losing precision, an exception is thrown.
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
*
* @throws MathException If this number cannot be exactly converted to a native integer.
*/
public abstract function toInt() : int;
/**
* Returns an approximation of this number as a floating-point value.
*
* Note that this method can discard information as the precision of a floating-point value
* is inherently limited.
*
* If the number is greater than the largest representable floating point number, positive infinity is returned.
* If the number is less than the smallest representable floating point number, negative infinity is returned.
*/
public abstract function toFloat() : float;
/**
* Returns a string representation of this number.
*
* The output of this method can be parsed by the `of()` factory method;
* this will yield an object equal to this one, without any information loss.
*/
public abstract function __toString() : string;
public final function jsonSerialize() : string {
return $this->__toString();
}
}
Members
Title Sort descending | Modifiers | Object type | Summary | Overrides |
---|---|---|---|---|
BigNumber::add | private static | function | Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException. | |
BigNumber::cleanUp | private static | function | Removes optional leading zeros and applies sign. | |
BigNumber::compareTo | abstract public | function | Compares this number to the given one. | 3 |
BigNumber::from | abstract protected static | function | Overridden by subclasses to convert a BigNumber to an instance of the subclass. | 3 |
BigNumber::getSign | abstract public | function | Returns the sign of this number. | 3 |
BigNumber::isEqualTo | final public | function | Checks if this number is equal to the given one. | |
BigNumber::isGreaterThan | final public | function | Checks if this number is strictly greater than the given one. | |
BigNumber::isGreaterThanOrEqualTo | final public | function | Checks if this number is greater than or equal to the given one. | |
BigNumber::isLessThan | final public | function | Checks if this number is strictly lower than the given one. | |
BigNumber::isLessThanOrEqualTo | final public | function | Checks if this number is lower than or equal to the given one. | |
BigNumber::isNegative | final public | function | Checks if this number is strictly negative. | |
BigNumber::isNegativeOrZero | final public | function | Checks if this number is negative or zero. | |
BigNumber::isPositive | final public | function | Checks if this number is strictly positive. | |
BigNumber::isPositiveOrZero | final public | function | Checks if this number is positive or zero. | |
BigNumber::isZero | final public | function | Checks if this number equals zero. | |
BigNumber::jsonSerialize | final public | function | ||
BigNumber::max | final public static | function | Returns the maximum of the given values. | |
BigNumber::min | final public static | function | Returns the minimum of the given values. | |
BigNumber::newBigDecimal | final protected | function | Proxy method to access BigDecimal's protected constructor from sibling classes. | |
BigNumber::newBigInteger | final protected | function | Proxy method to access BigInteger's protected constructor from sibling classes. | |
BigNumber::newBigRational | final protected | function | Proxy method to access BigRational's protected constructor from sibling classes. | |
BigNumber::of | final public static | function | Creates a BigNumber of the given value. | |
BigNumber::PARSE_REGEXP_NUMERICAL | private | constant | The regular expression used to parse integer or decimal numbers. | |
BigNumber::PARSE_REGEXP_RATIONAL | private | constant | The regular expression used to parse rational numbers. | |
BigNumber::sum | final public static | function | Returns the sum of the given values. | |
BigNumber::toBigDecimal | abstract public | function | Converts this number to a BigDecimal. | 3 |
BigNumber::toBigInteger | abstract public | function | Converts this number to a BigInteger. | 3 |
BigNumber::toBigRational | abstract public | function | Converts this number to a BigRational. | 3 |
BigNumber::toFloat | abstract public | function | Returns an approximation of this number as a floating-point value. | 3 |
BigNumber::toInt | abstract public | function | Returns the exact value of this number as a native integer. | 3 |
BigNumber::toScale | abstract public | function | Converts this number to a BigDecimal with the given scale, using rounding if necessary. | 3 |
BigNumber::_of | private static | function | @psalm-pure | |
BigNumber::__toString | abstract public | function | Returns a string representation of this number. | 3 |