From b21cace446ff1e426422e6e28be2d73004642ded Mon Sep 17 00:00:00 2001 From: fab2s Date: Mon, 28 Aug 2023 13:40:17 +0200 Subject: [PATCH] Drop php<8.1 --- .github/workflows/ci.yml | 42 +++++ .github/workflows/qa.yml | 52 ++++++ .php_cs | 141 ---------------- .travis.yml | 15 -- README.md | 4 +- composer.json | 14 +- phpunit.xml | 42 +++-- pint.json | 77 +++++++++ src/Math.php | 119 ++++---------- src/MathBaseAbstract.php | 165 +++++-------------- src/MathOpsAbstract.php | 109 ++----------- tests/MathTest.php | 339 ++++++++++++--------------------------- 12 files changed, 392 insertions(+), 727 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/qa.yml delete mode 100644 .php_cs delete mode 100644 .travis.yml create mode 100644 pint.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9a03d97 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI +on: [pull_request] +jobs: + tests: + name: Math CI PHP ${{ matrix.php-versions }} + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: [ '8.2', '8.1' ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, dom, fileinfo, gmp, bcmath + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Remove composer.lock + run: rm -f composer.lock + + - name: Remove Pint + run: composer remove "laravel/pint" --dev --no-update + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Test with phpunit + run: vendor/bin/phpunit diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml new file mode 100644 index 0000000..d3e6f6a --- /dev/null +++ b/.github/workflows/qa.yml @@ -0,0 +1,52 @@ +name: QA +on: + push: + branches: + - master + pull_request: + types: [ opened, synchronize ] +jobs: + tests: + name: Math QA + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + extensions: mbstring, dom, fileinfo, gmp, bcmath + coverage: xdebug + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Remove composer.lock + run: rm -f composer.lock + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Check code style + run: vendor/bin/pint --config pint.json --test + + - name: Compute Coverage + run: vendor/bin/phpunit --coverage-clover ./coverage.xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./coverage.xml + flags: unittests + name: codecov-math diff --git a/.php_cs b/.php_cs deleted file mode 100644 index ecd584b..0000000 --- a/.php_cs +++ /dev/null @@ -1,141 +0,0 @@ -files() - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests') - ->name('*.php'); - - -$header = <<<'EOF' -This file is part of Math. - (c) Fabrice de Stefanis / https://github.com/fab2s/Math -This source file is licensed under the MIT license which you will -find in the LICENSE file or at https://opensource.org/licenses/MIT -EOF; - -return PhpCsFixer\Config::create() - ->setRiskyAllowed(true) - ->setRules([ - '@PSR2' => true, - 'strict_param' => true, - 'array_syntax' => array('syntax' => 'short'), - 'binary_operator_spaces' => [ - 'align_double_arrow' => true, - 'align_equals' => true - ], - 'blank_line_after_namespace' => true, - 'blank_line_after_opening_tag' => true, - 'blank_line_before_return' => true, - 'braces' => true, - 'cast_spaces' => true, - 'class_definition' => array('singleLine' => true), - 'class_keyword_remove' => false, - 'combine_consecutive_unsets' => true, - 'concat_space' => ['spacing' => 'one'], - 'declare_equal_normalize' => true, - //'declare_strict_types' => true, - 'elseif' => true, - 'encoding' => true, - 'full_opening_tag' => true, - 'function_declaration' => true, - 'function_typehint_space' => true, - 'hash_to_slash_comment' => true, - 'header_comment' => array('header' => $header), - 'include' => true, - 'indentation_type' => true, - 'line_ending' => true, - 'linebreak_after_opening_tag' => true, - 'lowercase_cast' => true, - 'lowercase_constants' => true, - 'lowercase_keywords' => true, - 'method_argument_space' => true, - 'method_separation' => true, - 'native_function_casing' => true, - 'new_with_braces' => false, - 'no_blank_lines_after_class_opening' => true, - 'no_blank_lines_after_phpdoc' => true, - 'no_closing_tag' => true, - 'no_empty_comment' => true, - 'no_empty_phpdoc' => true, - 'no_empty_statement' => true, - 'no_extra_consecutive_blank_lines' => [ - 'break', - 'continue', - 'extra', - 'return', - 'throw', - 'use', - 'parenthesis_brace_block', - 'square_brace_block', - 'curly_brace_block' - ], - 'no_leading_import_slash' => true, - 'no_leading_namespace_whitespace' => true, - 'no_mixed_echo_print' => array('use' => 'echo'), - 'no_multiline_whitespace_around_double_arrow' => true, - 'no_multiline_whitespace_before_semicolons' => true, - 'no_short_bool_cast' => true, - 'no_singleline_whitespace_before_semicolons' => true, - 'no_spaces_after_function_name' => true, - 'no_spaces_around_offset' => true, - 'no_spaces_inside_parenthesis' => true, - 'no_trailing_comma_in_list_call' => true, - 'no_trailing_comma_in_singleline_array' => true, - 'no_trailing_whitespace' => true, - 'no_trailing_whitespace_in_comment' => true, - 'no_unneeded_control_parentheses' => true, - 'no_unused_imports' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'no_whitespace_before_comma_in_array' => true, - 'no_whitespace_in_blank_line' => true, - 'normalize_index_brace' => true, - 'object_operator_without_whitespace' => true, - 'ordered_class_elements' => true, - 'ordered_imports' => true, - 'php_unit_fqcn_annotation' => true, - 'phpdoc_add_missing_param_annotation' => true, - 'phpdoc_align' => true, - 'phpdoc_annotation_without_dot' => true, - 'phpdoc_indent' => true, - 'phpdoc_inline_tag' => true, - 'phpdoc_no_access' => true, - 'phpdoc_no_alias_tag' => true, - 'phpdoc_no_empty_return' => true, - 'phpdoc_no_package' => true, - 'phpdoc_no_useless_inheritdoc' => true, - 'phpdoc_order' => true, - 'phpdoc_return_self_reference' => true, - 'phpdoc_scalar' => true, - 'phpdoc_separation' => true, - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_summary' => false, - 'phpdoc_to_comment' => true, - 'phpdoc_trim' => true, - 'phpdoc_types' => true, - 'phpdoc_var_without_name' => true, - 'pre_increment' => true, - 'psr4' => true, - 'return_type_declaration' => true, - 'self_accessor' => true, - 'semicolon_after_instruction' => true, - 'short_scalar_cast' => true, - 'single_blank_line_at_eof' => true, - 'single_blank_line_before_namespace' => true, - 'single_class_element_per_statement' => true, - 'single_import_per_statement' => true, - 'single_line_after_imports' => true, - 'single_quote' => true, - 'space_after_semicolon' => true, - 'standardize_not_equals' => true, - 'switch_case_semicolon_to_colon' => true, - 'switch_case_space' => true, - 'ternary_operator_spaces' => true, - 'trailing_comma_in_multiline_array' => true, - 'trim_array_spaces' => true, - 'unary_operator_spaces' => true, - 'visibility_required' => true, - 'whitespace_after_comma_in_array' => true, - - ]) - ->setFinder($finder); \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ed55ef3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: php - -php: - - 7.1 - - 7.2 - - 7.3 - - 7.4 - - 8.0 - -before_script: - - composer self-update - - composer install --no-interaction - -script: - - vendor/bin/phpunit diff --git a/README.md b/README.md index d9d8e51..c16bc32 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Math -[![Build Status](https://travis-ci.com/fab2s/Math.svg?branch=master)](https://travis-ci.com/fab2s/Math) [![Total Downloads](https://poser.pugx.org/fab2s/math/downloads)](//packagist.org/packages/fab2s/math) [![Monthly Downloads](https://poser.pugx.org/fab2s/math/d/monthly)](//packagist.org/packages/fab2s/math) [![Latest Stable Version](https://poser.pugx.org/fab2s/math/v/stable)](https://packagist.org/packages/fab2s/math) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fab2s/Math/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/fab2s/Math/?branch=master) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com) [![License](https://poser.pugx.org/fab2s/math/license)](https://packagist.org/packages/fab2s/math) +[![CI](https://github.com/fab2s/Math/actions/workflows/ci.yml/badge.svg)](https://github.com/fab2s/Math/actions/workflows/ci.yml) [![QA](https://github.com/fab2s/Math/actions/workflows/qa.yml/badge.svg)](https://github.com/fab2s/Math/actions/workflows/qa.yml) [![Total Downloads](https://poser.pugx.org/fab2s/math/downloads)](//packagist.org/packages/fab2s/math) [![Monthly Downloads](https://poser.pugx.org/fab2s/math/d/monthly)](//packagist.org/packages/fab2s/math) [![Latest Stable Version](https://poser.pugx.org/fab2s/math/v/stable)](https://packagist.org/packages/fab2s/math) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fab2s/Math/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/fab2s/Math/?branch=master) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com) [![License](https://poser.pugx.org/fab2s/math/license)](https://packagist.org/packages/fab2s/math) A fluent [bcmath](https://php.net/bcmath) based _Helper_ to handle high precision calculus in base 10 with a rather strict approach (want precision for something right?). It does not try to be smart and just fails without `bcmath`, but it does auto detect [GMP](https://php.net/GMP) for faster base conversions. @@ -128,7 +128,7 @@ $number->setPrecision(14); // will use precision 14 for any further calculations ## Requirements -`Math` is tested against php 7.1, 7.2, 7.3, 7.4 and 8.0 +`Math` is tested against php 8.1 and 8.2 ## Contributing diff --git a/composer.json b/composer.json index 1410f87..d6c62a2 100644 --- a/composer.json +++ b/composer.json @@ -17,11 +17,12 @@ "MIT" ], "require" : { - "php": ">=7.1", + "php": "^8.1", "ext-bcmath": "*" }, "require-dev": { - "phpunit/phpunit": "~7.0|~8.0" + "phpunit/phpunit": "^10.0", + "laravel/pint": "^1.11" }, "autoload": { "classmap": [ @@ -33,6 +34,15 @@ "fab2s\\Math\\Tests\\": "tests/" } }, + "scripts": { + "post-update-cmd": [ + "rm -rf .*.cache" + ], + "post-install-cmd": [ + "rm -rf .*.cache" + ], + "fix": "@php vendor/bin/pint --config pint.json" + }, "suggest": { "ext-gmp": "For faster Math::baseConvert up to base62" } diff --git a/phpunit.xml b/phpunit.xml index 2be8c6d..8256e2e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,25 +1,21 @@ - - - - ./tests/ - - - - - - - - - - + + + + ./tests/ + + + + + src + + + + + + + + + + diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..4da5b43 --- /dev/null +++ b/pint.json @@ -0,0 +1,77 @@ +{ + "preset": "laravel", + "rules": { + "header_comment": { + "header": "This file is part of fab2s/Math.\n(c) Fabrice de Stefanis / https://github.com/fab2s/Math\nThis source file is licensed under the MIT license which you will\nfind in the LICENSE file or at https://opensource.org/licenses/MIT" + }, + "assign_null_coalescing_to_coalesce_equal": true, + "binary_operator_spaces": { + "default": "align_single_space_minimal" + }, + "class_definition": true, + "blank_lines_before_namespace": true, + "align_multiline_comment": true, + "class_attributes_separation": { + "elements": { + "method": "one", + "const": "only_if_meta", + "property": "only_if_meta", + "trait_import": "only_if_meta" + } + }, + "concat_space": { + "spacing": "one" + }, + "global_namespace_import": { + "import_classes": true, + "import_functions": false, + "import_constants": false + }, + "no_superfluous_phpdoc_tags": true, + "method_argument_space": { + "on_multiline": "ensure_fully_multiline" + }, + "method_chaining_indentation": true, + "multiline_whitespace_before_semicolons": { + "strategy": "new_line_for_chained_calls" + }, + "operator_linebreak": { + "position": "beginning" + }, + "ordered_class_elements": { + "order": [ + "use_trait", + "constant", + "case", + "property", + "method" + ] + }, + "phpdoc_align": true, + "phpdoc_separation": { + "groups": [ + [ + "param" + ], + [ + "return" + ] + ] + }, + "php_unit_method_casing": { + "case": "snake_case" + }, + "return_type_declaration": true, + "simplified_null_return": true, + "single_trait_insert_per_statement": true, + "trailing_comma_in_multiline": { + "elements": [ + "parameters", + "arguments" + ] + } + }, + "exclude": [ + "vendor" + ] +} diff --git a/src/Math.php b/src/Math.php index 877b5e8..ad4171c 100644 --- a/src/Math.php +++ b/src/Math.php @@ -1,27 +1,24 @@ precision = static::$globalPrecision; @@ -30,42 +27,25 @@ public function __construct($number) $this->number = static::validateInputNumber($number); } - /** - * @return string - */ public function __toString(): string { return static::normalizeNumber($this->number); } - /** - * @param string|int $number - * - * @throws \InvalidArgumentException - * - * @return static - */ - public static function number($number): self + public static function number(string|int $number): static { return new static($number); } /** * convert any based value bellow or equals to 64 to its decimal value - * - * @param string|int $number - * @param int $base - * - * @throws \InvalidArgumentException - * - * @return static */ - public static function fromBase($number, int $base): self + public static function fromBase(string|int $number, int $base): static { // trim base 64 padding char, only positive $number = trim($number, ' =-'); - if ($number === '' || strpos($number, '.') !== false) { - throw new \InvalidArgumentException('Argument number is not an integer'); + if ($number === '' || str_contains($number, '.')) { + throw new InvalidArgumentException('Argument number is not an integer'); } $baseChar = static::getBaseChar($base); @@ -81,79 +61,38 @@ public static function fromBase($number, int $base): self return new static(static::bcDec2Base($number, $base, $baseChar)); } - /** - * @param string|int|static $number - * - * @throws \InvalidArgumentException - * - * @return bool - */ - public function gte($number): bool + public function gte(string|int|Math $number): bool { return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) >= 0); } - /** - * @param string|int|static $number - * - * @throws \InvalidArgumentException - * - * @return bool - */ - public function gt($number): bool + public function gt(string|int|Math $number): bool { return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) === 1); } - /** - * @param string|int|static $number - * - * @throws \InvalidArgumentException - * - * @return bool - */ - public function lte($number): bool + public function lte(string|int|Math $number): bool { return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) <= 0); } - /** - * @param string|int|static $number - * - * @throws \InvalidArgumentException - * - * @return bool - */ - public function lt($number): bool + public function lt(string|int|Math $number): bool { return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) === -1); } - /** - * @param string|int|static $number - * - * @throws \InvalidArgumentException - * - * @return bool - */ - public function eq($number): bool + public function eq(string|int|Math $number): bool { return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) === 0); } /** * convert decimal value to any other base bellow or equals to 64 - * - * @param string|int $base - * - * @throws \InvalidArgumentException - * - * @return string */ - public function toBase($base): string + public function toBase(string|int $base): string { if ($this->normalize()->hasDecimals()) { - throw new \InvalidArgumentException('Argument number is not an integer'); + throw new InvalidArgumentException('Argument number is not an integer'); } // do not mutate, only support positive integers @@ -175,22 +114,15 @@ public function toBase($base): string return (string) $result; } - /** - * @param string|int $decimals - * @param string $decPoint - * @param string $thousandsSep - * - * @return string - */ - public function format($decimals = 0, string $decPoint = '.', string $thousandsSep = ' '): string + public function format(string|int $decimals = 0, string $decPoint = '.', string $thousandsSep = ' '): string { $decimals = max(0, (int) $decimals); $dec = ''; // do not mutate - $number = (new static($this))->round($decimals)->normalize(); - $sign = $number->isPositive() ? '' : '-'; + $number = (new static($this))->round($decimals)->normalize(); + $sign = $number->isPositive() ? '' : '-'; if ($number->abs()->hasDecimals()) { - list($number, $dec) = explode('.', (string) $number); + [$number, $dec] = explode('.', (string) $number); } if ($decimals) { @@ -199,6 +131,11 @@ public function format($decimals = 0, string $decPoint = '.', string $thousandsS return $sign . preg_replace("/(?<=\d)(?=(\d{3})+(?!\d))/", $thousandsSep, $number) . ($decimals ? $decPoint . $dec : ''); } + + public function jsonSerialize(): string + { + return $this->__toString(); + } } // OMG a dynamic static anti pattern ^^ diff --git a/src/MathBaseAbstract.php b/src/MathBaseAbstract.php index ca2db3d..776b12d 100644 --- a/src/MathBaseAbstract.php +++ b/src/MathBaseAbstract.php @@ -1,14 +1,16 @@ */ - protected static $baseChars = [ + protected static array $baseChars = [ 36 => self::BASECHAR_36, 62 => self::BASECHAR_62, 64 => self::BASECHAR_64, @@ -52,75 +54,44 @@ abstract class MathBaseAbstract /** * if set, will be used as default for all consecutive instances - * - * @var int */ - protected static $globalPrecision; + protected static int $globalPrecision; /** * Used in static context, aligned with $globalPrecision, default to self::PRECISION - * - * @var int */ - protected static $staticPrecision = self::PRECISION; - - /** - * @var bool - */ - protected static $gmpSupport; - - /** - * @var string - */ - protected $number; + protected static int $staticPrecision = self::PRECISION; + protected static ?bool $gmpSupport = null; + protected string $number; /** * Instance precision, initialized with globalPrecision, default to self::PRECISION - * - * @var int */ - protected $precision = self::PRECISION; + protected int $precision = self::PRECISION; - /** - * @return string - */ public function getNumber(): string { return $this->number; } - /** - * @return bool - */ public function isPositive(): bool { return $this->number[0] !== '-'; } - /** - * @return bool - */ public function hasDecimals(): bool { - return strpos($this->number, '.') !== false; + return str_contains($this->number, '.'); } - /** - * @return $this - */ - public function normalize(): self + public function normalize(): static { $this->number = static::normalizeReal($this->number); return $this; } - /** - * @param string|int $precision - * - * @return $this - */ - public function setPrecision($precision): self + public function setPrecision(string|int $precision): static { // even INT_32 should be enough precision $this->precision = max(0, (int) $precision); @@ -128,27 +99,23 @@ public function setPrecision($precision): self return $this; } - /** - * @param string|int $precision - */ - public static function setGlobalPrecision($precision) + public static function setGlobalPrecision(string|int $precision): void { // even INT_32 should be enough precision static::$globalPrecision = max(0, (int) $precision); static::$staticPrecision = static::$globalPrecision; } - /** - * @param bool $disable - * - * @return bool - */ - public static function gmpSupport(bool $disable = false): bool + public static function gmpSupport(?bool $disable = false): bool { - if ($disable) { + if ($disable || $disable === null) { return static::$gmpSupport = false; } + if (static::$gmpSupport !== null) { + return static::$gmpSupport; + } + return static::$gmpSupport = function_exists('gmp_init'); } @@ -156,41 +123,25 @@ public static function gmpSupport(bool $disable = false): bool * There is no way around it, if you want to trust bcmath * you need to feed it with VALID numbers * Things like '1.1.1' or '12E16'are all 0 in bcmath world - * - * @param string|int $number - * - * @return bool */ - public static function isNumber($number): bool + public static function isNumber(string|int $number): bool { return (bool) preg_match('`^[+-]?([0-9]+(\.[0-9]+)?|\.[0-9]+)$`', $number); } /** * Validation flavour of normalization logic - * - * @param string|int $number - * @param string|int|null $default - * - * @return string|null */ - public static function normalizeNumber($number, $default = null): ? string + public static function normalizeNumber(string|int $number, string|int $default = null): ?string { - if (!static::isNumber($number)) { + if (! static::isNumber($number)) { return $default; } return static::normalizeReal($number); } - /** - * @param string|int $base - * - * @throws \InvalidArgumentException - * - * @return string - */ - public static function getBaseChar($base): string + public static function getBaseChar(string|int $base): string { if (isset(static::$baseChars[$base])) { return static::$baseChars[$base]; @@ -214,15 +165,9 @@ public static function getBaseChar($base): string * * WARNING This method requires ext-gmp * - * @param string|int $number - * @param string|int $fromBase - * @param string|int $toBase - * - * @return string - * * @internal param int $base */ - public static function baseConvert($number, $fromBase = 10, $toBase = 62): string + public static function baseConvert(string|int $number, string|int $fromBase = 10, string|int $toBase = 62): string { return gmp_strval(gmp_init($number, $fromBase), $toBase); } @@ -230,55 +175,42 @@ public static function baseConvert($number, $fromBase = 10, $toBase = 62): strin /** * Normalize a valid real number * removes preceding / trailing 0 and + - * - * @param string $number - * - * @return string */ - protected static function normalizeReal(string $number): string + protected static function normalizeReal(string|int $number): string { $sign = $number[0] === '-' ? '-' : ''; $number = ltrim($number, '0+-'); - if (strpos($number, '.') !== false) { + if (str_contains($number, '.')) { // also clear trailing 0 / .0000 - list($number, $dec) = explode('.', $number); - $dec = ($dec = rtrim($dec, '0.')) ? '.' . $dec : ''; - $number = ($number ?: '0') . $dec; + [$number, $dec] = explode('.', $number); + $dec = ($dec = rtrim($dec, '0.')) ? '.' . $dec : ''; + $number = ($number ?: '0') . $dec; } return $number ? $sign . $number : '0'; } /** - * @param int $base - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ protected static function validateBase(int $base): void { - if ($base < 2 || $base > self::BASE_MAX || $base > 64) { - throw new \InvalidArgumentException('Argument base is not valid, base 2 to 64 are supported'); + if ($base < 2 || $base > self::BASE_MAX || ! static::gmpSupport() && $base > 62) { + throw new InvalidArgumentException('Argument base is not valid, base 2 to ' . (static::gmpSupport() ? 64 : 62) . ' are supported'); } } - /** - * @param string|int $number - * @param string|int $base - * @param string $baseChar - * - * @return string - */ - protected static function bcDec2Base($number, $base, string $baseChar) + protected static function bcDec2Base(string|int $number, string|int $base, string $baseChar): string { $result = ''; $numberLen = strlen($number); // Now loop through each digit in the number - for ($i = $numberLen - 1; $i >= 0; --$i) { + for ($i = $numberLen - 1; $i >= 0; $i--) { $char = $number[$i]; // extract the last char from the number $ord = strpos($baseChar, $char); // get the decimal value if ($ord === false || $ord > $base) { - throw new \InvalidArgumentException('Argument number is invalid'); + throw new InvalidArgumentException('Argument number is invalid'); } // Now convert the value+position to decimal @@ -288,22 +220,15 @@ protected static function bcDec2Base($number, $base, string $baseChar) return $result ? $result : '0'; } - /** - * @param string|int|static $number - * - * @throws \InvalidArgumentException - * - * @return string - */ - protected static function validateInputNumber($number): string + protected static function validateInputNumber(string|int|Math $number): string { if ($number instanceof static) { return $number->getNumber(); } $number = trim($number); - if (!static::isNumber($number)) { - throw new \InvalidArgumentException('Argument number is not valid'); + if (! static::isNumber($number)) { + throw new InvalidArgumentException('Argument number is not valid'); } return $number; @@ -312,16 +237,12 @@ protected static function validateInputNumber($number): string /** * @param string|int $integer up to INT_32|64 since it's only used for things * like exponents, it should be enough - * - * @throws \InvalidArgumentException - * - * @return string */ - protected static function validatePositiveInteger($integer) + protected static function validatePositiveInteger(string|int $integer): string { $integer = max(0, (int) $integer); - if (!$integer) { - throw new \InvalidArgumentException('Argument number is not valid'); + if (! $integer) { + throw new InvalidArgumentException('Argument number is not valid'); } return (string) $integer; diff --git a/src/MathOpsAbstract.php b/src/MathOpsAbstract.php index e1ee9a3..69c9ed5 100644 --- a/src/MathOpsAbstract.php +++ b/src/MathOpsAbstract.php @@ -1,8 +1,8 @@ number = bcadd($this->number, static::validateInputNumber($number), $this->precision); @@ -30,14 +23,7 @@ public function add(...$numbers): self return $this; } - /** - * @param (string|int|static)[] $numbers - * - * @throws \InvalidArgumentException - * - * @return static - */ - public function sub(...$numbers): self + public function sub(string|int|Math ...$numbers): static { foreach ($numbers as $number) { $this->number = bcsub($this->number, static::validateInputNumber($number), $this->precision); @@ -46,14 +32,7 @@ public function sub(...$numbers): self return $this; } - /** - * @param (string|int|static)[] $numbers - * - * @throws \InvalidArgumentException - * - * @return static - */ - public function mul(...$numbers): self + public function mul(string|int|Math ...$numbers): static { foreach ($numbers as $number) { $this->number = bcmul($this->number, static::validateInputNumber($number), $this->precision); @@ -62,14 +41,7 @@ public function mul(...$numbers): self return $this; } - /** - * @param (string|int|static)[] $numbers - * - * @throws \InvalidArgumentException - * - * @return static - */ - public function div(...$numbers): self + public function div(string|int|Math ...$numbers): static { foreach ($numbers as $number) { $this->number = bcdiv($this->number, static::validateInputNumber($number), $this->precision); @@ -78,65 +50,35 @@ public function div(...$numbers): self return $this; } - /** - * @return static - */ - public function sqrt(): self + public function sqrt(): static { $this->number = bcsqrt($this->number, $this->precision); return $this; } - /** - * @param string|int $exponent - * - * @throws \InvalidArgumentException - * - * @return static - */ - public function pow($exponent): self + public function pow(string|int $exponent): static { $this->number = bcpow($this->number, static::validatePositiveInteger($exponent), $this->precision); return $this; } - /** - * @param string|int $modulus - * - * @throws \InvalidArgumentException - * - * @return static - */ - public function mod($modulus): self + public function mod(string|int $modulus): static { $this->number = bcmod($this->number, static::validatePositiveInteger($modulus)); return $this; } - /** - * @param string|int $exponent - * @param string|int $modulus - * - * @throws \InvalidArgumentException - * - * @return static - */ - public function powMod($exponent, $modulus): self + public function powMod(string|int $exponent, string|int $modulus): static { $this->number = bcpowmod($this->number, static::validatePositiveInteger($exponent), static::validatePositiveInteger($modulus)); return $this; } - /** - * @param string|int $precision - * - * @return static - */ - public function round($precision = 0): self + public function round(string|int $precision = 0): static { $precision = max(0, (int) $precision); if ($this->hasDecimals()) { @@ -152,10 +94,7 @@ public function round($precision = 0): self return $this; } - /** - * @return static - */ - public function ceil(): self + public function ceil(): static { if ($this->hasDecimals()) { if ($this->isPositive()) { @@ -170,10 +109,7 @@ public function ceil(): self return $this; } - /** - * @return static - */ - public function floor(): self + public function floor(): static { if ($this->hasDecimals()) { if ($this->isPositive()) { @@ -188,10 +124,7 @@ public function floor(): self return $this; } - /** - * @return static - */ - public function abs(): self + public function abs(): static { $this->number = ltrim($this->number, '-'); @@ -200,12 +133,8 @@ public function abs(): self /** * returns the highest number among all arguments - * - * @param (string|int|static)[] $numbers - * - * @return static */ - public function max(...$numbers): self + public function max(string|int|Math ...$numbers): static { foreach ($numbers as $number) { if (bccomp($number = static::validateInputNumber($number), $this->number, $this->precision) === 1) { @@ -218,14 +147,8 @@ public function max(...$numbers): self /** * returns the smallest number among all arguments - * - * @param (string|int|static)[] $numbers - * - * @throws \InvalidArgumentException - * - * @return static */ - public function min(...$numbers): self + public function min(string|int|Math ...$numbers): static { foreach ($numbers as $number) { if (bccomp($number = static::validateInputNumber($number), $this->number, $this->precision) === -1) { diff --git a/tests/MathTest.php b/tests/MathTest.php index a068e3a..a5be657 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -1,8 +1,8 @@ assertSame($expected, (string) Math::number($number)->format($decimals, $dec_point, $thousands_sep)); } - /** - * @return array - */ - public function compData() + public static function compData(): array { return [ [ - 'left' => '255173029255255255', - 'operator'=> '<', - 'right' => '255173', - 'expected'=> false, + 'left' => '255173029255255255', + 'operator' => '<', + 'right' => '255173', + 'expected' => false, ], [ 'left' => '255173029255255255', @@ -96,10 +83,10 @@ public function compData() 'expected' => true, ], [ - 'left' => '54', - 'operator'=> '<=', - 'right' => '0', - 'expected'=> false, + 'left' => '54', + 'operator' => '<=', + 'right' => '0', + 'expected' => false, ], [ 'left' => '54', @@ -170,60 +157,50 @@ public function compData() ]; } - /** - * @dataProvider compData - * - * @param string|int|Math $left - * @param string $operator - * @param string|int|Math $right - * @param mixed $expected - */ - public function testComp($left, string $operator, $right, bool $expected) + #[DataProvider('compData')] + public function test_comp(string|int|Math $left, string $operator, $right, bool $expected) { switch ($operator) { case '<': $this->assertSame( $expected, - Math::number($left)->lt($right) + Math::number($left)->lt($right), ); break; case '<=': $this->assertSame( $expected, - Math::number($left)->lte($right) + Math::number($left)->lte($right), ); break; case '>': $this->assertSame( $expected, - Math::number($left)->gt($right) + Math::number($left)->gt($right), ); break; case '>=': $this->assertSame( $expected, - Math::number($left)->gte($right) + Math::number($left)->gte($right), ); break; case '=': $this->assertSame( $expected, - Math::number($left)->eq($right) + Math::number($left)->eq($right), ); break; case '!=': $this->assertSame( $expected, - !Math::number($left)->eq($right) + ! Math::number($left)->eq($right), ); break; } } - /** - * @return array - */ - public function roundData() + public static function roundData(): array { return [ [ @@ -309,22 +286,13 @@ public function roundData() ]; } - /** - * @dataProvider roundData - * - * @param string|int|Math $number - * @param int $precision - * @param string $expected - */ - public function testRound($number, int $precision, string $expected) + #[DataProvider('roundData')] + public function test_round(string|int|Math $number, int $precision, string $expected) { $this->assertSame($expected, (string) Math::number($number)->round($precision)); } - /** - * @return array - */ - public function maxMinData() + public static function maxMinData(): array { return [ [ @@ -371,43 +339,34 @@ public function maxMinData() } /** - * @dataProvider maxMinData - * * @param (string|int|Math)[] $param - * @param string|int|Math $min - * @param string|int|Math $max */ - public function testMax(array $param, $min, $max) + #[DataProvider('maxMinData')] + public function test_max(array $param, string|int|Math $min, string|int|Math $max) { $first = $param[0]; unset($param[0]); $this->assertSame( $max, - (string) Math::number($first)->max(...$param) + (string) Math::number($first)->max(...$param), ); } /** - * @dataProvider maxMinData - * * @param (string|int|Math)[] $param - * @param string $min - * @param string $max */ - public function testMin(array $param, $min, $max) + #[DataProvider('maxMinData')] + public function test_min(array $param, string|int|Math $min, string|int|Math $max) { $first = $param[0]; unset($param[0]); $this->assertSame( $min, - (string) Math::number($first)->min(...$param) + (string) Math::number($first)->min(...$param), ); } - /** - * @return array - */ - public function normalizeData() + public static function normalizeData(): array { return [ [ @@ -489,36 +448,22 @@ public function normalizeData() ]; } - /** - * @dataProvider normalizeData - * - * @param string|int|Math $number - * @param string $expected - */ - public function testNormalize($number, string $expected) + #[DataProvider('normalizeData')] + public function test_normalize(string|int|Math $number, string $expected) { $this->assertSame($expected, (string) Math::number($number)); } - /** - * @dataProvider addData - * - * @param string|int|Math $left - * @param string|int|Math $right - * @param string $expected - */ - public function testAdd($left, $right, string $expected) + #[DataProvider('addData')] + public function test_add(string|int|Math $left, string|int|Math $right, string $expected) { $this->assertSame( $expected, - (string) Math::number($left)->add($right) + (string) Math::number($left)->add($right), ); } - /** - * @return array - */ - public function addData() + public static function addData(): array { return [ [ @@ -549,25 +494,16 @@ public function addData() ]; } - /** - * @dataProvider subData - * - * @param string|int|Math $left - * @param string|int|Math $right - * @param string $expected - */ - public function testSub($left, $right, string $expected) + #[DataProvider('subData')] + public function test_sub(string|int|Math $left, string|int|Math $right, string $expected) { $this->assertSame( $expected, - (string) Math::number($left)->sub($right) + (string) Math::number($left)->sub($right), ); } - /** - * @return array - */ - public function subData() + public static function subData(): array { return [ [ @@ -603,31 +539,22 @@ public function subData() ]; } - /** - * @dataProvider mulDivData - * - * @param string|int|Math $left - * @param string|int|Math $right - * @param string $expected - */ - public function testMulDiv($left, $right, string $expected) + #[DataProvider('mulDivData')] + public function test_mul_div(string|int|Math $left, string|int|Math $right, string $expected) { $result = Math::number($left)->mul($right); $this->assertSame( $expected, - (string) $result + (string) $result, ); $this->assertSame( (string) Math::number($left), - (string) $result->div($right) + (string) $result->div($right), ); } - /** - * @return array - */ - public function mulDivData() + public static function mulDivData(): array { return [ [ @@ -653,30 +580,22 @@ public function mulDivData() ]; } - /** - * @dataProvider sqrtData - * - * @param string|int|Math $number - * @param string $expected - */ - public function testSqrt($number, string $expected) + #[DataProvider('sqrtData')] + public function test_sqrt(string|int|Math $number, string $expected) { $result = Math::number($number)->sqrt(); $this->assertSame( $expected, - (string) $result + (string) $result, ); $this->assertSame( (string) Math::number($number), - (string) $result->pow(2) + (string) $result->pow(2), ); } - /** - * @return array - */ - public function sqrtData() + public static function sqrtData(): array { $result = [ [ @@ -693,7 +612,7 @@ public function sqrtData() ], ]; - for ($i = 1; $i < 50; ++$i) { + for ($i = 1; $i < 50; $i++) { $number = mt_rand(1, 10000) . '.' . mt_rand(0, 10000); $result[] = [ 'number' => Math::number($number)->pow(2), @@ -704,25 +623,16 @@ public function sqrtData() return $result; } - /** - * @dataProvider modData - * - * @param string|int|Math $number - * @param string $mod - * @param string $expected - */ - public function testMod($number, string $mod, string $expected) + #[DataProvider('modData')] + public function test_mod(string|int|Math $number, string $mod, string $expected) { $this->assertSame( $expected, - (string) Math::number($number)->mod($mod) + (string) Math::number($number)->mod($mod), ); } - /** - * @return array - */ - public function modData() + public static function modData(): array { $result = [ [ @@ -742,7 +652,7 @@ public function modData() ], ]; - for ($i = 1; $i < 50; ++$i) { + for ($i = 1; $i < 50; $i++) { $number = mt_rand(1, 10000); $mod = mt_rand(1, 100); $result[] = [ @@ -755,28 +665,19 @@ public function modData() return $result; } - /** - * @dataProvider powModData - * - * @param string|int|Math $number - * @param string $pow - * @param string $mod - */ - public function testPowMod($number, string $pow, string $mod) + #[DataProvider('powModData')] + public function test_pow_mod(string|int|Math $number, string $pow, string $mod) { $this->assertSame( (string) Math::number($number)->powMod($pow, $mod), - (string) Math::number($number)->pow($pow)->mod($mod) + (string) Math::number($number)->pow($pow)->mod($mod), ); } - /** - * @return array - */ - public function powModData() + public static function powModData(): array { $result = []; - for ($i = 1; $i < 50; ++$i) { + for ($i = 1; $i < 50; $i++) { $result[] = [ 'number' => (string) mt_rand(1, 100000), 'pow' => (string) mt_rand(1, 1000), @@ -787,24 +688,16 @@ public function powModData() return $result; } - /** - * @dataProvider ceilData - * - * @param string|int|Math $number - * @param string $expected - */ - public function testCeil($number, string $expected) + #[DataProvider('ceilData')] + public function test_ceil(string|int|Math $number, string $expected) { $this->assertSame( $expected, - (string) Math::number($number)->ceil() + (string) Math::number($number)->ceil(), ); } - /** - * @return array - */ - public function ceilData() + public static function ceilData(): array { return [ [ @@ -842,24 +735,16 @@ public function ceilData() ]; } - /** - * @dataProvider floorData - * - * @param string|int|Math $number - * @param string $expected - */ - public function testFloor($number, string $expected) + #[DataProvider('floorData')] + public function test_floor(string|int|Math $number, string $expected) { $this->assertSame( $expected, - (string) Math::number($number)->floor() + (string) Math::number($number)->floor(), ); } - /** - * @return array - */ - public function floorData() + public static function floorData(): array { return [ [ @@ -897,24 +782,16 @@ public function floorData() ]; } - /** - * @dataProvider absData - * - * @param string|int|Math $number - * @param string $expected - */ - public function testAbs($number, string $expected) + #[DataProvider('absData')] + public function test_abs(string|int|Math $number, string $expected) { $this->assertSame( $expected, - (string) Math::number($number)->abs() + (string) Math::number($number)->abs(), ); } - /** - * @return array - */ - public function absData() + public static function absData(): array { return [ [ @@ -928,24 +805,16 @@ public function absData() ]; } - /** - * @dataProvider isNumberData - * - * @param string|int|Math $number - * @param bool $expected - */ - public function testIsNumber($number, bool $expected) + #[DataProvider('isNumberData')] + public function test_is_number(string|int|Math $number, bool $expected) { $this->assertSame( $expected, - Math::isNumber($number) + Math::isNumber($number), ); } - /** - * @return array - */ - public function isNumberData() + public static function isNumberData(): array { return [ [ @@ -1033,10 +902,7 @@ public function isNumberData() ]; } - /** - * @return array - */ - public function baseConvertData() + public static function baseConvertData(): array { return [ [ @@ -1069,7 +935,7 @@ public function baseConvertData() ], [ 'number' => '000255173029255255255', - 'base' => '63', + 'base' => Math::gmpSupport() ? '63' : '62', ], [ 'number' => '00025517302925525525', @@ -1097,7 +963,7 @@ public function baseConvertData() ], [ 'number' => '1000', - 'base' => '64', + 'base' => Math::gmpSupport() ? '64' : '62', ], [ 'number' => '0', @@ -1105,50 +971,47 @@ public function baseConvertData() ], [ 'number' => '9856565', - 'base' => '62', + 'base' => '61', ], ]; } - /** - * @dataProvider baseConvertData - * - * @param string|int|Math $number - * @param string $base - * - * @throws \InvalidArgumentException - */ - public function testBaseConvert($number, string $base) + #[DataProvider('baseConvertData')] + public function test_base_convert(string|int|Math $number, string $base) { - $this->assertSame( - (string) Math::number($number), - (string) Math::fromBase(Math::number($number)->toBase($base), $base) - ); - if ($base > 62) { + if ($base > 62 && ! Math::gmpSupport()) { + $this->markTestSkipped('Cannot test Base Conversion with bases 63 and 64 without ext-gmp'); + return; } $this->assertSame( (string) Math::number($number), - Math::normalizeNumber(Math::baseConvert(Math::baseConvert($number, 10, $base), $base, 10)) + (string) Math::fromBase(Math::number($number)->toBase($base), $base), ); - if (!Math::gmpSupport()) { + if (! Math::gmpSupport()) { return; } + $this->assertSame( + (string) Math::number($number), + Math::normalizeNumber(Math::baseConvert(Math::baseConvert($number, 10, $base), $base, 10)), + ); + $expected = gmp_strval(gmp_init((string) Math::number($number)), $base); $this->assertSame( $expected, - Math::baseConvert($number, 10, $base) + Math::baseConvert($number, 10, $base), ); Math::gmpSupport(true); $this->assertSame( $expected, - (string) Math::number($number)->toBase($base) + Math::number($number)->toBase($base), ); - Math::gmpSupport(false); + + Math::gmpSupport(null); } }