PluginProbe
Media Cloud Sync / 1.3.11
Media Cloud Sync v1.3.11
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / sdk / google / brick / math / src / BigNumber.php

BigNumber.php in Media Cloud Sync 1.3.11, at includes/sdk/google/brick/math/src/BigNumber.php

424 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare (strict_types=1);
4 namespace Dudlewebs\WPMCS\GCP\Brick\Math;
5
6 use Dudlewebs\WPMCS\GCP\Brick\Math\Exception\DivisionByZeroException;
7 use Dudlewebs\WPMCS\GCP\Brick\Math\Exception\MathException;
8 use Dudlewebs\WPMCS\GCP\Brick\Math\Exception\NumberFormatException;
9 use Dudlewebs\WPMCS\GCP\Brick\Math\Exception\RoundingNecessaryException;
10 use Dudlewebs\WPMCS\GCP\Override;
11 /**
12 * Common interface for arbitrary-precision rational numbers.
13 *
14 * @psalm-immutable
15 */
16 abstract class BigNumber implements \JsonSerializable
17 {
18 /**
19 * The regular expression used to parse integer or decimal numbers.
20 */
21 private const PARSE_REGEXP_NUMERICAL = '/^' . '(?<sign>[\\-\\+])?' . '(?<integral>[0-9]+)?' . '(?<point>\\.)?' . '(?<fractional>[0-9]+)?' . '(?:[eE](?<exponent>[\\-\\+]?[0-9]+))?' . '$/';
22 /**
23 * The regular expression used to parse rational numbers.
24 */
25 private const PARSE_REGEXP_RATIONAL = '/^' . '(?<sign>[\\-\\+])?' . '(?<numerator>[0-9]+)' . '\\/?' . '(?<denominator>[0-9]+)' . '$/';
26 /**
27 * Creates a BigNumber of the given value.
28 *
29 * The concrete return type is dependent on the given value, with the following rules:
30 *
31 * - BigNumber instances are returned as is
32 * - integer numbers are returned as BigInteger
33 * - floating point numbers are converted to a string then parsed as such
34 * - strings containing a `/` character are returned as BigRational
35 * - strings containing a `.` character or using an exponential notation are returned as BigDecimal
36 * - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
37 *
38 * @throws NumberFormatException If the format of the number is not valid.
39 * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
40 * @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding.
41 *
42 * @psalm-pure
43 */
44 public static final function of(BigNumber|int|float|string $value) : static
45 {
46 $value = self::_of($value);
47 if (static::class === BigNumber::class) {
48 // https://github.com/vimeo/psalm/issues/10309
49 \assert($value instanceof static);
50 return $value;
51 }
52 return static::from($value);
53 }
54 /**
55 * @throws NumberFormatException If the format of the number is not valid.
56 * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
57 *
58 * @psalm-pure
59 */
60 private static function _of(BigNumber|int|float|string $value) : BigNumber
61 {
62 if ($value instanceof BigNumber) {
63 return $value;
64 }
65 if (\is_int($value)) {
66 return new BigInteger((string) $value);
67 }
68 if (\is_float($value)) {
69 $value = (string) $value;
70 }
71 if (\str_contains($value, '/')) {
72 // Rational number
73 if (\preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, \PREG_UNMATCHED_AS_NULL) !== 1) {
74 throw NumberFormatException::invalidFormat($value);
75 }
76 $sign = $matches['sign'];
77 $numerator = $matches['numerator'];
78 $denominator = $matches['denominator'];
79 \assert($numerator !== null);
80 \assert($denominator !== null);
81 $numerator = self::cleanUp($sign, $numerator);
82 $denominator = self::cleanUp(null, $denominator);
83 if ($denominator === '0') {
84 throw DivisionByZeroException::denominatorMustNotBeZero();
85 }
86 return new BigRational(new BigInteger($numerator), new BigInteger($denominator), \false);
87 } else {
88 // Integer or decimal number
89 if (\preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, \PREG_UNMATCHED_AS_NULL) !== 1) {
90 throw NumberFormatException::invalidFormat($value);
91 }
92 $sign = $matches['sign'];
93 $point = $matches['point'];
94 $integral = $matches['integral'];
95 $fractional = $matches['fractional'];
96 $exponent = $matches['exponent'];
97 if ($integral === null && $fractional === null) {
98 throw NumberFormatException::invalidFormat($value);
99 }
100 if ($integral === null) {
101 $integral = '0';
102 }
103 if ($point !== null || $exponent !== null) {
104 $fractional = $fractional ?? '';
105 $exponent = $exponent !== null ? (int) $exponent : 0;
106 if ($exponent === \PHP_INT_MIN || $exponent === \PHP_INT_MAX) {
107 throw new NumberFormatException('Exponent too large.');
108 }
109 $unscaledValue = self::cleanUp($sign, $integral . $fractional);
110 $scale = \strlen($fractional) - $exponent;
111 if ($scale < 0) {
112 if ($unscaledValue !== '0') {
113 $unscaledValue .= \str_repeat('0', -$scale);
114 }
115 $scale = 0;
116 }
117 return new BigDecimal($unscaledValue, $scale);
118 }
119 $integral = self::cleanUp($sign, $integral);
120 return new BigInteger($integral);
121 }
122 }
123 /**
124 * Overridden by subclasses to convert a BigNumber to an instance of the subclass.
125 *
126 * @throws RoundingNecessaryException If the value cannot be converted.
127 *
128 * @psalm-pure
129 */
130 protected static abstract function from(BigNumber $number) : static;
131 /**
132 * Proxy method to access BigInteger's protected constructor from sibling classes.
133 *
134 * @internal
135 * @psalm-pure
136 */
137 protected final function newBigInteger(string $value) : BigInteger
138 {
139 return new BigInteger($value);
140 }
141 /**
142 * Proxy method to access BigDecimal's protected constructor from sibling classes.
143 *
144 * @internal
145 * @psalm-pure
146 */
147 protected final function newBigDecimal(string $value, int $scale = 0) : BigDecimal
148 {
149 return new BigDecimal($value, $scale);
150 }
151 /**
152 * Proxy method to access BigRational's protected constructor from sibling classes.
153 *
154 * @internal
155 * @psalm-pure
156 */
157 protected final function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational
158 {
159 return new BigRational($numerator, $denominator, $checkDenominator);
160 }
161 /**
162 * Returns the minimum of the given values.
163 *
164 * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
165 * to an instance of the class this method is called on.
166 *
167 * @throws \InvalidArgumentException If no values are given.
168 * @throws MathException If an argument is not valid.
169 *
170 * @psalm-pure
171 */
172 public static final function min(BigNumber|int|float|string ...$values) : static
173 {
174 $min = null;
175 foreach ($values as $value) {
176 $value = static::of($value);
177 if ($min === null || $value->isLessThan($min)) {
178 $min = $value;
179 }
180 }
181 if ($min === null) {
182 throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
183 }
184 return $min;
185 }
186 /**
187 * Returns the maximum of the given values.
188 *
189 * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
190 * to an instance of the class this method is called on.
191 *
192 * @throws \InvalidArgumentException If no values are given.
193 * @throws MathException If an argument is not valid.
194 *
195 * @psalm-pure
196 */
197 public static final function max(BigNumber|int|float|string ...$values) : static
198 {
199 $max = null;
200 foreach ($values as $value) {
201 $value = static::of($value);
202 if ($max === null || $value->isGreaterThan($max)) {
203 $max = $value;
204 }
205 }
206 if ($max === null) {
207 throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
208 }
209 return $max;
210 }
211 /**
212 * Returns the sum of the given values.
213 *
214 * @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible
215 * to an instance of the class this method is called on.
216 *
217 * @throws \InvalidArgumentException If no values are given.
218 * @throws MathException If an argument is not valid.
219 *
220 * @psalm-pure
221 */
222 public static final function sum(BigNumber|int|float|string ...$values) : static
223 {
224 /** @var static|null $sum */
225 $sum = null;
226 foreach ($values as $value) {
227 $value = static::of($value);
228 $sum = $sum === null ? $value : self::add($sum, $value);
229 }
230 if ($sum === null) {
231 throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
232 }
233 return $sum;
234 }
235 /**
236 * Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
237 *
238 * @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to
239 * concrete classes the responsibility to perform the addition themselves or delegate it to the given number,
240 * depending on their ability to perform the operation. This will also require a version bump because we're
241 * potentially breaking custom BigNumber implementations (if any...)
242 *
243 * @psalm-pure
244 */
245 private static function add(BigNumber $a, BigNumber $b) : BigNumber
246 {
247 if ($a instanceof BigRational) {
248 return $a->plus($b);
249 }
250 if ($b instanceof BigRational) {
251 return $b->plus($a);
252 }
253 if ($a instanceof BigDecimal) {
254 return $a->plus($b);
255 }
256 if ($b instanceof BigDecimal) {
257 return $b->plus($a);
258 }
259 /** @var BigInteger $a */
260 return $a->plus($b);
261 }
262 /**
263 * Removes optional leading zeros and applies sign.
264 *
265 * @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'.
266 * @param string $number The number, validated as a non-empty string of digits.
267 *
268 * @psalm-pure
269 */
270 private static function cleanUp(string|null $sign, string $number) : string
271 {
272 $number = \ltrim($number, '0');
273 if ($number === '') {
274 return '0';
275 }
276 return $sign === '-' ? '-' . $number : $number;
277 }
278 /**
279 * Checks if this number is equal to the given one.
280 */
281 public final function isEqualTo(BigNumber|int|float|string $that) : bool
282 {
283 return $this->compareTo($that) === 0;
284 }
285 /**
286 * Checks if this number is strictly lower than the given one.
287 */
288 public final function isLessThan(BigNumber|int|float|string $that) : bool
289 {
290 return $this->compareTo($that) < 0;
291 }
292 /**
293 * Checks if this number is lower than or equal to the given one.
294 */
295 public final function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool
296 {
297 return $this->compareTo($that) <= 0;
298 }
299 /**
300 * Checks if this number is strictly greater than the given one.
301 */
302 public final function isGreaterThan(BigNumber|int|float|string $that) : bool
303 {
304 return $this->compareTo($that) > 0;
305 }
306 /**
307 * Checks if this number is greater than or equal to the given one.
308 */
309 public final function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool
310 {
311 return $this->compareTo($that) >= 0;
312 }
313 /**
314 * Checks if this number equals zero.
315 */
316 public final function isZero() : bool
317 {
318 return $this->getSign() === 0;
319 }
320 /**
321 * Checks if this number is strictly negative.
322 */
323 public final function isNegative() : bool
324 {
325 return $this->getSign() < 0;
326 }
327 /**
328 * Checks if this number is negative or zero.
329 */
330 public final function isNegativeOrZero() : bool
331 {
332 return $this->getSign() <= 0;
333 }
334 /**
335 * Checks if this number is strictly positive.
336 */
337 public final function isPositive() : bool
338 {
339 return $this->getSign() > 0;
340 }
341 /**
342 * Checks if this number is positive or zero.
343 */
344 public final function isPositiveOrZero() : bool
345 {
346 return $this->getSign() >= 0;
347 }
348 /**
349 * Returns the sign of this number.
350 *
351 * @psalm-return -1|0|1
352 *
353 * @return int -1 if the number is negative, 0 if zero, 1 if positive.
354 */
355 public abstract function getSign() : int;
356 /**
357 * Compares this number to the given one.
358 *
359 * @psalm-return -1|0|1
360 *
361 * @return int -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`.
362 *
363 * @throws MathException If the number is not valid.
364 */
365 public abstract function compareTo(BigNumber|int|float|string $that) : int;
366 /**
367 * Converts this number to a BigInteger.
368 *
369 * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
370 */
371 public abstract function toBigInteger() : BigInteger;
372 /**
373 * Converts this number to a BigDecimal.
374 *
375 * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
376 */
377 public abstract function toBigDecimal() : BigDecimal;
378 /**
379 * Converts this number to a BigRational.
380 */
381 public abstract function toBigRational() : BigRational;
382 /**
383 * Converts this number to a BigDecimal with the given scale, using rounding if necessary.
384 *
385 * @param int $scale The scale of the resulting `BigDecimal`.
386 * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY.
387 *
388 * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
389 * This only applies when RoundingMode::UNNECESSARY is used.
390 */
391 public abstract function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal;
392 /**
393 * Returns the exact value of this number as a native integer.
394 *
395 * If this number cannot be converted to a native integer without losing precision, an exception is thrown.
396 * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
397 *
398 * @throws MathException If this number cannot be exactly converted to a native integer.
399 */
400 public abstract function toInt() : int;
401 /**
402 * Returns an approximation of this number as a floating-point value.
403 *
404 * Note that this method can discard information as the precision of a floating-point value
405 * is inherently limited.
406 *
407 * If the number is greater than the largest representable floating point number, positive infinity is returned.
408 * If the number is less than the smallest representable floating point number, negative infinity is returned.
409 */
410 public abstract function toFloat() : float;
411 /**
412 * Returns a string representation of this number.
413 *
414 * The output of this method can be parsed by the `of()` factory method;
415 * this will yield an object equal to this one, without any information loss.
416 */
417 public abstract function __toString() : string;
418 #[\Override]
419 public final function jsonSerialize() : string
420 {
421 return $this->__toString();
422 }
423 }
424