bump composer PHP version to 8.2 and update libs

This commit is contained in:
Mario Vavti
2025-11-07 20:55:45 +01:00
parent 6320506c27
commit 6c74672d40
32 changed files with 771 additions and 555 deletions

View File

@@ -23,7 +23,7 @@
"source": "https://framagit.org/hubzilla/core/"
},
"require": {
"php": ">=8.1",
"php": ">=8.2",
"ext-curl": "*",
"ext-iconv": "*",
"ext-intl": "*",

147
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "24176151470b3ff960b0acb1165bd1f2",
"content-hash": "4e8f12e610e0ac056f5f8473415e9a6c",
"packages": [
{
"name": "bakame/http-structured-fields",
@@ -155,25 +155,25 @@
},
{
"name": "brick/math",
"version": "0.13.1",
"version": "0.14.0",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04"
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04",
"url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"shasum": ""
},
"require": {
"php": "^8.1"
"php": "^8.2"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1",
"vimeo/psalm": "6.8.8"
"phpstan/phpstan": "2.1.22",
"phpunit/phpunit": "^11.5"
},
"type": "library",
"autoload": {
@@ -203,7 +203,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.13.1"
"source": "https://github.com/brick/math/tree/0.14.0"
},
"funding": [
{
@@ -211,7 +211,7 @@
"type": "github"
}
],
"time": "2025-03-29T13:50:30+00:00"
"time": "2025-08-29T12:40:03+00:00"
},
{
"name": "bshaffer/oauth2-server-php",
@@ -1397,16 +1397,16 @@
},
{
"name": "paragonie/easy-ecc",
"version": "v1.3.0",
"version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/paragonie/easy-ecc.git",
"reference": "1a72ed23a6598d482f2794c5ee6b2eb5abb364b2"
"reference": "ee588d73919f18ee27789ec1f0af2025fe74406a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/easy-ecc/zipball/1a72ed23a6598d482f2794c5ee6b2eb5abb364b2",
"reference": "1a72ed23a6598d482f2794c5ee6b2eb5abb364b2",
"url": "https://api.github.com/repos/paragonie/easy-ecc/zipball/ee588d73919f18ee27789ec1f0af2025fe74406a",
"reference": "ee588d73919f18ee27789ec1f0af2025fe74406a",
"shasum": ""
},
"require": {
@@ -1448,7 +1448,7 @@
"issues": "https://github.com/paragonie/ionizer/issues",
"source": "https://github.com/paragonie/ionizer"
},
"time": "2025-07-19T03:27:16+00:00"
"time": "2025-10-22T18:38:57+00:00"
},
{
"name": "paragonie/ecc",
@@ -3318,25 +3318,25 @@
},
{
"name": "symfony/filesystem",
"version": "v6.4.24",
"version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8"
"reference": "e9bcfd7837928ab656276fe00464092cc9e1826a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
"reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a",
"reference": "e9bcfd7837928ab656276fe00464092cc9e1826a",
"shasum": ""
},
"require": {
"php": ">=8.1",
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
"require-dev": {
"symfony/process": "^5.4|^6.4|^7.0"
"symfony/process": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -3364,7 +3364,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.4.24"
"source": "https://github.com/symfony/filesystem/tree/v7.3.6"
},
"funding": [
{
@@ -3384,7 +3384,7 @@
"type": "tidelift"
}
],
"time": "2025-07-10T08:14:14+00:00"
"time": "2025-11-05T09:52:27+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -4017,16 +4017,16 @@
},
{
"name": "nikic/php-parser",
"version": "v5.6.1",
"version": "v5.6.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2"
"reference": "3a454ca033b9e06b63282ce19562e892747449bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
"reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
"reference": "3a454ca033b9e06b63282ce19562e892747449bb",
"shasum": ""
},
"require": {
@@ -4069,9 +4069,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
},
"time": "2025-08-13T20:13:15+00:00"
"time": "2025-10-21T19:32:17+00:00"
},
{
"name": "pdepend/pdepend",
@@ -6114,34 +6114,34 @@
},
{
"name": "symfony/config",
"version": "v6.4.26",
"version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "f18dc5926cb203e125956987def795d052ee774e"
"reference": "9d18eba95655a3152ae4c1d53c6cc34eb4d4a0b7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/f18dc5926cb203e125956987def795d052ee774e",
"reference": "f18dc5926cb203e125956987def795d052ee774e",
"url": "https://api.github.com/repos/symfony/config/zipball/9d18eba95655a3152ae4c1d53c6cc34eb4d4a0b7",
"reference": "9d18eba95655a3152ae4c1d53c6cc34eb4d4a0b7",
"shasum": ""
},
"require": {
"php": ">=8.1",
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/filesystem": "^5.4|^6.0|^7.0",
"symfony/filesystem": "^7.1",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/finder": "<5.4",
"symfony/finder": "<6.4",
"symfony/service-contracts": "<2.5"
},
"require-dev": {
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
"symfony/finder": "^5.4|^6.0|^7.0",
"symfony/messenger": "^5.4|^6.0|^7.0",
"symfony/event-dispatcher": "^6.4|^7.0",
"symfony/finder": "^6.4|^7.0",
"symfony/messenger": "^6.4|^7.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/yaml": "^5.4|^6.0|^7.0"
"symfony/yaml": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -6169,7 +6169,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/config/tree/v6.4.26"
"source": "https://github.com/symfony/config/tree/v7.3.6"
},
"funding": [
{
@@ -6189,44 +6189,43 @@
"type": "tidelift"
}
],
"time": "2025-09-11T09:57:09+00:00"
"time": "2025-11-02T08:04:43+00:00"
},
{
"name": "symfony/dependency-injection",
"version": "v6.4.26",
"version": "v7.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
"reference": "5f311eaf0b321f8ec640f6bae12da43a14026898"
"reference": "98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5f311eaf0b321f8ec640f6bae12da43a14026898",
"reference": "5f311eaf0b321f8ec640f6bae12da43a14026898",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69",
"reference": "98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69",
"shasum": ""
},
"require": {
"php": ">=8.1",
"php": ">=8.2",
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/service-contracts": "^2.5|^3.0",
"symfony/service-contracts": "^3.5",
"symfony/var-exporter": "^6.4.20|^7.2.5"
},
"conflict": {
"ext-psr": "<1.1|>=2",
"symfony/config": "<6.1",
"symfony/finder": "<5.4",
"symfony/proxy-manager-bridge": "<6.3",
"symfony/yaml": "<5.4"
"symfony/config": "<6.4",
"symfony/finder": "<6.4",
"symfony/yaml": "<6.4"
},
"provide": {
"psr/container-implementation": "1.1|2.0",
"symfony/service-implementation": "1.1|2.0|3.0"
},
"require-dev": {
"symfony/config": "^6.1|^7.0",
"symfony/expression-language": "^5.4|^6.0|^7.0",
"symfony/yaml": "^5.4|^6.0|^7.0"
"symfony/config": "^6.4|^7.0",
"symfony/expression-language": "^6.4|^7.0",
"symfony/yaml": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -6254,7 +6253,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/dependency-injection/tree/v6.4.26"
"source": "https://github.com/symfony/dependency-injection/tree/v7.3.6"
},
"funding": [
{
@@ -6274,20 +6273,20 @@
"type": "tidelift"
}
],
"time": "2025-09-11T09:57:09+00:00"
"time": "2025-10-31T10:11:11+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.6.0",
"version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
"reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
"shasum": ""
},
"require": {
@@ -6341,7 +6340,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
"source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
},
"funding": [
{
@@ -6352,35 +6351,39 @@
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-04-25T09:37:31+00:00"
"time": "2025-07-15T11:30:57+00:00"
},
{
"name": "symfony/var-exporter",
"version": "v6.4.26",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
"reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc"
"reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/466fcac5fa2e871f83d31173f80e9c2684743bfc",
"reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc",
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4",
"reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4",
"shasum": ""
},
"require": {
"php": ">=8.1",
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3"
},
"require-dev": {
"symfony/property-access": "^6.4|^7.0",
"symfony/serializer": "^6.4|^7.0",
"symfony/var-dumper": "^5.4|^6.0|^7.0"
"symfony/var-dumper": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -6418,7 +6421,7 @@
"serialize"
],
"support": {
"source": "https://github.com/symfony/var-exporter/tree/v6.4.26"
"source": "https://github.com/symfony/var-exporter/tree/v7.3.4"
},
"funding": [
{
@@ -6438,7 +6441,7 @@
"type": "tidelift"
}
],
"time": "2025-09-11T09:57:09+00:00"
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "theseer/tokenizer",
@@ -6497,7 +6500,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=8.1",
"php": ">=8.2",
"ext-curl": "*",
"ext-iconv": "*",
"ext-intl": "*",

View File

@@ -2,6 +2,23 @@
All notable changes to this project will be documented in this file.
## [0.14.0](https://github.com/brick/math/releases/tag/0.14.0) - 2025-08-29
**New features**
- New methods: `BigInteger::clamp()` and `BigDecimal::clamp()` (#96 by @JesterIruka)
**Improvements**
- All pure methods in `BigNumber` classes are now marked as `@pure` for better static analysis
💥 **Breaking changes**
- Minimum PHP version is now 8.2
- `BigNumber` classes are now `readonly`
- `BigNumber` is now marked as sealed: it must not be extended outside of this package
- Exception classes are now `final`
## [0.13.1](https://github.com/brick/math/releases/tag/0.13.1) - 2025-03-29
**Improvements**

View File

@@ -19,12 +19,12 @@
],
"license": "MIT",
"require": {
"php": "^8.1"
"php": "^8.2"
},
"require-dev": {
"phpunit/phpunit": "^10.1",
"phpunit/phpunit": "^11.5",
"php-coveralls/php-coveralls": "^2.2",
"vimeo/psalm": "6.8.8"
"phpstan/phpstan": "2.1.22"
},
"autoload": {
"psr-4": {

14
vendor/brick/math/phpstan.neon vendored Normal file
View File

@@ -0,0 +1,14 @@
parameters:
level: 10
checkUninitializedProperties: true
paths:
- src
ignoreErrors:
- '~Impure call to function array_shift\(\) in pure method~'
- '~Possibly impure call to function assert\(\) in pure method~'
- '~Possibly impure call to function hex2bin\(\) in pure method~'
- '~Possibly impure call to function preg_match\(\) in pure method~'
- '~Impure static variable in pure method Brick\\Math\\Big(Integer|Decimal|Rational)::(zero|one|ten)\(\)~'
-
message: '~Parameter #\d \$\S+ of function bc\S+ expects numeric-string, string given~'
path: src/Internal/Calculator/BcMathCalculator.php

View File

@@ -1,70 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.8.8@1361cd33008feb3ae2b4a93f1860e14e538ec8c2">
<file src="src/BigInteger.php">
<FalsableReturnStatement>
<code><![CDATA[\hex2bin($hex)]]></code>
</FalsableReturnStatement>
<InvalidFalsableReturnType>
<code><![CDATA[string]]></code>
</InvalidFalsableReturnType>
</file>
<file src="src/Exception/DivisionByZeroException.php">
<ClassMustBeFinal>
<code><![CDATA[DivisionByZeroException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Exception/IntegerOverflowException.php">
<ClassMustBeFinal>
<code><![CDATA[IntegerOverflowException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Exception/NegativeNumberException.php">
<ClassMustBeFinal>
<code><![CDATA[NegativeNumberException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Exception/NumberFormatException.php">
<ClassMustBeFinal>
<code><![CDATA[NumberFormatException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Exception/RoundingNecessaryException.php">
<ClassMustBeFinal>
<code><![CDATA[RoundingNecessaryException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Internal/Calculator/BcMathCalculator.php">
<ClassMustBeFinal>
<code><![CDATA[BcMathCalculator]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Internal/Calculator/GmpCalculator.php">
<ClassMustBeFinal>
<code><![CDATA[GmpCalculator]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Internal/Calculator/NativeCalculator.php">
<ClassMustBeFinal>
<code><![CDATA[NativeCalculator]]></code>
</ClassMustBeFinal>
<InvalidOperand>
<code><![CDATA[$a * $b]]></code>
<code><![CDATA[$a * 1]]></code>
<code><![CDATA[$a + $b]]></code>
<code><![CDATA[$b * 1]]></code>
<code><![CDATA[$b * 1]]></code>
<code><![CDATA[$blockA * $blockB + $carry]]></code>
<code><![CDATA[$blockA + $blockB]]></code>
<code><![CDATA[$blockA + $blockB + $carry]]></code>
<code><![CDATA[$blockA - $blockB]]></code>
<code><![CDATA[$blockA - $blockB - $carry]]></code>
<code><![CDATA[$carry]]></code>
<code><![CDATA[$mul % $complement]]></code>
<code><![CDATA[$mul - $value]]></code>
<code><![CDATA[$nb - 1]]></code>
<code><![CDATA[$sum += $complement]]></code>
<code><![CDATA[($mul - $value) / $complement]]></code>
<code><![CDATA[($nb - 1) * 10]]></code>
</InvalidOperand>
</file>
</files>

View File

@@ -8,14 +8,13 @@ use Brick\Math\Exception\DivisionByZeroException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NegativeNumberException;
use Brick\Math\Internal\Calculator;
use Brick\Math\Internal\CalculatorRegistry;
use Override;
/**
* Immutable, arbitrary-precision signed decimal numbers.
*
* @psalm-immutable
*/
final class BigDecimal extends BigNumber
final readonly class BigDecimal extends BigNumber
{
/**
* The unscaled value of this decimal number.
@@ -24,20 +23,22 @@ final class BigDecimal extends BigNumber
* No leading zero must be present.
* No leading minus sign must be present if the value is 0.
*/
private readonly string $value;
private string $value;
/**
* The scale (number of digits after the decimal point) of this decimal number.
*
* This must be zero or more.
*/
private readonly int $scale;
private int $scale;
/**
* Protected constructor. Use a factory method to obtain an instance.
*
* @param string $value The unscaled value, validated.
* @param int $scale The scale, validated.
*
* @pure
*/
protected function __construct(string $value, int $scale = 0)
{
@@ -45,9 +46,6 @@ final class BigDecimal extends BigNumber
$this->scale = $scale;
}
/**
* @psalm-pure
*/
#[Override]
protected static function from(BigNumber $number): static
{
@@ -63,7 +61,7 @@ final class BigDecimal extends BigNumber
* @param int $scale The scale of the number. If negative, the scale will be set to zero
* and the unscaled value will be adjusted accordingly.
*
* @psalm-pure
* @pure
*/
public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0) : BigDecimal
{
@@ -82,14 +80,11 @@ final class BigDecimal extends BigNumber
/**
* Returns a BigDecimal representing zero, with a scale of zero.
*
* @psalm-pure
* @pure
*/
public static function zero() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $zero
*/
/** @var BigDecimal|null $zero */
static $zero;
if ($zero === null) {
@@ -102,14 +97,11 @@ final class BigDecimal extends BigNumber
/**
* Returns a BigDecimal representing one, with a scale of zero.
*
* @psalm-pure
* @pure
*/
public static function one() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $one
*/
/** @var BigDecimal|null $one */
static $one;
if ($one === null) {
@@ -122,14 +114,11 @@ final class BigDecimal extends BigNumber
/**
* Returns a BigDecimal representing ten, with a scale of zero.
*
* @psalm-pure
* @pure
*/
public static function ten() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $ten
*/
/** @var BigDecimal|null $ten */
static $ten;
if ($ten === null) {
@@ -147,6 +136,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
*
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
*
* @pure
*/
public function plus(BigNumber|int|float|string $that) : BigDecimal
{
@@ -162,7 +153,7 @@ final class BigDecimal extends BigNumber
[$a, $b] = $this->scaleValues($this, $that);
$value = Calculator::get()->add($a, $b);
$value = CalculatorRegistry::get()->add($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($value, $scale);
@@ -176,6 +167,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
*
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
*
* @pure
*/
public function minus(BigNumber|int|float|string $that) : BigDecimal
{
@@ -187,7 +180,7 @@ final class BigDecimal extends BigNumber
[$a, $b] = $this->scaleValues($this, $that);
$value = Calculator::get()->sub($a, $b);
$value = CalculatorRegistry::get()->sub($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($value, $scale);
@@ -201,6 +194,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
*
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
*
* @pure
*/
public function multipliedBy(BigNumber|int|float|string $that) : BigDecimal
{
@@ -214,7 +209,7 @@ final class BigDecimal extends BigNumber
return $that;
}
$value = Calculator::get()->mul($this->value, $that->value);
$value = CalculatorRegistry::get()->mul($this->value, $that->value);
$scale = $this->scale + $that->scale;
return new BigDecimal($value, $scale);
@@ -229,6 +224,8 @@ final class BigDecimal extends BigNumber
*
* @throws \InvalidArgumentException If the scale or rounding mode is invalid.
* @throws MathException If the number is invalid, is zero, or rounding was necessary.
*
* @pure
*/
public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
{
@@ -251,7 +248,7 @@ final class BigDecimal extends BigNumber
$p = $this->valueWithMinScale($that->scale + $scale);
$q = $that->valueWithMinScale($this->scale - $scale);
$result = Calculator::get()->divRound($p, $q, $roundingMode);
$result = CalculatorRegistry::get()->divRound($p, $q, $roundingMode);
return new BigDecimal($result, $scale);
}
@@ -265,6 +262,8 @@ final class BigDecimal extends BigNumber
*
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
* or the result yields an infinite number of digits.
*
* @pure
*/
public function exactlyDividedBy(BigNumber|int|float|string $that) : BigDecimal
{
@@ -279,7 +278,7 @@ final class BigDecimal extends BigNumber
$d = \rtrim($b, '0');
$scale = \strlen($b) - \strlen($d);
$calculator = Calculator::get();
$calculator = CalculatorRegistry::get();
foreach ([5, 2] as $prime) {
for (;;) {
@@ -297,12 +296,36 @@ final class BigDecimal extends BigNumber
return $this->dividedBy($that, $scale)->stripTrailingZeros();
}
/**
* Limits (clamps) this number between the given minimum and maximum values.
*
* If the number is lower than $min, returns a copy of $min.
* If the number is greater than $max, returns a copy of $max.
* Otherwise, returns this number unchanged.
*
* @param BigNumber|int|float|string $min The minimum. Must be convertible to a BigDecimal.
* @param BigNumber|int|float|string $max The maximum. Must be convertible to a BigDecimal.
*
* @throws MathException If min/max are not convertible to a BigDecimal.
*/
public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max) : BigDecimal
{
if ($this->isLessThan($min)) {
return BigDecimal::of($min);
} elseif ($this->isGreaterThan($max)) {
return BigDecimal::of($max);
}
return $this;
}
/**
* Returns this number exponentiated to the given value.
*
* The result has a scale of `$this->scale * $exponent`.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*
* @pure
*/
public function power(int $exponent) : BigDecimal
{
@@ -322,7 +345,7 @@ final class BigDecimal extends BigNumber
));
}
return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent);
return new BigDecimal(CalculatorRegistry::get()->pow($this->value, $exponent), $this->scale * $exponent);
}
/**
@@ -333,6 +356,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*
* @pure
*/
public function quotient(BigNumber|int|float|string $that) : BigDecimal
{
@@ -345,7 +370,7 @@ final class BigDecimal extends BigNumber
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
$quotient = Calculator::get()->divQ($p, $q);
$quotient = CalculatorRegistry::get()->divQ($p, $q);
return new BigDecimal($quotient, 0);
}
@@ -358,6 +383,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*
* @pure
*/
public function remainder(BigNumber|int|float|string $that) : BigDecimal
{
@@ -370,7 +397,7 @@ final class BigDecimal extends BigNumber
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
$remainder = Calculator::get()->divR($p, $q);
$remainder = CalculatorRegistry::get()->divR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
@@ -384,11 +411,11 @@ final class BigDecimal extends BigNumber
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @return BigDecimal[] An array containing the quotient and the remainder.
*
* @psalm-return array{BigDecimal, BigDecimal}
* @return array{BigDecimal, BigDecimal} An array containing the quotient and the remainder.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*
* @pure
*/
public function quotientAndRemainder(BigNumber|int|float|string $that) : array
{
@@ -401,7 +428,7 @@ final class BigDecimal extends BigNumber
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
[$quotient, $remainder] = Calculator::get()->divQR($p, $q);
[$quotient, $remainder] = CalculatorRegistry::get()->divQR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
@@ -416,6 +443,8 @@ final class BigDecimal extends BigNumber
*
* @throws \InvalidArgumentException If the scale is negative.
* @throws NegativeNumberException If this number is negative.
*
* @pure
*/
public function sqrt(int $scale) : BigDecimal
{
@@ -447,13 +476,15 @@ final class BigDecimal extends BigNumber
$value = \substr($value, 0, $addDigits);
}
$value = Calculator::get()->sqrt($value);
$value = CalculatorRegistry::get()->sqrt($value);
return new BigDecimal($value, $scale);
}
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
*
* @pure
*/
public function withPointMovedLeft(int $n) : BigDecimal
{
@@ -470,6 +501,8 @@ final class BigDecimal extends BigNumber
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
*
* @pure
*/
public function withPointMovedRight(int $n) : BigDecimal
{
@@ -496,6 +529,8 @@ final class BigDecimal extends BigNumber
/**
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
*
* @pure
*/
public function stripTrailingZeros() : BigDecimal
{
@@ -527,6 +562,8 @@ final class BigDecimal extends BigNumber
/**
* Returns the absolute value of this number.
*
* @pure
*/
public function abs() : BigDecimal
{
@@ -535,10 +572,12 @@ final class BigDecimal extends BigNumber
/**
* Returns the negated value of this number.
*
* @pure
*/
public function negated() : BigDecimal
{
return new BigDecimal(Calculator::get()->neg($this->value), $this->scale);
return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale);
}
#[Override]
@@ -553,7 +592,7 @@ final class BigDecimal extends BigNumber
if ($that instanceof BigDecimal) {
[$a, $b] = $this->scaleValues($this, $that);
return Calculator::get()->cmp($a, $b);
return CalculatorRegistry::get()->cmp($a, $b);
}
return - $that->compareTo($this);
@@ -565,11 +604,17 @@ final class BigDecimal extends BigNumber
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
}
/**
* @pure
*/
public function getUnscaledValue() : BigInteger
{
return self::newBigInteger($this->value);
}
/**
* @pure
*/
public function getScale() : int
{
return $this->scale;
@@ -588,6 +633,8 @@ final class BigDecimal extends BigNumber
* 123.456 => 6
* 0.00123 => 3
* 0.0012300 => 5
*
* @pure
*/
public function getPrecision(): int
{
@@ -606,6 +653,8 @@ final class BigDecimal extends BigNumber
* Returns a string representing the integral part of this decimal number.
*
* Example: `-123.456` => `-123`.
*
* @pure
*/
public function getIntegralPart() : string
{
@@ -624,6 +673,8 @@ final class BigDecimal extends BigNumber
* If the scale is zero, an empty string is returned.
*
* Examples: `-123.456` => '456', `123` => ''.
*
* @pure
*/
public function getFractionalPart() : string
{
@@ -638,6 +689,8 @@ final class BigDecimal extends BigNumber
/**
* Returns whether this decimal number has a non-zero fractional part.
*
* @pure
*/
public function hasNonZeroFractionalPart() : bool
{
@@ -702,7 +755,7 @@ final class BigDecimal extends BigNumber
$value = $this->getUnscaledValueWithLeadingZeros();
/** @var numeric-string */
/** @phpstan-ignore return.type */
return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale);
}
@@ -722,7 +775,6 @@ final class BigDecimal extends BigNumber
* This method is only here to allow unserializing the object and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param array{value: string, scale: int} $data
*
@@ -730,10 +782,12 @@ final class BigDecimal extends BigNumber
*/
public function __unserialize(array $data): void
{
/** @phpstan-ignore isset.initializedProperty */
if (isset($this->value)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
/** @phpstan-ignore deadCode.unreachable */
$this->value = $data['value'];
$this->scale = $data['scale'];
}
@@ -742,6 +796,8 @@ final class BigDecimal extends BigNumber
* Puts the internal values of the given decimal numbers on the same scale.
*
* @return array{string, string} The scaled integer values of $x and $y.
*
* @pure
*/
private function scaleValues(BigDecimal $x, BigDecimal $y) : array
{
@@ -757,6 +813,9 @@ final class BigDecimal extends BigNumber
return [$a, $b];
}
/**
* @pure
*/
private function valueWithMinScale(int $scale) : string
{
$value = $this->value;
@@ -770,6 +829,8 @@ final class BigDecimal extends BigNumber
/**
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
*
* @pure
*/
private function getUnscaledValueWithLeadingZeros() : string
{

View File

@@ -10,6 +10,7 @@ use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NegativeNumberException;
use Brick\Math\Exception\NumberFormatException;
use Brick\Math\Internal\Calculator;
use Brick\Math\Internal\CalculatorRegistry;
use Override;
/**
@@ -17,10 +18,8 @@ use Override;
*
* All methods accepting a number as a parameter accept either a BigInteger instance,
* an integer, or a string representing an arbitrary size integer.
*
* @psalm-immutable
*/
final class BigInteger extends BigNumber
final readonly class BigInteger extends BigNumber
{
/**
* The value, as a string of digits with optional leading minus sign.
@@ -28,21 +27,20 @@ final class BigInteger extends BigNumber
* No leading zeros must be present.
* No leading minus sign must be present if the number is zero.
*/
private readonly string $value;
private string $value;
/**
* Protected constructor. Use a factory method to obtain an instance.
*
* @param string $value A string of digits, with optional leading minus sign.
*
* @pure
*/
protected function __construct(string $value)
{
$this->value = $value;
}
/**
* @psalm-pure
*/
#[Override]
protected static function from(BigNumber $number): static
{
@@ -66,7 +64,7 @@ final class BigInteger extends BigNumber
* @throws NumberFormatException If the number is empty, or contains invalid chars for the given base.
* @throws \InvalidArgumentException If the base is out of range.
*
* @psalm-pure
* @pure
*/
public static function fromBase(string $number, int $base) : BigInteger
{
@@ -115,7 +113,7 @@ final class BigInteger extends BigNumber
return new BigInteger($sign . $number);
}
$result = Calculator::get()->fromBase($number, $base);
$result = CalculatorRegistry::get()->fromBase($number, $base);
return new BigInteger($sign . $result);
}
@@ -131,7 +129,7 @@ final class BigInteger extends BigNumber
* @throws NumberFormatException If the given number is empty or contains invalid chars for the given alphabet.
* @throws \InvalidArgumentException If the alphabet does not contain at least 2 chars.
*
* @psalm-pure
* @pure
*/
public static function fromArbitraryBase(string $number, string $alphabet) : BigInteger
{
@@ -151,7 +149,7 @@ final class BigInteger extends BigNumber
throw NumberFormatException::charNotInAlphabet($matches[0]);
}
$number = Calculator::get()->fromArbitraryBase($number, $alphabet, $base);
$number = CalculatorRegistry::get()->fromArbitraryBase($number, $alphabet, $base);
return new BigInteger($number);
}
@@ -172,6 +170,8 @@ final class BigInteger extends BigNumber
* sign bit.
*
* @throws NumberFormatException If the string is empty.
*
* @pure
*/
public static function fromBytes(string $value, bool $signed = true) : BigInteger
{
@@ -203,12 +203,10 @@ final class BigInteger extends BigNumber
*
* Using the default random bytes generator, this method is suitable for cryptographic use.
*
* @psalm-param (callable(int): string)|null $randomBytesGenerator
*
* @param int $numBits The number of bits.
* @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer, and returns a
* string of random bytes of the given length. Defaults to the
* `random_bytes()` function.
* @param int $numBits The number of bits.
* @param (callable(int): string)|null $randomBytesGenerator A function that accepts a number of bytes, and returns
* a string of random bytes of the given length. Defaults
* to the `random_bytes()` function.
*
* @throws \InvalidArgumentException If $numBits is negative.
*/
@@ -243,13 +241,11 @@ final class BigInteger extends BigNumber
*
* Using the default random bytes generator, this method is suitable for cryptographic use.
*
* @psalm-param (callable(int): string)|null $randomBytesGenerator
*
* @param BigNumber|int|float|string $min The lower bound. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $max The upper bound. Must be convertible to a BigInteger.
* @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer,
* and returns a string of random bytes of the given length.
* Defaults to the `random_bytes()` function.
* @param BigNumber|int|float|string $min The lower bound. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $max The upper bound. Must be convertible to a BigInteger.
* @param (callable(int): string)|null $randomBytesGenerator A function that accepts a number of bytes, and returns
* a string of random bytes of the given length. Defaults
* to the `random_bytes()` function.
*
* @throws MathException If one of the parameters cannot be converted to a BigInteger,
* or `$min` is greater than `$max`.
@@ -284,14 +280,11 @@ final class BigInteger extends BigNumber
/**
* Returns a BigInteger representing zero.
*
* @psalm-pure
* @pure
*/
public static function zero() : BigInteger
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigInteger|null $zero
*/
/** @var BigInteger|null $zero */
static $zero;
if ($zero === null) {
@@ -304,14 +297,11 @@ final class BigInteger extends BigNumber
/**
* Returns a BigInteger representing one.
*
* @psalm-pure
* @pure
*/
public static function one() : BigInteger
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigInteger|null $one
*/
/** @var BigInteger|null $one */
static $one;
if ($one === null) {
@@ -324,14 +314,11 @@ final class BigInteger extends BigNumber
/**
* Returns a BigInteger representing ten.
*
* @psalm-pure
* @pure
*/
public static function ten() : BigInteger
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigInteger|null $ten
*/
/** @var BigInteger|null $ten */
static $ten;
if ($ten === null) {
@@ -341,6 +328,9 @@ final class BigInteger extends BigNumber
return $ten;
}
/**
* @pure
*/
public static function gcdMultiple(BigInteger $a, BigInteger ...$n): BigInteger
{
$result = $a;
@@ -362,6 +352,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigInteger.
*
* @throws MathException If the number is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
public function plus(BigNumber|int|float|string $that) : BigInteger
{
@@ -375,7 +367,7 @@ final class BigInteger extends BigNumber
return $that;
}
$value = Calculator::get()->add($this->value, $that->value);
$value = CalculatorRegistry::get()->add($this->value, $that->value);
return new BigInteger($value);
}
@@ -386,6 +378,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigInteger.
*
* @throws MathException If the number is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
public function minus(BigNumber|int|float|string $that) : BigInteger
{
@@ -395,7 +389,7 @@ final class BigInteger extends BigNumber
return $this;
}
$value = Calculator::get()->sub($this->value, $that->value);
$value = CalculatorRegistry::get()->sub($this->value, $that->value);
return new BigInteger($value);
}
@@ -406,6 +400,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigInteger.
*
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigInteger.
*
* @pure
*/
public function multipliedBy(BigNumber|int|float|string $that) : BigInteger
{
@@ -419,7 +415,7 @@ final class BigInteger extends BigNumber
return $that;
}
$value = Calculator::get()->mul($this->value, $that->value);
$value = CalculatorRegistry::get()->mul($this->value, $that->value);
return new BigInteger($value);
}
@@ -432,6 +428,8 @@ final class BigInteger extends BigNumber
*
* @throws MathException If the divisor is not a valid number, is not convertible to a BigInteger, is zero,
* or RoundingMode::UNNECESSARY is used and the remainder is not zero.
*
* @pure
*/
public function dividedBy(BigNumber|int|float|string $that, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigInteger
{
@@ -445,15 +443,40 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::divisionByZero();
}
$result = Calculator::get()->divRound($this->value, $that->value, $roundingMode);
$result = CalculatorRegistry::get()->divRound($this->value, $that->value, $roundingMode);
return new BigInteger($result);
}
/**
* Limits (clamps) this number between the given minimum and maximum values.
*
* If the number is lower than $min, returns a copy of $min.
* If the number is greater than $max, returns a copy of $max.
* Otherwise, returns this number unchanged.
*
* @param BigNumber|int|float|string $min The minimum. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $max The maximum. Must be convertible to a BigInteger.
*
* @throws MathException If min/max are not convertible to a BigInteger.
*/
public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max) : BigInteger
{
if ($this->isLessThan($min)) {
return BigInteger::of($min);
} elseif ($this->isGreaterThan($max)) {
return BigInteger::of($max);
}
return $this;
}
/**
* Returns this number exponentiated to the given value.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*
* @pure
*/
public function power(int $exponent) : BigInteger
{
@@ -473,7 +496,7 @@ final class BigInteger extends BigNumber
));
}
return new BigInteger(Calculator::get()->pow($this->value, $exponent));
return new BigInteger(CalculatorRegistry::get()->pow($this->value, $exponent));
}
/**
@@ -482,6 +505,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
public function quotient(BigNumber|int|float|string $that) : BigInteger
{
@@ -495,7 +520,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::divisionByZero();
}
$quotient = Calculator::get()->divQ($this->value, $that->value);
$quotient = CalculatorRegistry::get()->divQ($this->value, $that->value);
return new BigInteger($quotient);
}
@@ -508,6 +533,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
public function remainder(BigNumber|int|float|string $that) : BigInteger
{
@@ -521,7 +548,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::divisionByZero();
}
$remainder = Calculator::get()->divR($this->value, $that->value);
$remainder = CalculatorRegistry::get()->divR($this->value, $that->value);
return new BigInteger($remainder);
}
@@ -531,11 +558,11 @@ final class BigInteger extends BigNumber
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @return BigInteger[] An array containing the quotient and the remainder.
*
* @psalm-return array{BigInteger, BigInteger}
* @return array{BigInteger, BigInteger} An array containing the quotient and the remainder.
*
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
public function quotientAndRemainder(BigNumber|int|float|string $that) : array
{
@@ -545,7 +572,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::divisionByZero();
}
[$quotient, $remainder] = Calculator::get()->divQR($this->value, $that->value);
[$quotient, $remainder] = CalculatorRegistry::get()->divQR($this->value, $that->value);
return [
new BigInteger($quotient),
@@ -564,6 +591,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
public function mod(BigNumber|int|float|string $that) : BigInteger
{
@@ -573,7 +602,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::modulusMustNotBeZero();
}
$value = Calculator::get()->mod($this->value, $that->value);
$value = CalculatorRegistry::get()->mod($this->value, $that->value);
return new BigInteger($value);
}
@@ -585,6 +614,8 @@ final class BigInteger extends BigNumber
* @throws NegativeNumberException If $m is negative.
* @throws MathException If this BigInteger has no multiplicative inverse mod m (that is, this BigInteger
* is not relatively prime to m).
*
* @pure
*/
public function modInverse(BigInteger $m) : BigInteger
{
@@ -600,7 +631,7 @@ final class BigInteger extends BigNumber
return BigInteger::zero();
}
$value = Calculator::get()->modInverse($this->value, $m->value);
$value = CalculatorRegistry::get()->modInverse($this->value, $m->value);
if ($value === null) {
throw new MathException('Unable to compute the modInverse for the given modulus.');
@@ -619,6 +650,8 @@ final class BigInteger extends BigNumber
*
* @throws NegativeNumberException If any of the operands is negative.
* @throws DivisionByZeroException If the modulus is zero.
*
* @pure
*/
public function modPow(BigNumber|int|float|string $exp, BigNumber|int|float|string $mod) : BigInteger
{
@@ -633,7 +666,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::modulusMustNotBeZero();
}
$result = Calculator::get()->modPow($this->value, $exp->value, $mod->value);
$result = CalculatorRegistry::get()->modPow($this->value, $exp->value, $mod->value);
return new BigInteger($result);
}
@@ -644,6 +677,8 @@ final class BigInteger extends BigNumber
* The GCD is always positive, unless both operands are zero, in which case it is zero.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
*
* @pure
*/
public function gcd(BigNumber|int|float|string $that) : BigInteger
{
@@ -657,7 +692,7 @@ final class BigInteger extends BigNumber
return $that;
}
$value = Calculator::get()->gcd($this->value, $that->value);
$value = CalculatorRegistry::get()->gcd($this->value, $that->value);
return new BigInteger($value);
}
@@ -668,6 +703,8 @@ final class BigInteger extends BigNumber
* The result is the largest x such that x² ≤ n.
*
* @throws NegativeNumberException If this number is negative.
*
* @pure
*/
public function sqrt() : BigInteger
{
@@ -675,13 +712,15 @@ final class BigInteger extends BigNumber
throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
}
$value = Calculator::get()->sqrt($this->value);
$value = CalculatorRegistry::get()->sqrt($this->value);
return new BigInteger($value);
}
/**
* Returns the absolute value of this number.
*
* @pure
*/
public function abs() : BigInteger
{
@@ -690,10 +729,12 @@ final class BigInteger extends BigNumber
/**
* Returns the inverse of this number.
*
* @pure
*/
public function negated() : BigInteger
{
return new BigInteger(Calculator::get()->neg($this->value));
return new BigInteger(CalculatorRegistry::get()->neg($this->value));
}
/**
@@ -702,12 +743,14 @@ final class BigInteger extends BigNumber
* This method returns a negative BigInteger if and only if both operands are negative.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
*
* @pure
*/
public function and(BigNumber|int|float|string $that) : BigInteger
{
$that = BigInteger::of($that);
return new BigInteger(Calculator::get()->and($this->value, $that->value));
return new BigInteger(CalculatorRegistry::get()->and($this->value, $that->value));
}
/**
@@ -716,12 +759,14 @@ final class BigInteger extends BigNumber
* This method returns a negative BigInteger if and only if either of the operands is negative.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
*
* @pure
*/
public function or(BigNumber|int|float|string $that) : BigInteger
{
$that = BigInteger::of($that);
return new BigInteger(Calculator::get()->or($this->value, $that->value));
return new BigInteger(CalculatorRegistry::get()->or($this->value, $that->value));
}
/**
@@ -730,16 +775,20 @@ final class BigInteger extends BigNumber
* This method returns a negative BigInteger if and only if exactly one of the operands is negative.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
*
* @pure
*/
public function xor(BigNumber|int|float|string $that) : BigInteger
{
$that = BigInteger::of($that);
return new BigInteger(Calculator::get()->xor($this->value, $that->value));
return new BigInteger(CalculatorRegistry::get()->xor($this->value, $that->value));
}
/**
* Returns the bitwise-not of this BigInteger.
*
* @pure
*/
public function not() : BigInteger
{
@@ -748,6 +797,8 @@ final class BigInteger extends BigNumber
/**
* Returns the integer left shifted by a given number of bits.
*
* @pure
*/
public function shiftedLeft(int $distance) : BigInteger
{
@@ -764,6 +815,8 @@ final class BigInteger extends BigNumber
/**
* Returns the integer right shifted by a given number of bits.
*
* @pure
*/
public function shiftedRight(int $distance) : BigInteger
{
@@ -789,6 +842,8 @@ final class BigInteger extends BigNumber
*
* For positive BigIntegers, this is equivalent to the number of bits in the ordinary binary representation.
* Computes (ceil(log2(this < 0 ? -this : this+1))).
*
* @pure
*/
public function getBitLength() : int
{
@@ -807,6 +862,8 @@ final class BigInteger extends BigNumber
* Returns the index of the rightmost (lowest-order) one bit in this BigInteger.
*
* Returns -1 if this BigInteger contains no one bits.
*
* @pure
*/
public function getLowestSetBit() : int
{
@@ -826,6 +883,8 @@ final class BigInteger extends BigNumber
/**
* Returns whether this number is even.
*
* @pure
*/
public function isEven() : bool
{
@@ -834,6 +893,8 @@ final class BigInteger extends BigNumber
/**
* Returns whether this number is odd.
*
* @pure
*/
public function isOdd() : bool
{
@@ -848,6 +909,8 @@ final class BigInteger extends BigNumber
* @param int $n The bit to test, 0-based.
*
* @throws \InvalidArgumentException If the bit to test is negative.
*
* @pure
*/
public function testBit(int $n) : bool
{
@@ -864,7 +927,7 @@ final class BigInteger extends BigNumber
$that = BigNumber::of($that);
if ($that instanceof BigInteger) {
return Calculator::get()->cmp($this->value, $that->value);
return CalculatorRegistry::get()->cmp($this->value, $that->value);
}
return - $that->compareTo($this);
@@ -924,6 +987,8 @@ final class BigInteger extends BigNumber
* The output will always be lowercase for bases greater than 10.
*
* @throws \InvalidArgumentException If the base is out of range.
*
* @pure
*/
public function toBase(int $base) : string
{
@@ -935,7 +1000,7 @@ final class BigInteger extends BigNumber
throw new \InvalidArgumentException(\sprintf('Base %d is out of range [2, 36]', $base));
}
return Calculator::get()->toBase($this->value, $base);
return CalculatorRegistry::get()->toBase($this->value, $base);
}
/**
@@ -948,6 +1013,8 @@ final class BigInteger extends BigNumber
*
* @throws NegativeNumberException If this number is negative.
* @throws \InvalidArgumentException If the given alphabet does not contain at least 2 chars.
*
* @pure
*/
public function toArbitraryBase(string $alphabet) : string
{
@@ -961,7 +1028,7 @@ final class BigInteger extends BigNumber
throw new NegativeNumberException(__FUNCTION__ . '() does not support negative numbers.');
}
return Calculator::get()->toArbitraryBase($this->value, $alphabet, $base);
return CalculatorRegistry::get()->toArbitraryBase($this->value, $alphabet, $base);
}
/**
@@ -981,6 +1048,8 @@ final class BigInteger extends BigNumber
* @param bool $signed Whether to output a signed number in two's-complement representation with a leading sign bit.
*
* @throws NegativeNumberException If $signed is false, and the number is negative.
*
* @pure
*/
public function toBytes(bool $signed = true) : string
{
@@ -1020,7 +1089,10 @@ final class BigInteger extends BigNumber
}
}
return \hex2bin($hex);
$result = \hex2bin($hex);
assert($result !== false);
return $result;
}
/**
@@ -1049,7 +1121,6 @@ final class BigInteger extends BigNumber
* This method is only here to allow unserializing the object and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param array{value: string} $data
*
@@ -1057,10 +1128,12 @@ final class BigInteger extends BigNumber
*/
public function __unserialize(array $data): void
{
/** @phpstan-ignore isset.initializedProperty */
if (isset($this->value)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
/** @phpstan-ignore deadCode.unreachable */
$this->value = $data['value'];
}
}

View File

@@ -11,11 +11,14 @@ use Brick\Math\Exception\RoundingNecessaryException;
use Override;
/**
* Common interface for arbitrary-precision rational numbers.
* Base class for arbitrary-precision numbers.
*
* @psalm-immutable
* This class is sealed: it is part of the public API but should not be subclassed in userland.
* Protected methods may change in any version.
*
* @phpstan-sealed BigInteger|BigDecimal|BigRational
*/
abstract class BigNumber implements \JsonSerializable
abstract readonly class BigNumber implements \JsonSerializable, \Stringable
{
/**
* The regular expression used to parse integer or decimal numbers.
@@ -43,7 +46,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Creates a BigNumber of the given value.
*
* The concrete return type is dependent on the given value, with the following rules:
* When of() is called on BigNumber, 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
@@ -52,18 +56,20 @@ abstract class BigNumber implements \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
*
* When of() is called on BigInteger, BigDecimal, or BigRational, the resulting number is converted to an instance
* of the subclass when possible; otherwise a RoundingNecessaryException exception is thrown.
*
* @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
* @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;
@@ -76,7 +82,7 @@ abstract class BigNumber implements \JsonSerializable
* @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
* @pure
*/
private static function _of(BigNumber|int|float|string $value) : BigNumber
{
@@ -102,9 +108,6 @@ abstract class BigNumber implements \JsonSerializable
$numerator = $matches['numerator'];
$denominator = $matches['denominator'];
assert($numerator !== null);
assert($denominator !== null);
$numerator = self::cleanUp($sign, $numerator);
$denominator = self::cleanUp(null, $denominator);
@@ -138,7 +141,7 @@ abstract class BigNumber implements \JsonSerializable
}
if ($point !== null || $exponent !== null) {
$fractional = ($fractional ?? '');
$fractional ??= '';
$exponent = ($exponent !== null) ? (int)$exponent : 0;
if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) {
@@ -170,15 +173,15 @@ abstract class BigNumber implements \JsonSerializable
*
* @throws RoundingNecessaryException If the value cannot be converted.
*
* @psalm-pure
* @pure
*/
abstract protected static function from(BigNumber $number): static;
/**
* Proxy method to access BigInteger's protected constructor from sibling classes.
*
* @pure
* @internal
* @psalm-pure
*/
final protected function newBigInteger(string $value) : BigInteger
{
@@ -188,8 +191,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Proxy method to access BigDecimal's protected constructor from sibling classes.
*
* @pure
* @internal
* @psalm-pure
*/
final protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal
{
@@ -199,8 +202,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Proxy method to access BigRational's protected constructor from sibling classes.
*
* @pure
* @internal
* @psalm-pure
*/
final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational
{
@@ -216,7 +219,7 @@ abstract class BigNumber implements \JsonSerializable
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
* @pure
*/
final public static function min(BigNumber|int|float|string ...$values) : static
{
@@ -246,7 +249,7 @@ abstract class BigNumber implements \JsonSerializable
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
* @pure
*/
final public static function max(BigNumber|int|float|string ...$values) : static
{
@@ -270,41 +273,43 @@ abstract class BigNumber implements \JsonSerializable
/**
* 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.
* When called on BigNumber, sum() accepts any supported type and returns a result whose type is the widest among
* the given values (BigInteger < BigDecimal < BigRational).
*
* When called on BigInteger, BigDecimal, or BigRational, sum() requires that all values can be converted to that
* specific subclass, and returns a result of the same type.
*
* @param BigNumber|int|float|string ...$values The values to add. All values must be convertible to the class on
* which this method is called.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
* @pure
*/
final public static function sum(BigNumber|int|float|string ...$values) : static
{
/** @var static|null $sum */
$sum = null;
$first = array_shift($values);
foreach ($values as $value) {
$value = static::of($value);
$sum = $sum === null ? $value : self::add($sum, $value);
}
if ($sum === null) {
if ($first === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
$sum = static::of($first);
foreach ($values as $value) {
$sum = self::add($sum, static::of($value));
}
assert($sum instanceof static);
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
* @pure
*/
private static function add(BigNumber $a, BigNumber $b) : BigNumber
{
@@ -324,8 +329,6 @@ abstract class BigNumber implements \JsonSerializable
return $b->plus($a);
}
/** @var BigInteger $a */
return $a->plus($b);
}
@@ -333,9 +336,9 @@ abstract class BigNumber implements \JsonSerializable
* 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.
* @param string $number The number, validated as a string of digits.
*
* @psalm-pure
* @pure
*/
private static function cleanUp(string|null $sign, string $number) : string
{
@@ -350,6 +353,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is equal to the given one.
*
* @pure
*/
final public function isEqualTo(BigNumber|int|float|string $that) : bool
{
@@ -358,6 +363,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is strictly lower than the given one.
*
* @pure
*/
final public function isLessThan(BigNumber|int|float|string $that) : bool
{
@@ -366,6 +373,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is lower than or equal to the given one.
*
* @pure
*/
final public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool
{
@@ -374,6 +383,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is strictly greater than the given one.
*
* @pure
*/
final public function isGreaterThan(BigNumber|int|float|string $that) : bool
{
@@ -382,6 +393,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is greater than or equal to the given one.
*
* @pure
*/
final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool
{
@@ -390,6 +403,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number equals zero.
*
* @pure
*/
final public function isZero() : bool
{
@@ -398,6 +413,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is strictly negative.
*
* @pure
*/
final public function isNegative() : bool
{
@@ -406,6 +423,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is negative or zero.
*
* @pure
*/
final public function isNegativeOrZero() : bool
{
@@ -414,6 +433,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is strictly positive.
*
* @pure
*/
final public function isPositive() : bool
{
@@ -422,6 +443,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is positive or zero.
*
* @pure
*/
final public function isPositiveOrZero() : bool
{
@@ -431,20 +454,24 @@ abstract class BigNumber implements \JsonSerializable
/**
* Returns the sign of this number.
*
* @psalm-return -1|0|1
* Returns -1 if the number is negative, 0 if zero, 1 if positive.
*
* @return int -1 if the number is negative, 0 if zero, 1 if positive.
* @return -1|0|1
*
* @pure
*/
abstract public function getSign() : int;
/**
* Compares this number to the given one.
*
* @psalm-return -1|0|1
* Returns -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`.
*
* @return int -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`.
* @return -1|0|1
*
* @throws MathException If the number is not valid.
*
* @pure
*/
abstract public function compareTo(BigNumber|int|float|string $that) : int;
@@ -452,6 +479,8 @@ abstract class BigNumber implements \JsonSerializable
* Converts this number to a BigInteger.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
*
* @pure
*/
abstract public function toBigInteger() : BigInteger;
@@ -459,11 +488,15 @@ abstract class BigNumber implements \JsonSerializable
* Converts this number to a BigDecimal.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
*
* @pure
*/
abstract public function toBigDecimal() : BigDecimal;
/**
* Converts this number to a BigRational.
*
* @pure
*/
abstract public function toBigRational() : BigRational;
@@ -475,6 +508,8 @@ abstract class BigNumber implements \JsonSerializable
*
* @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
* This only applies when RoundingMode::UNNECESSARY is used.
*
* @pure
*/
abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal;
@@ -485,6 +520,8 @@ abstract class BigNumber implements \JsonSerializable
* 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.
*
* @pure
*/
abstract public function toInt() : int;
@@ -496,6 +533,8 @@ abstract class BigNumber implements \JsonSerializable
*
* 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.
*
* @pure
*/
abstract public function toFloat() : float;
@@ -504,6 +543,8 @@ abstract class BigNumber implements \JsonSerializable
*
* 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.
*
* @pure
*/
abstract public function __toString() : string;

View File

@@ -14,20 +14,18 @@ use Override;
* An arbitrarily large rational number.
*
* This class is immutable.
*
* @psalm-immutable
*/
final class BigRational extends BigNumber
final readonly class BigRational extends BigNumber
{
/**
* The numerator.
*/
private readonly BigInteger $numerator;
private BigInteger $numerator;
/**
* The denominator. Always strictly positive.
*/
private readonly BigInteger $denominator;
private BigInteger $denominator;
/**
* Protected constructor. Use a factory method to obtain an instance.
@@ -37,6 +35,8 @@ final class BigRational extends BigNumber
* @param bool $checkDenominator Whether to check the denominator for negative and zero.
*
* @throws DivisionByZeroException If the denominator is zero.
*
* @pure
*/
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
{
@@ -55,9 +55,6 @@ final class BigRational extends BigNumber
$this->denominator = $denominator;
}
/**
* @psalm-pure
*/
#[Override]
protected static function from(BigNumber $number): static
{
@@ -77,7 +74,7 @@ final class BigRational extends BigNumber
* @throws RoundingNecessaryException If an argument represents a non-integer number.
* @throws DivisionByZeroException If the denominator is zero.
*
* @psalm-pure
* @pure
*/
public static function nd(
BigNumber|int|float|string $numerator,
@@ -92,14 +89,11 @@ final class BigRational extends BigNumber
/**
* Returns a BigRational representing zero.
*
* @psalm-pure
* @pure
*/
public static function zero() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $zero
*/
/** @var BigRational|null $zero */
static $zero;
if ($zero === null) {
@@ -112,14 +106,11 @@ final class BigRational extends BigNumber
/**
* Returns a BigRational representing one.
*
* @psalm-pure
* @pure
*/
public static function one() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $one
*/
/** @var BigRational|null $one */
static $one;
if ($one === null) {
@@ -132,14 +123,11 @@ final class BigRational extends BigNumber
/**
* Returns a BigRational representing ten.
*
* @psalm-pure
* @pure
*/
public static function ten() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $ten
*/
/** @var BigRational|null $ten */
static $ten;
if ($ten === null) {
@@ -149,11 +137,17 @@ final class BigRational extends BigNumber
return $ten;
}
/**
* @pure
*/
public function getNumerator() : BigInteger
{
return $this->numerator;
}
/**
* @pure
*/
public function getDenominator() : BigInteger
{
return $this->denominator;
@@ -161,6 +155,8 @@ final class BigRational extends BigNumber
/**
* Returns the quotient of the division of the numerator by the denominator.
*
* @pure
*/
public function quotient() : BigInteger
{
@@ -169,6 +165,8 @@ final class BigRational extends BigNumber
/**
* Returns the remainder of the division of the numerator by the denominator.
*
* @pure
*/
public function remainder() : BigInteger
{
@@ -178,9 +176,9 @@ final class BigRational extends BigNumber
/**
* Returns the quotient and remainder of the division of the numerator by the denominator.
*
* @return BigInteger[]
* @return array{BigInteger, BigInteger}
*
* @psalm-return array{BigInteger, BigInteger}
* @pure
*/
public function quotientAndRemainder() : array
{
@@ -193,6 +191,8 @@ final class BigRational extends BigNumber
* @param BigNumber|int|float|string $that The number to add.
*
* @throws MathException If the number is not valid.
*
* @pure
*/
public function plus(BigNumber|int|float|string $that) : BigRational
{
@@ -211,6 +211,8 @@ final class BigRational extends BigNumber
* @param BigNumber|int|float|string $that The number to subtract.
*
* @throws MathException If the number is not valid.
*
* @pure
*/
public function minus(BigNumber|int|float|string $that) : BigRational
{
@@ -229,6 +231,8 @@ final class BigRational extends BigNumber
* @param BigNumber|int|float|string $that The multiplier.
*
* @throws MathException If the multiplier is not a valid number.
*
* @pure
*/
public function multipliedBy(BigNumber|int|float|string $that) : BigRational
{
@@ -246,6 +250,8 @@ final class BigRational extends BigNumber
* @param BigNumber|int|float|string $that The divisor.
*
* @throws MathException If the divisor is not a valid number, or is zero.
*
* @pure
*/
public function dividedBy(BigNumber|int|float|string $that) : BigRational
{
@@ -261,6 +267,8 @@ final class BigRational extends BigNumber
* Returns this number exponentiated to the given value.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*
* @pure
*/
public function power(int $exponent) : BigRational
{
@@ -287,6 +295,8 @@ final class BigRational extends BigNumber
* The reciprocal has the numerator and denominator swapped.
*
* @throws DivisionByZeroException If the numerator is zero.
*
* @pure
*/
public function reciprocal() : BigRational
{
@@ -295,6 +305,8 @@ final class BigRational extends BigNumber
/**
* Returns the absolute value of this BigRational.
*
* @pure
*/
public function abs() : BigRational
{
@@ -303,6 +315,8 @@ final class BigRational extends BigNumber
/**
* Returns the negated value of this BigRational.
*
* @pure
*/
public function negated() : BigRational
{
@@ -311,6 +325,8 @@ final class BigRational extends BigNumber
/**
* Returns the simplified value of this BigRational.
*
* @pure
*/
public function simplified() : BigRational
{
@@ -406,7 +422,6 @@ final class BigRational extends BigNumber
* This method is only here to allow unserializing the object and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param array{numerator: BigInteger, denominator: BigInteger} $data
*
@@ -414,10 +429,12 @@ final class BigRational extends BigNumber
*/
public function __unserialize(array $data): void
{
/** @phpstan-ignore isset.initializedProperty */
if (isset($this->numerator)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
/** @phpstan-ignore deadCode.unreachable */
$this->numerator = $data['numerator'];
$this->denominator = $data['denominator'];
}

View File

@@ -7,10 +7,10 @@ namespace Brick\Math\Exception;
/**
* Exception thrown when a division by zero occurs.
*/
class DivisionByZeroException extends MathException
final class DivisionByZeroException extends MathException
{
/**
* @psalm-pure
* @pure
*/
public static function divisionByZero() : DivisionByZeroException
{
@@ -18,7 +18,7 @@ class DivisionByZeroException extends MathException
}
/**
* @psalm-pure
* @pure
*/
public static function modulusMustNotBeZero() : DivisionByZeroException
{
@@ -26,7 +26,7 @@ class DivisionByZeroException extends MathException
}
/**
* @psalm-pure
* @pure
*/
public static function denominatorMustNotBeZero() : DivisionByZeroException
{

View File

@@ -9,10 +9,10 @@ use Brick\Math\BigInteger;
/**
* Exception thrown when an integer overflow occurs.
*/
class IntegerOverflowException extends MathException
final class IntegerOverflowException extends MathException
{
/**
* @psalm-pure
* @pure
*/
public static function toIntOverflow(BigInteger $value) : IntegerOverflowException
{

View File

@@ -7,6 +7,6 @@ namespace Brick\Math\Exception;
/**
* Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number.
*/
class NegativeNumberException extends MathException
final class NegativeNumberException extends MathException
{
}

View File

@@ -7,8 +7,11 @@ namespace Brick\Math\Exception;
/**
* Exception thrown when attempting to create a number from a string with an invalid format.
*/
class NumberFormatException extends MathException
final class NumberFormatException extends MathException
{
/**
* @pure
*/
public static function invalidFormat(string $value) : self
{
return new self(\sprintf(
@@ -20,7 +23,7 @@ class NumberFormatException extends MathException
/**
* @param string $char The failing character.
*
* @psalm-pure
* @pure
*/
public static function charNotInAlphabet(string $char) : self
{

View File

@@ -7,10 +7,10 @@ namespace Brick\Math\Exception;
/**
* Exception thrown when a number cannot be represented at the requested scale without rounding.
*/
class RoundingNecessaryException extends MathException
final class RoundingNecessaryException extends MathException
{
/**
* @psalm-pure
* @pure
*/
public static function roundingNecessary() : RoundingNecessaryException
{

View File

@@ -17,10 +17,8 @@ use Brick\Math\RoundingMode;
* All methods must return strings respecting this format, unless specified otherwise.
*
* @internal
*
* @psalm-immutable
*/
abstract class Calculator
abstract readonly class Calculator
{
/**
* The maximum exponent value allowed for the pow() method.
@@ -32,63 +30,12 @@ abstract class Calculator
*/
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
/**
* The Calculator instance in use.
*/
private static ?Calculator $instance = null;
/**
* Sets the Calculator instance to use.
*
* An instance is typically set only in unit tests: the autodetect is usually the best option.
*
* @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect.
*/
final public static function set(?Calculator $calculator) : void
{
self::$instance = $calculator;
}
/**
* Returns the Calculator instance to use.
*
* If none has been explicitly set, the fastest available implementation will be returned.
*
* @psalm-pure
* @psalm-suppress ImpureStaticProperty
*/
final public static function get() : Calculator
{
if (self::$instance === null) {
/** @psalm-suppress ImpureMethodCall */
self::$instance = self::detect();
}
return self::$instance;
}
/**
* Returns the fastest available Calculator implementation.
*
* @codeCoverageIgnore
*/
private static function detect() : Calculator
{
if (\extension_loaded('gmp')) {
return new Calculator\GmpCalculator();
}
if (\extension_loaded('bcmath')) {
return new Calculator\BcMathCalculator();
}
return new Calculator\NativeCalculator();
}
/**
* Extracts the sign & digits of the operands.
*
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
*
* @pure
*/
final protected function init(string $a, string $b) : array
{
@@ -103,6 +50,8 @@ abstract class Calculator
/**
* Returns the absolute value of a number.
*
* @pure
*/
final public function abs(string $n) : string
{
@@ -111,6 +60,8 @@ abstract class Calculator
/**
* Negates a number.
*
* @pure
*/
final public function neg(string $n) : string
{
@@ -128,9 +79,11 @@ abstract class Calculator
/**
* Compares two numbers.
*
* @psalm-return -1|0|1
* Returns -1 if the first number is less than, 0 if equal to, 1 if greater than the second number.
*
* @return int -1 if the first number is less than, 0 if equal to, 1 if greater than the second number.
* @return -1|0|1
*
* @pure
*/
final public function cmp(string $a, string $b) : int
{
@@ -160,16 +113,22 @@ abstract class Calculator
/**
* Adds two numbers.
*
* @pure
*/
abstract public function add(string $a, string $b) : string;
/**
* Subtracts two numbers.
*
* @pure
*/
abstract public function sub(string $a, string $b) : string;
/**
* Multiplies two numbers.
*
* @pure
*/
abstract public function mul(string $a, string $b) : string;
@@ -180,6 +139,8 @@ abstract class Calculator
* @param string $b The divisor, must not be zero.
*
* @return string The quotient.
*
* @pure
*/
abstract public function divQ(string $a, string $b) : string;
@@ -190,6 +151,8 @@ abstract class Calculator
* @param string $b The divisor, must not be zero.
*
* @return string The remainder.
*
* @pure
*/
abstract public function divR(string $a, string $b) : string;
@@ -200,6 +163,8 @@ abstract class Calculator
* @param string $b The divisor, must not be zero.
*
* @return array{string, string} An array containing the quotient and remainder.
*
* @pure
*/
abstract public function divQR(string $a, string $b) : array;
@@ -210,11 +175,15 @@ abstract class Calculator
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
*
* @return string The power.
*
* @pure
*/
abstract public function pow(string $a, int $e) : string;
/**
* @param string $b The modulus; must not be zero.
*
* @pure
*/
public function mod(string $a, string $b) : string
{
@@ -229,6 +198,8 @@ abstract class Calculator
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
*
* @param string $m The modulus; must not be negative or zero.
*
* @pure
*/
public function modInverse(string $x, string $m) : ?string
{
@@ -257,6 +228,8 @@ abstract class Calculator
* @param string $base The base number; must be positive or zero.
* @param string $exp The exponent; must be positive or zero.
* @param string $mod The modulus; must be strictly positive.
*
* @pure
*/
abstract public function modPow(string $base, string $exp, string $mod) : string;
@@ -267,6 +240,8 @@ abstract class Calculator
* has built-in support for GCD calculations.
*
* @return string The GCD, always positive, or zero if both arguments are zero.
*
* @pure
*/
public function gcd(string $a, string $b) : string
{
@@ -283,6 +258,8 @@ abstract class Calculator
/**
* @return array{string, string, string} GCD, X, Y
*
* @pure
*/
private function gcdExtended(string $a, string $b) : array
{
@@ -303,6 +280,8 @@ abstract class Calculator
*
* The result is the largest x such that x² ≤ n.
* The input MUST NOT be negative.
*
* @pure
*/
abstract public function sqrt(string $n) : string;
@@ -316,6 +295,8 @@ abstract class Calculator
* @param int $base The base of the number, validated from 2 to 36.
*
* @return string The converted number, following the Calculator conventions.
*
* @pure
*/
public function fromBase(string $number, int $base) : string
{
@@ -332,6 +313,8 @@ abstract class Calculator
* @param int $base The base to convert to, validated from 2 to 36.
*
* @return string The converted number, lowercase.
*
* @pure
*/
public function toBase(string $number, int $base) : string
{
@@ -359,6 +342,8 @@ abstract class Calculator
* @param int $base The base of the number, validated from 2 to alphabet length.
*
* @return string The number in base 10, following the Calculator conventions.
*
* @pure
*/
final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string
{
@@ -405,6 +390,8 @@ abstract class Calculator
* @param int $base The base to convert to, validated from 2 to alphabet length.
*
* @return string The converted number in the given alphabet.
*
* @pure
*/
final public function toArbitraryBase(string $number, string $alphabet, int $base) : string
{
@@ -434,10 +421,9 @@ abstract class Calculator
* @param string $b The divisor, must not be zero.
* @param RoundingMode $roundingMode The rounding mode.
*
* @throws \InvalidArgumentException If the rounding mode is invalid.
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
*
* @psalm-suppress ImpureFunctionCall
* @pure
*/
final public function divRound(string $a, string $b, RoundingMode $roundingMode) : string
{
@@ -498,9 +484,6 @@ abstract class Calculator
$lastDigitIsEven = ($lastDigit % 2 === 0);
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
break;
default:
throw new \InvalidArgumentException('Invalid rounding mode.');
}
if ($increment) {
@@ -515,6 +498,8 @@ abstract class Calculator
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*
* @pure
*/
public function and(string $a, string $b) : string
{
@@ -526,6 +511,8 @@ abstract class Calculator
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*
* @pure
*/
public function or(string $a, string $b) : string
{
@@ -537,6 +524,8 @@ abstract class Calculator
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*
* @pure
*/
public function xor(string $a, string $b) : string
{
@@ -549,6 +538,8 @@ abstract class Calculator
* @param 'and'|'or'|'xor' $operator The operator to use.
* @param string $a The left operand.
* @param string $b The right operand.
*
* @pure
*/
private function bitwise(string $operator, string $a, string $b) : string
{
@@ -596,6 +587,8 @@ abstract class Calculator
/**
* @param string $number A positive, binary number.
*
* @pure
*/
private function twosComplement(string $number) : string
{
@@ -625,6 +618,8 @@ abstract class Calculator
* Converts a decimal number to a binary string.
*
* @param string $number The number to convert, positive or zero, only digits.
*
* @pure
*/
private function toBinary(string $number) : string
{
@@ -642,6 +637,8 @@ abstract class Calculator
* Returns the positive decimal representation of a binary number.
*
* @param string $bytes The bytes representing the number.
*
* @pure
*/
private function toDecimal(string $bytes) : string
{

View File

@@ -11,10 +11,8 @@ use Override;
* Calculator implementation built around the bcmath library.
*
* @internal
*
* @psalm-immutable
*/
class BcMathCalculator extends Calculator
final readonly class BcMathCalculator extends Calculator
{
#[Override]
public function add(string $a, string $b) : string

View File

@@ -5,16 +5,15 @@ declare(strict_types=1);
namespace Brick\Math\Internal\Calculator;
use Brick\Math\Internal\Calculator;
use GMP;
use Override;
/**
* Calculator implementation built around the GMP library.
*
* @internal
*
* @psalm-immutable
*/
class GmpCalculator extends Calculator
final readonly class GmpCalculator extends Calculator
{
#[Override]
public function add(string $a, string $b) : string
@@ -51,6 +50,10 @@ class GmpCalculator extends Calculator
{
[$q, $r] = \gmp_div_qr($a, $b);
/**
* @var GMP $q
* @var GMP $r
*/
return [
\gmp_strval($q),
\gmp_strval($r)

View File

@@ -11,10 +11,8 @@ use Override;
* Calculator implementation using only native PHP code.
*
* @internal
*
* @psalm-immutable
*/
class NativeCalculator extends Calculator
final readonly class NativeCalculator extends Calculator
{
/**
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
@@ -24,9 +22,10 @@ class NativeCalculator extends Calculator
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
*/
private readonly int $maxDigits;
private int $maxDigits;
/**
* @pure
* @codeCoverageIgnore
*/
public function __construct()
@@ -34,7 +33,6 @@ class NativeCalculator extends Calculator
$this->maxDigits = match (PHP_INT_SIZE) {
4 => 9,
8 => 18,
default => throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.')
};
}
@@ -42,8 +40,8 @@ class NativeCalculator extends Calculator
public function add(string $a, string $b) : string
{
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
* @var numeric-string $a
* @var numeric-string $b
*/
$result = $a + $b;
@@ -80,8 +78,8 @@ class NativeCalculator extends Calculator
public function mul(string $a, string $b) : string
{
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
* @var numeric-string $a
* @var numeric-string $b
*/
$result = $a * $b;
@@ -151,11 +149,11 @@ class NativeCalculator extends Calculator
return [$this->neg($a), '0'];
}
/** @psalm-var numeric-string $a */
/** @var numeric-string $a */
$na = $a * 1; // cast to number
if (is_int($na)) {
/** @psalm-var numeric-string $b */
/** @var numeric-string $b */
$nb = $b * 1;
if (is_int($nb)) {
@@ -202,7 +200,6 @@ class NativeCalculator extends Calculator
$aa = $this->mul($a, $a);
/** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */
$result = $this->pow($aa, $e / 2);
if ($odd === 1) {
@@ -278,6 +275,8 @@ class NativeCalculator extends Calculator
/**
* Performs the addition of two non-signed large integers.
*
* @pure
*/
private function doAdd(string $a, string $b) : string
{
@@ -291,14 +290,13 @@ class NativeCalculator extends Calculator
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
/** @var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
/** @var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = (string) ($blockA + $blockB + $carry);
@@ -330,6 +328,8 @@ class NativeCalculator extends Calculator
/**
* Performs the subtraction of two non-signed large integers.
*
* @pure
*/
private function doSub(string $a, string $b) : string
{
@@ -360,14 +360,13 @@ class NativeCalculator extends Calculator
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
/** @var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
/** @var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = $blockA - $blockB - $carry;
@@ -407,6 +406,8 @@ class NativeCalculator extends Calculator
/**
* Performs the multiplication of two non-signed large integers.
*
* @pure
*/
private function doMul(string $a, string $b) : string
{
@@ -423,7 +424,6 @@ class NativeCalculator extends Calculator
if ($i < 0) {
$blockALength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
@@ -437,7 +437,6 @@ class NativeCalculator extends Calculator
if ($j < 0) {
$blockBLength += $j;
/** @psalm-suppress LoopInvalidation */
$j = 0;
}
@@ -480,6 +479,8 @@ class NativeCalculator extends Calculator
* Performs the division of two non-signed large integers.
*
* @return string[] The quotient and remainder.
*
* @pure
*/
private function doDiv(string $a, string $b) : array
{
@@ -498,7 +499,7 @@ class NativeCalculator extends Calculator
$r = $a; // remainder
$z = $y; // focus length, always $y or $y+1
/** @psalm-var numeric-string $b */
/** @var numeric-string $b */
$nb = $b * 1; // cast to number
// performance optimization in cases where the remainder will never cause int overflow
if (is_int(($nb - 1) * 10 + 9)) {
@@ -506,7 +507,7 @@ class NativeCalculator extends Calculator
for ($i = $z - 1; $i < $x; $i++) {
$n = $r * 10 + (int) $a[$i];
/** @psalm-var int $nb */
/** @var int $nb */
$q .= \intdiv($n, $nb);
$r = $n % $nb;
}
@@ -553,7 +554,9 @@ class NativeCalculator extends Calculator
/**
* Compares two non-signed large numbers.
*
* @psalm-return -1|0|1
* @return -1|0|1
*
* @pure
*/
private function doCmp(string $a, string $b) : int
{
@@ -575,6 +578,8 @@ class NativeCalculator extends Calculator
* The numbers must only consist of digits, without leading minus sign.
*
* @return array{string, string, int}
*
* @pure
*/
private function pad(string $a, string $b) : array
{

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal;
use function extension_loaded;
/**
* Stores the current Calculator instance used by BigNumber classes.
*
* @internal
*/
final class CalculatorRegistry
{
/**
* The Calculator instance in use.
*/
private static ?Calculator $instance = null;
/**
* Sets the Calculator instance to use.
*
* An instance is typically set only in unit tests: autodetect is usually the best option.
*
* @param Calculator|null $calculator The calculator instance, or null to revert to autodetect.
*/
final public static function set(?Calculator $calculator) : void
{
self::$instance = $calculator;
}
/**
* Returns the Calculator instance to use.
*
* If none has been explicitly set, the fastest available implementation will be returned.
*
* Note: even though this method is not technically pure, it is considered pure when used in a normal context, when
* only relying on autodetect.
*
* @pure
*/
final public static function get() : Calculator
{
/** @phpstan-ignore impure.staticPropertyAccess */
if (self::$instance === null) {
/** @phpstan-ignore impure.propertyAssign */
self::$instance = self::detect();
}
/** @phpstan-ignore impure.staticPropertyAccess */
return self::$instance;
}
/**
* Returns the fastest available Calculator implementation.
*
* @pure
* @codeCoverageIgnore
*/
private static function detect() : Calculator
{
if (extension_loaded('gmp')) {
return new Calculator\GmpCalculator();
}
if (extension_loaded('bcmath')) {
return new Calculator\BcMathCalculator();
}
return new Calculator\NativeCalculator();
}
}

View File

@@ -47,6 +47,7 @@ return array(
'Brick\\Math\\Exception\\NumberFormatException' => $vendorDir . '/brick/math/src/Exception/NumberFormatException.php',
'Brick\\Math\\Exception\\RoundingNecessaryException' => $vendorDir . '/brick/math/src/Exception/RoundingNecessaryException.php',
'Brick\\Math\\Internal\\Calculator' => $vendorDir . '/brick/math/src/Internal/Calculator.php',
'Brick\\Math\\Internal\\CalculatorRegistry' => $vendorDir . '/brick/math/src/Internal/CalculatorRegistry.php',
'Brick\\Math\\Internal\\Calculator\\BcMathCalculator' => $vendorDir . '/brick/math/src/Internal/Calculator/BcMathCalculator.php',
'Brick\\Math\\Internal\\Calculator\\GmpCalculator' => $vendorDir . '/brick/math/src/Internal/Calculator/GmpCalculator.php',
'Brick\\Math\\Internal\\Calculator\\NativeCalculator' => $vendorDir . '/brick/math/src/Internal/Calculator/NativeCalculator.php',

View File

@@ -391,6 +391,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Brick\\Math\\Exception\\NumberFormatException' => __DIR__ . '/..' . '/brick/math/src/Exception/NumberFormatException.php',
'Brick\\Math\\Exception\\RoundingNecessaryException' => __DIR__ . '/..' . '/brick/math/src/Exception/RoundingNecessaryException.php',
'Brick\\Math\\Internal\\Calculator' => __DIR__ . '/..' . '/brick/math/src/Internal/Calculator.php',
'Brick\\Math\\Internal\\CalculatorRegistry' => __DIR__ . '/..' . '/brick/math/src/Internal/CalculatorRegistry.php',
'Brick\\Math\\Internal\\Calculator\\BcMathCalculator' => __DIR__ . '/..' . '/brick/math/src/Internal/Calculator/BcMathCalculator.php',
'Brick\\Math\\Internal\\Calculator\\GmpCalculator' => __DIR__ . '/..' . '/brick/math/src/Internal/Calculator/GmpCalculator.php',
'Brick\\Math\\Internal\\Calculator\\NativeCalculator' => __DIR__ . '/..' . '/brick/math/src/Internal/Calculator/NativeCalculator.php',

View File

@@ -155,28 +155,28 @@
},
{
"name": "brick/math",
"version": "0.13.1",
"version_normalized": "0.13.1.0",
"version": "0.14.0",
"version_normalized": "0.14.0.0",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04"
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04",
"url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"shasum": ""
},
"require": {
"php": "^8.1"
"php": "^8.2"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1",
"vimeo/psalm": "6.8.8"
"phpstan/phpstan": "2.1.22",
"phpunit/phpunit": "^11.5"
},
"time": "2025-03-29T13:50:30+00:00",
"time": "2025-08-29T12:40:03+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -206,7 +206,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.13.1"
"source": "https://github.com/brick/math/tree/0.14.0"
},
"funding": [
{
@@ -1443,17 +1443,17 @@
},
{
"name": "paragonie/easy-ecc",
"version": "v1.3.0",
"version_normalized": "1.3.0.0",
"version": "v1.3.1",
"version_normalized": "1.3.1.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/easy-ecc.git",
"reference": "1a72ed23a6598d482f2794c5ee6b2eb5abb364b2"
"reference": "ee588d73919f18ee27789ec1f0af2025fe74406a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/easy-ecc/zipball/1a72ed23a6598d482f2794c5ee6b2eb5abb364b2",
"reference": "1a72ed23a6598d482f2794c5ee6b2eb5abb364b2",
"url": "https://api.github.com/repos/paragonie/easy-ecc/zipball/ee588d73919f18ee27789ec1f0af2025fe74406a",
"reference": "ee588d73919f18ee27789ec1f0af2025fe74406a",
"shasum": ""
},
"require": {
@@ -1468,7 +1468,7 @@
"phpunit/phpunit": "^7|^8|^9",
"vimeo/psalm": "^1|^3|^4|^5|^6"
},
"time": "2025-07-19T03:27:16+00:00",
"time": "2025-10-22T18:38:57+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -3451,28 +3451,28 @@
},
{
"name": "symfony/filesystem",
"version": "v6.4.24",
"version_normalized": "6.4.24.0",
"version": "v7.3.6",
"version_normalized": "7.3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8"
"reference": "e9bcfd7837928ab656276fe00464092cc9e1826a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
"reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a",
"reference": "e9bcfd7837928ab656276fe00464092cc9e1826a",
"shasum": ""
},
"require": {
"php": ">=8.1",
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
"require-dev": {
"symfony/process": "^5.4|^6.4|^7.0"
"symfony/process": "^6.4|^7.0"
},
"time": "2025-07-10T08:14:14+00:00",
"time": "2025-11-05T09:52:27+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -3500,7 +3500,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.4.24"
"source": "https://github.com/symfony/filesystem/tree/v7.3.6"
},
"funding": [
{

View File

@@ -3,7 +3,7 @@
'name' => 'zotlabs/hubzilla',
'pretty_version' => 'dev-10.6RC',
'version' => 'dev-10.6RC',
'reference' => 'd11b05de71d84416aa085abd8570c038b1dc618a',
'reference' => '6320506c2738258c0bab9a6778342fbdc958b022',
'type' => 'application',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -29,9 +29,9 @@
'dev_requirement' => false,
),
'brick/math' => array(
'pretty_version' => '0.13.1',
'version' => '0.13.1.0',
'reference' => 'fc7ed316430118cc7836bf45faff18d5dfc8de04',
'pretty_version' => '0.14.0',
'version' => '0.14.0.0',
'reference' => '113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2',
'type' => 'library',
'install_path' => __DIR__ . '/../brick/math',
'aliases' => array(),
@@ -191,9 +191,9 @@
'dev_requirement' => false,
),
'paragonie/easy-ecc' => array(
'pretty_version' => 'v1.3.0',
'version' => '1.3.0.0',
'reference' => '1a72ed23a6598d482f2794c5ee6b2eb5abb364b2',
'pretty_version' => 'v1.3.1',
'version' => '1.3.1.0',
'reference' => 'ee588d73919f18ee27789ec1f0af2025fe74406a',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/easy-ecc',
'aliases' => array(),
@@ -473,9 +473,9 @@
'dev_requirement' => false,
),
'symfony/filesystem' => array(
'pretty_version' => 'v6.4.24',
'version' => '6.4.24.0',
'reference' => '75ae2edb7cdcc0c53766c30b0a2512b8df574bd8',
'pretty_version' => 'v7.3.6',
'version' => '7.3.6.0',
'reference' => 'e9bcfd7837928ab656276fe00464092cc9e1826a',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/filesystem',
'aliases' => array(),
@@ -544,7 +544,7 @@
'zotlabs/hubzilla' => array(
'pretty_version' => 'dev-10.6RC',
'version' => 'dev-10.6RC',
'reference' => 'd11b05de71d84416aa085abd8570c038b1dc618a',
'reference' => '6320506c2738258c0bab9a6778342fbdc958b022',
'type' => 'application',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

View File

@@ -4,8 +4,8 @@
$issues = array();
if (!(PHP_VERSION_ID >= 80100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
if (!(PHP_VERSION_ID >= 80200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.';
}
if (PHP_INT_SIZE !== 8) {

View File

@@ -12,6 +12,10 @@
</projectFiles>
<issueHandlers>
<MissingDependency errorLevel="info" />
<PossiblyUnusedParam errorLevel="info" />
<UndefinedClass errorLevel="info" />
<UndefinedMethod errorLevel="info" />
<InvalidDocblock errorLevel="info" />
<PossiblyUnusedMethod errorLevel="info" />
<PropertyNotSetInConstructor errorLevel="suppress" />

View File

@@ -26,7 +26,7 @@ final class X25519 implements EcDHInterface
* @throws \SodiumException
* @throws \TypeError
*/
public function __construct(PrivateKeyInterface $sk = null, PublicKeyInterface $pk = null)
public function __construct(?PrivateKeyInterface $sk = null, ?PublicKeyInterface $pk = null)
{
if ($sk) {
$this->setSenderKey($sk);

View File

@@ -1,6 +1,16 @@
CHANGELOG
=========
7.1
---
* Add the `Filesystem::readFile()` method
7.0
---
* Add argument `$lock` to `Filesystem::appendToFile()`
5.4
---

View File

@@ -20,12 +20,12 @@ namespace Symfony\Component\Filesystem\Exception;
*/
class IOException extends \RuntimeException implements IOExceptionInterface
{
private ?string $path;
public function __construct(string $message, int $code = 0, ?\Throwable $previous = null, ?string $path = null)
{
$this->path = $path;
public function __construct(
string $message,
int $code = 0,
?\Throwable $previous = null,
private ?string $path = null,
) {
parent::__construct($message, $code, $previous);
}

View File

@@ -31,12 +31,10 @@ class Filesystem
* If the target file is newer, it is overwritten only when the
* $overwriteNewerFiles option is set to true.
*
* @return void
*
* @throws FileNotFoundException When originFile doesn't exist
* @throws IOException When copy fails
*/
public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false)
public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false): void
{
$originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
if ($originIsLocal && !is_file($originFile)) {
@@ -87,11 +85,9 @@ class Filesystem
/**
* Creates a directory recursively.
*
* @return void
*
* @throws IOException On any directory creation failure
*/
public function mkdir(string|iterable $dirs, int $mode = 0777)
public function mkdir(string|iterable $dirs, int $mode = 0777): void
{
foreach ($this->toIterable($dirs) as $dir) {
if (is_dir($dir)) {
@@ -130,11 +126,9 @@ class Filesystem
* @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used
* @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
*
* @return void
*
* @throws IOException When touch fails
*/
public function touch(string|iterable $files, ?int $time = null, ?int $atime = null)
public function touch(string|iterable $files, ?int $time = null, ?int $atime = null): void
{
foreach ($this->toIterable($files) as $file) {
if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) {
@@ -146,11 +140,9 @@ class Filesystem
/**
* Removes files or directories.
*
* @return void
*
* @throws IOException When removal fails
*/
public function remove(string|iterable $files)
public function remove(string|iterable $files): void
{
if ($files instanceof \Traversable) {
$files = iterator_to_array($files, false);
@@ -214,11 +206,9 @@ class Filesystem
* @param int $umask The mode mask (octal)
* @param bool $recursive Whether change the mod recursively or not
*
* @return void
*
* @throws IOException When the change fails
*/
public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false)
public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false): void
{
foreach ($this->toIterable($files) as $file) {
if (!self::box('chmod', $file, $mode & ~$umask)) {
@@ -240,11 +230,9 @@ class Filesystem
* @param string|int $user A user name or number
* @param bool $recursive Whether change the owner recursively or not
*
* @return void
*
* @throws IOException When the change fails
*/
public function chown(string|iterable $files, string|int $user, bool $recursive = false)
public function chown(string|iterable $files, string|int $user, bool $recursive = false): void
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
@@ -272,11 +260,9 @@ class Filesystem
* @param string|int $group A group name or number
* @param bool $recursive Whether change the group recursively or not
*
* @return void
*
* @throws IOException When the change fails
*/
public function chgrp(string|iterable $files, string|int $group, bool $recursive = false)
public function chgrp(string|iterable $files, string|int $group, bool $recursive = false): void
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
@@ -297,12 +283,10 @@ class Filesystem
/**
* Renames a file or a directory.
*
* @return void
*
* @throws IOException When target file or directory already exists
* @throws IOException When origin cannot be renamed
*/
public function rename(string $origin, string $target, bool $overwrite = false)
public function rename(string $origin, string $target, bool $overwrite = false): void
{
// we check that target does not exist
if (!$overwrite && $this->isReadable($target)) {
@@ -340,11 +324,9 @@ class Filesystem
/**
* Creates a symbolic link or copy a directory.
*
* @return void
*
* @throws IOException When symlink fails
*/
public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false)
public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false): void
{
self::assertFunctionExists('symlink');
@@ -378,12 +360,10 @@ class Filesystem
*
* @param string|string[] $targetFiles The target file(s)
*
* @return void
*
* @throws FileNotFoundException When original file is missing or not a file
* @throws IOException When link fails, including if link already exists
*/
public function hardlink(string $originFile, string|iterable $targetFiles)
public function hardlink(string $originFile, string|iterable $targetFiles): void
{
self::assertFunctionExists('link');
@@ -537,11 +517,9 @@ class Filesystem
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @return void
*
* @throws IOException When file type is unknown
*/
public function mirror(string $originDir, string $targetDir, ?\Traversable $iterator = null, array $options = [])
public function mirror(string $originDir, string $targetDir, ?\Traversable $iterator = null, array $options = []): void
{
$targetDir = rtrim($targetDir, '/\\');
$originDir = rtrim($originDir, '/\\');
@@ -598,17 +576,11 @@ class Filesystem
}
/**
* Returns whether the file path is an absolute path.
* Returns whether the given path is absolute.
*/
public function isAbsolutePath(string $file): bool
{
return '' !== $file && (strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, \PHP_URL_SCHEME)
);
return Path::isAbsolute($file);
}
/**
@@ -641,7 +613,7 @@ class Filesystem
// Loop until we create a valid temp file or have reached 10 attempts
for ($i = 0; $i < 10; ++$i) {
// Create a unique filename
$tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true).$suffix;
$tmpFile = $dir.'/'.$prefix.bin2hex(random_bytes(4)).$suffix;
// Use fopen instead of file_exists as some streams do not support stat
// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
@@ -663,11 +635,9 @@ class Filesystem
*
* @param string|resource $content The data to write into the file
*
* @return void
*
* @throws IOException if the file cannot be written to
*/
public function dumpFile(string $filename, $content)
public function dumpFile(string $filename, $content): void
{
if (\is_array($content)) {
throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
@@ -714,11 +684,9 @@ class Filesystem
* @param string|resource $content The content to append
* @param bool $lock Whether the file should be locked when writing to it
*
* @return void
*
* @throws IOException If the file is not writable
*/
public function appendToFile(string $filename, $content/* , bool $lock = false */)
public function appendToFile(string $filename, $content, bool $lock = false): void
{
if (\is_array($content)) {
throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
@@ -730,13 +698,30 @@ class Filesystem
$this->mkdir($dir);
}
$lock = \func_num_args() > 2 && func_get_arg(2);
if (false === self::box('file_put_contents', $filename, $content, \FILE_APPEND | ($lock ? \LOCK_EX : 0))) {
throw new IOException(\sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename);
}
}
/**
* Returns the content of a file as a string.
*
* @throws IOException If the file cannot be read
*/
public function readFile(string $filename): string
{
if (is_dir($filename)) {
throw new IOException(\sprintf('Failed to read file "%s": File is a directory.', $filename));
}
$content = self::box('file_get_contents', $filename);
if (false === $content) {
throw new IOException(\sprintf('Failed to read file "%s": ', $filename).self::$lastError, 0, null, $filename);
}
return $content;
}
private function toIterable(string|iterable $files): iterable
{
return is_iterable($files) ? $files : [$files];

View File

@@ -346,50 +346,30 @@ final class Path
$extension = ltrim($extension, '.');
// No extension for paths
if ('/' === substr($path, -1)) {
if (str_ends_with($path, '/')) {
return $path;
}
// No actual extension in path
if (empty($actualExtension)) {
return $path.('.' === substr($path, -1) ? '' : '.').$extension;
if (!$actualExtension) {
return $path.(str_ends_with($path, '.') ? '' : '.').$extension;
}
return substr($path, 0, -\strlen($actualExtension)).$extension;
}
/**
* Returns whether the given path is absolute.
*/
public static function isAbsolute(string $path): bool
{
if ('' === $path) {
return false;
}
// Strip scheme
if (false !== ($schemeSeparatorPosition = strpos($path, '://')) && 1 !== $schemeSeparatorPosition) {
$path = substr($path, $schemeSeparatorPosition + 3);
}
$firstCharacter = $path[0];
// UNIX root "/" or "\" (Windows style)
if ('/' === $firstCharacter || '\\' === $firstCharacter) {
return true;
}
// Windows root
if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) {
// Special case: "C:"
if (2 === \strlen($path)) {
return true;
}
// Normal case: "C:/ or "C:\"
if ('/' === $path[2] || '\\' === $path[2]) {
return true;
}
}
return false;
return '' !== $path && (strspn($path, '/\\', 0, 1)
|| (\strlen($path) > 3 && ctype_alpha($path[0])
&& ':' === $path[1]
&& strspn($path, '/\\', 2, 1)
)
|| null !== parse_url($path, \PHP_URL_SCHEME)
);
}
public static function isRelative(string $path): bool
@@ -668,7 +648,7 @@ final class Path
}
// Only add slash if previous part didn't end with '/' or '\'
if (!\in_array(substr($finalPath, -1), ['/', '\\'])) {
if (!\in_array(substr($finalPath, -1), ['/', '\\'], true)) {
$finalPath .= '/';
}

View File

@@ -16,12 +16,12 @@
}
],
"require": {
"php": ">=8.1",
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
"require-dev": {
"symfony/process": "^5.4|^6.4|^7.0"
"symfony/process": "^6.4|^7.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Filesystem\\": "" },