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