diff --git a/README.md b/README.md index e9a0fc38..9b2885a5 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Polyfills are provided for: - the `mb_trim`, `mb_ltrim` and `mb_rtrim` functions introduced in PHP 8.4; - the `CURL_HTTP_VERSION_3` and `CURL_HTTP_VERSION_3ONLY` constants introduced in PHP 8.4; - the `grapheme_str_split` function introduced in PHP 8.4; +- the `bcdivmod` function introduced in PHP 8.4; - the `get_error_handler` and `get_exception_handler` functions introduced in PHP 8.5; - the `NoDiscard` attribute introduced in PHP 8.5; - the `array_first` and `array_last` functions introduced in PHP 8.5; diff --git a/src/Php84/Php84.php b/src/Php84/Php84.php index accb2486..defa2fd1 100644 --- a/src/Php84/Php84.php +++ b/src/Php84/Php84.php @@ -204,4 +204,14 @@ public static function grapheme_str_split(string $string, int $length) return $chunks; } + + public static function bcdivmod(string $num1, string $num2, ?int $scale = null): ?array + { + if (null === $quot = \bcdiv($num1, $num2, 0)) { + return null; + } + $scale = $scale ?? (\PHP_VERSION_ID >= 70300 ? \bcscale() : (ini_get('bcmath.scale') ?: 0); + + return [$quot, \bcmod($num1, $num2, $scale)]; + } } diff --git a/src/Php84/README.md b/src/Php84/README.md index 49aa56e3..39493600 100644 --- a/src/Php84/README.md +++ b/src/Php84/README.md @@ -4,6 +4,7 @@ Symfony Polyfill / Php84 This component provides features added to PHP 8.4 core: - [`array_find`, `array_find_key`, `array_any` and `array_all`](https://wiki.php.net/rfc/array_find) +- [`bcdivmod`](https://wiki.php.net/rfc/add_bcdivmod_to_bcmath) - [`Deprecated`](https://wiki.php.net/rfc/deprecated_attribute) - `CURL_HTTP_VERSION_3` and `CURL_HTTP_VERSION_3ONLY` constants - [`fpow`](https://wiki.php.net/rfc/raising_zero_to_power_of_negative_number) diff --git a/src/Php84/bootstrap.php b/src/Php84/bootstrap.php index 5f004f6c..4bd1c17d 100644 --- a/src/Php84/bootstrap.php +++ b/src/Php84/bootstrap.php @@ -67,6 +67,12 @@ function mb_rtrim(string $string, ?string $characters = null, ?string $encoding } } +if (extension_loaded('bcmath')) { + if (!function_exists('bcdivmod')) { + function bcdivmod(string $num1, string $num2, ?int $scale = null): ?array { return p\Php84::bcdivmod($num1, $num2, $scale); } + } +} + if (\PHP_VERSION_ID >= 80200) { return require __DIR__.'/bootstrap82.php'; } diff --git a/tests/Php84/Php84Test.php b/tests/Php84/Php84Test.php index 14600c0a..a52901d7 100644 --- a/tests/Php84/Php84Test.php +++ b/tests/Php84/Php84Test.php @@ -693,4 +693,71 @@ public static function graphemeStrSplitDataProvider(): array return $cases; } + + /** + * @requires extension bcmath + * + * @covers \Symfony\Polyfill\Php84\Php84::bcdivmod + * + * @dataProvider bcDivModProvider + */ + public function testBcDivMod(string $num1, string $num2, ?int $scale, array $expected) + { + $this->assertSame($expected, bcdivmod($num1, $num2, $scale)); + } + + /** + * @requires extension bcmath + */ + public function testBcDivModDivideByZero() + { + $this->expectException(\DivisionByZeroError::class); + + bcdivmod('1', '0'); + } + + /** + * @requires extension bcmath + */ + public function testBcDivModDivideByFloatingZero() + { + $this->expectException(\DivisionByZeroError::class); + + bcdivmod('1', '0.00'); + } + + /** + * @requires PHP 8.0 + * @requires extension bcmath + */ + public function testBcDivModMalformedNumber() + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage('Argument #1 ($num1) is not well-formed'); + + bcdivmod('a', '1'); + } + + /** + * @requires PHP 8.0 + * @requires extension bcmath + */ + public function testBcDivModMalformedNumber2() + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage('Argument #2 ($num2) is not well-formed'); + + bcdivmod('1', 'a'); + } + + public static function bcDivModProvider(): iterable + { + yield ['1', '1', null, ['1', '0']]; + yield ['1', '2', null, ['0', '1']]; + yield ['5', '2', null, ['2', '1']]; + yield ['5', '2', 0, ['2', '1']]; + yield ['5', '2', 1, ['2', '1.0']]; + yield ['5', '2', 2, ['2', '1.00']]; + yield ['7.2', '3', 2, ['2', '1.20']]; + } }