diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/LICENSE b/LICENSE index 9f8d320..0c9c63a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,28 @@ -MIT License +BSD 3-Clause License -Copyright (c) 2024 IDAP +Copyright (c) 2024, IDAP -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f2ce499 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Relational Namber + +PHP Class for Rational numbers manipulations + +## Installation + +The preferred way to install this extension is through [composer](https://getcomposer.org/download/). + +Either run + +``` +composer require idapgroup/rational-number +``` + +or add + +```json +{ + "require": { + "idapgroup/rational-number": "^1.0.0" + } +} +``` + +to the requirement section of your `composer.json` file. + +## Quickstart + +```php +$number = new Rational(100); +``` + +### Create number + +```php +$number = new Rational(100); +``` + +### Operations with numbers + +```php +$firstNumber = new Rational(100); +$secondNumber = new Rational(200); + +//Multiplication +$mulResult = $firstNumber->mul($secondNumber); + +//Division +$divResult = $firstNumber->div($secondNumber); + +//Summarizing +$sumResult = $firstNumber->add($secondNumber); + +//Subtraction +$subResult = $firstNumber->sub($secondNumber); + +//Сomparison +$equalsResult = $firstNumber->equals($secondNumber); + +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..979a6df --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "name": "idapgroup/rational-number", + "type": "library", + "description": "PHP Class for Rational numbers manipulations", + "homepage": "https://github.com/idapgroup/Rational-Number", + "license": "BSD-3-Clause", + "keywords": [ + "rational-number" + ], + "version": "1.0.0", + "authors": [ + { + "name": "Anton Petrov", + "email": "anton.petrov@idapgroup.com", + "homepage": "https://github.com/antonpetrov0233" + } + ], + "minimum-stability": "stable", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "IdapGroup\\RationalNumber\\": "src/" + } + } +} diff --git a/src/Rational.php b/src/Rational.php new file mode 100644 index 0000000..d0e0d37 --- /dev/null +++ b/src/Rational.php @@ -0,0 +1,171 @@ +significand = $significand; + $this->denominator = $denominator; + $this->base = $base; + + $this->normalize(); + } + + public function mul(Rational $that): Rational + { + return new self( + $this->significand * $that->significand, + $this->base + $that->base, + $this->denominator * $that->denominator + ); + } + + public function div(Rational $that): Rational + { + $ret = new self($this->significand * $that->denominator, $this->base - $that->base); + $ret->denominator = $this->denominator * $that->significand; + $ret->normalize(); + + return $ret; + } + + public function add(Rational $that): Rational + { + $baseDiff = $that->base - $this->base; + if ($baseDiff === 0) { + return new self( + $this->significand * $that->denominator + $that->significand * $this->denominator, + $this->base, + $this->denominator * $that->denominator + ); + } + if ($baseDiff > 0) { + return new self( + $this->significand * $that->denominator + $that->significand * 10 ** $baseDiff * $this->denominator, + $this->base, + $this->denominator * $that->denominator + ); + } + + return new self( + $this->significand * 10 ** -$baseDiff * $that->denominator + $that->significand * $this->denominator, + $that->base, + $this->denominator * $that->denominator + ); + } + + public function sub(Rational $that): Rational + { + return $this->add(new Rational(-$that->significand, $that->base, $that->denominator)); + } + + public function equals(Rational $that): bool + { + $d = $this->sub($that); + return $d->significand === 0 && $d->denominator === 1; + } + + public static function zero(): Rational + { + return new self(0, 0); + } + + public static function one(): Rational + { + return new self(1, 0); + } + + public static function fromNumber(int|float $number, int $digits = -1): Rational + { + if (is_int($number)) { + return new self($number, 0); + } + + if ($digits >= 0) { + return new self((int) round($number * 10 ** $digits), -$digits); + } + + $significand = $number; + $base = 0; + while ($significand - (int) $significand !== 0.0) { + $significand *= 10; + $base--; + } + + return new self((int) $significand, $base); + } + + public function toFloat(int $precision = null): float + { + if ($precision !== null) { + return round($this->significand / $this->denominator * 10 ** $this->base, $precision); + } + + return $this->significand / $this->denominator * 10 ** $this->base; + } + + /** + * @return int[] + */ + public function toSource(): array + { + return [$this->significand, $this->base, $this->denominator]; + } + + private static function gcd(int $a, int $b): int + { + if ($a === $b) { + return $a; + } + if ($a === 0 || $b === 0) { + return 0; + } + if ($a === 1 || $b === 1) { + return 1; + } + if (!($a & 1 || $b & 1)) { + return self::gcd($a >> 1, $b >> 1) << 1; + } + if (!($a & 1)) { + return self::gcd($a >> 1, $b); + } + if (!($b & 1)) { + return self::gcd($a, $b >> 1); + } + if ($a > $b) { + return self::gcd(($a - $b) >> 1, $b); + } + return self::gcd(($b - $a) >> 1, $a); + } + + private function normalize(): void + { + if ($this->significand === 0) { + $this->denominator = 1; + $this->base = 0; + + return; + } + + $gcd = self::gcd(abs($this->significand), abs($this->denominator)); + $this->significand = intdiv($this->significand, $gcd); + $this->denominator = intdiv($this->denominator, $gcd); + + while ($this->significand - ($ts = (int)($this->significand * .1)) * 10 === 0) { + $this->significand = $ts; + $this->base++; + } + + while ($this->denominator - ($td = (int)($this->denominator * .1)) * 10 === 0) { + $this->denominator = $td; + $this->base--; + } + } +}