.github/workflows/main.yml000064400000011173144760126070011622 0ustar00name: main on: [ push, pull_request ] jobs: test: runs-on: ubuntu-latest strategy: matrix: experimental: - false php-version: - '7.2' - '7.3' - '7.4' - '8.0' include: - php-version: '8.1' experimental: true name: PHP ${{ matrix.php-version }} steps: - name: Checkout uses: actions/checkout@v2 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} coverage: none - 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@v2 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Delete composer lock file id: composer-lock if: ${{ matrix.php-version == '8.1' }} run: | echo "::set-output name=flags::--ignore-platform-reqs" - name: Install dependencies run: composer update --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - name: Setup problem matchers for PHPUnit run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: "Run PHPUnit tests (Experimental: ${{ matrix.experimental }})" env: FAILURE_ACTION: "${{ matrix.experimental == true }}" run: vendor/bin/phpunit --verbose || $FAILURE_ACTION phpcs: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: 7.4 coverage: none tools: cs2pr - 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@v2 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Code style with PHP_CodeSniffer run: ./vendor/bin/phpcs -q --report=checkstyle classes/src/ | cs2pr versions: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: 7.4 coverage: none tools: cs2pr - 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@v2 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Code Version Compatibility check with PHP_CodeSniffer run: ./vendor/bin/phpcs -q --report-width=200 --report=summary,full classes/src/ --standard=PHPCompatibility --runtime-set testVersion 7.2- coverage: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: 7.4 coverage: pcov - 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@v2 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Test Coverage run: ./vendor/bin/phpunit --verbose --coverage-text README.md000064400000010633144760126070006035 0ustar00PHPComplex ========== --- PHP Class for handling Complex numbers [![Build Status](https://github.com/MarkBaker/PHPComplex/workflows/main/badge.svg)](https://github.com/MarkBaker/PHPComplex/actions) [![Total Downloads](https://img.shields.io/packagist/dt/markbaker/complex)](https://packagist.org/packages/markbaker/complex) [![Latest Stable Version](https://img.shields.io/github/v/release/MarkBaker/PHPComplex)](https://packagist.org/packages/markbaker/complex) [![License](https://img.shields.io/github/license/MarkBaker/PHPComplex)](https://packagist.org/packages/markbaker/complex) [![Complex Numbers](https://imgs.xkcd.com/comics/complex_numbers_2x.png)](https://xkcd.com/2028/) --- The library currently provides the following operations: - addition - subtraction - multiplication - division - division by - division into together with functions for - theta (polar theta angle) - rho (polar distance/radius) - conjugate * negative - inverse (1 / complex) - cos (cosine) - acos (inverse cosine) - cosh (hyperbolic cosine) - acosh (inverse hyperbolic cosine) - sin (sine) - asin (inverse sine) - sinh (hyperbolic sine) - asinh (inverse hyperbolic sine) - sec (secant) - asec (inverse secant) - sech (hyperbolic secant) - asech (inverse hyperbolic secant) - csc (cosecant) - acsc (inverse cosecant) - csch (hyperbolic secant) - acsch (inverse hyperbolic secant) - tan (tangent) - atan (inverse tangent) - tanh (hyperbolic tangent) - atanh (inverse hyperbolic tangent) - cot (cotangent) - acot (inverse cotangent) - coth (hyperbolic cotangent) - acoth (inverse hyperbolic cotangent) - sqrt (square root) - exp (exponential) - ln (natural log) - log10 (base-10 log) - log2 (base-2 log) - pow (raised to the power of a real number) --- # Usage To create a new complex object, you can provide either the real, imaginary and suffix parts as individual values, or as an array of values passed passed to the constructor; or a string representing the value. e.g ```php $real = 1.23; $imaginary = -4.56; $suffix = 'i'; $complexObject = new Complex\Complex($real, $imaginary, $suffix); ``` or as an array ```php $real = 1.23; $imaginary = -4.56; $suffix = 'i'; $arguments = [$real, $imaginary, $suffix]; $complexObject = new Complex\Complex($arguments); ``` or as a string ```php $complexString = '1.23-4.56i'; $complexObject = new Complex\Complex($complexString); ``` Complex objects are immutable: whenever you call a method or pass a complex value to a function that returns a complex value, a new Complex object will be returned, and the original will remain unchanged. This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Complex result). ## Performing Mathematical Operations To perform mathematical operations with Complex values, you can call the appropriate method against a complex value, passing other values as arguments ```php $complexString1 = '1.23-4.56i'; $complexString2 = '2.34+5.67i'; $complexObject = new Complex\Complex($complexString1); echo $complexObject->add($complexString2); ``` or use the static Operation methods ```php $complexString1 = '1.23-4.56i'; $complexString2 = '2.34+5.67i'; echo Complex\Operations::add($complexString1, $complexString2); ``` If you want to perform the same operation against multiple values (e.g. to add three or more complex numbers), then you can pass multiple arguments to any of the operations. You can pass these arguments as Complex objects, or as an array or string that will parse to a complex object. ## Using functions When calling any of the available functions for a complex value, you can either call the relevant method for the Complex object ```php $complexString = '1.23-4.56i'; $complexObject = new Complex\Complex($complexString); echo $complexObject->sinh(); ``` or use the static Functions methods ```php $complexString = '1.23-4.56i'; echo Complex\Functions::sinh($complexString); ``` In the case of the `pow()` function (the only implemented function that requires an additional argument) you need to pass both arguments when calling the function ```php $complexString = '1.23-4.56i'; $complexObject = new Complex\Complex($complexString); echo Complex\Functions::pow($complexObject, 2); ``` or pass the additional argument when calling the method ```php $complexString = '1.23-4.56i'; $complexObject = new Complex\Complex($complexString); echo $complexObject->pow(2); ``` classes/src/Complex.php000064400000026017144760126070011125 0ustar00realPart = (float) $realPart; $this->imaginaryPart = (float) $imaginaryPart; $this->suffix = strtolower($suffix ?? ''); } /** * Gets the real part of this complex number * * @return Float */ public function getReal(): float { return $this->realPart; } /** * Gets the imaginary part of this complex number * * @return Float */ public function getImaginary(): float { return $this->imaginaryPart; } /** * Gets the suffix of this complex number * * @return String */ public function getSuffix(): string { return $this->suffix; } /** * Returns true if this is a real value, false if a complex value * * @return Bool */ public function isReal(): bool { return $this->imaginaryPart == 0.0; } /** * Returns true if this is a complex value, false if a real value * * @return Bool */ public function isComplex(): bool { return !$this->isReal(); } public function format(): string { $str = ""; if ($this->imaginaryPart != 0.0) { if (\abs($this->imaginaryPart) != 1.0) { $str .= $this->imaginaryPart . $this->suffix; } else { $str .= (($this->imaginaryPart < 0.0) ? '-' : '') . $this->suffix; } } if ($this->realPart != 0.0) { if (($str) && ($this->imaginaryPart > 0.0)) { $str = "+" . $str; } $str = $this->realPart . $str; } if (!$str) { $str = "0.0"; } return $str; } public function __toString(): string { return $this->format(); } /** * Validates whether the argument is a valid complex number, converting scalar or array values if possible * * @param mixed $complex The value to validate * @return Complex * @throws Exception If the argument isn't a Complex number or cannot be converted to one */ public static function validateComplexArgument($complex): Complex { if (is_scalar($complex) || is_array($complex)) { $complex = new Complex($complex); } elseif (!is_object($complex) || !($complex instanceof Complex)) { throw new Exception('Value is not a valid complex number'); } return $complex; } /** * Returns the reverse of this complex number * * @return Complex */ public function reverse(): Complex { return new Complex( $this->imaginaryPart, $this->realPart, ($this->realPart == 0.0) ? null : $this->suffix ); } public function invertImaginary(): Complex { return new Complex( $this->realPart, $this->imaginaryPart * -1, ($this->imaginaryPart == 0.0) ? null : $this->suffix ); } public function invertReal(): Complex { return new Complex( $this->realPart * -1, $this->imaginaryPart, ($this->imaginaryPart == 0.0) ? null : $this->suffix ); } protected static $functions = [ 'abs', 'acos', 'acosh', 'acot', 'acoth', 'acsc', 'acsch', 'argument', 'asec', 'asech', 'asin', 'asinh', 'atan', 'atanh', 'conjugate', 'cos', 'cosh', 'cot', 'coth', 'csc', 'csch', 'exp', 'inverse', 'ln', 'log2', 'log10', 'negative', 'pow', 'rho', 'sec', 'sech', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'theta', ]; protected static $operations = [ 'add', 'subtract', 'multiply', 'divideby', 'divideinto', ]; /** * Returns the result of the function call or operation * * @return Complex|float * @throws Exception|\InvalidArgumentException */ public function __call($functionName, $arguments) { $functionName = strtolower(str_replace('_', '', $functionName)); // Test for function calls if (in_array($functionName, self::$functions, true)) { return Functions::$functionName($this, ...$arguments); } // Test for operation calls if (in_array($functionName, self::$operations, true)) { return Operations::$functionName($this, ...$arguments); } throw new Exception('Complex Function or Operation does not exist'); } } classes/src/Exception.php000064400000000361144760126070011446 0ustar00getReal() - $invsqrt->getImaginary(), $complex->getImaginary() + $invsqrt->getReal() ); $log = self::ln($adjust); return new Complex( $log->getImaginary(), -1 * $log->getReal() ); } /** * Returns the inverse hyperbolic cosine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function acosh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal() && ($complex->getReal() > 1)) { return new Complex(\acosh($complex->getReal())); } $acosh = self::acos($complex) ->reverse(); if ($acosh->getReal() < 0.0) { $acosh = $acosh->invertReal(); } return $acosh; } /** * Returns the inverse cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acot($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::atan(self::inverse($complex)); } /** * Returns the inverse hyperbolic cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acoth($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::atanh(self::inverse($complex)); } /** * Returns the inverse cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acsc($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::asin(self::inverse($complex)); } /** * Returns the inverse hyperbolic cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acsch($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::asinh(self::inverse($complex)); } /** * Returns the argument of a complex number. * Also known as the theta of the complex number, i.e. the angle in radians * from the real axis to the representation of the number in polar coordinates. * * This function is a synonym for theta() * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The argument (or theta) value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * * @see theta */ public static function argument($complex): float { return self::theta($complex); } /** * Returns the inverse secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function asec($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::acos(self::inverse($complex)); } /** * Returns the inverse hyperbolic secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function asech($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::acosh(self::inverse($complex)); } /** * Returns the inverse sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function asin($complex): Complex { $complex = Complex::validateComplexArgument($complex); $square = Operations::multiply($complex, $complex); $invsqrt = new Complex(1.0); $invsqrt = Operations::subtract($invsqrt, $square); $invsqrt = self::sqrt($invsqrt); $adjust = new Complex( $invsqrt->getReal() - $complex->getImaginary(), $invsqrt->getImaginary() + $complex->getReal() ); $log = self::ln($adjust); return new Complex( $log->getImaginary(), -1 * $log->getReal() ); } /** * Returns the inverse hyperbolic sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function asinh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal() && ($complex->getReal() > 1)) { return new Complex(\asinh($complex->getReal())); } $asinh = clone $complex; $asinh = $asinh->reverse() ->invertReal(); $asinh = self::asin($asinh); return $asinh->reverse() ->invertImaginary(); } /** * Returns the inverse tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function atan($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\atan($complex->getReal())); } $t1Value = new Complex(-1 * $complex->getImaginary(), $complex->getReal()); $uValue = new Complex(1, 0); $d1Value = clone $uValue; $d1Value = Operations::subtract($d1Value, $t1Value); $d2Value = Operations::add($t1Value, $uValue); $uResult = $d1Value->divideBy($d2Value); $uResult = self::ln($uResult); return new Complex( (($uResult->getImaginary() == M_PI) ? -M_PI : $uResult->getImaginary()) * -0.5, $uResult->getReal() * 0.5, $complex->getSuffix() ); } /** * Returns the inverse hyperbolic tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function atanh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { $real = $complex->getReal(); if ($real >= -1.0 && $real <= 1.0) { return new Complex(\atanh($real)); } else { return new Complex(\atanh(1 / $real), (($real < 0.0) ? M_PI_2 : -1 * M_PI_2)); } } $iComplex = clone $complex; $iComplex = $iComplex->invertImaginary() ->reverse(); return self::atan($iComplex) ->invertReal() ->reverse(); } /** * Returns the complex conjugate of a complex number * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The conjugate of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function conjugate($complex): Complex { $complex = Complex::validateComplexArgument($complex); return new Complex( $complex->getReal(), -1 * $complex->getImaginary(), $complex->getSuffix() ); } /** * Returns the cosine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function cos($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\cos($complex->getReal())); } return self::conjugate( new Complex( \cos($complex->getReal()) * \cosh($complex->getImaginary()), \sin($complex->getReal()) * \sinh($complex->getImaginary()), $complex->getSuffix() ) ); } /** * Returns the hyperbolic cosine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function cosh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\cosh($complex->getReal())); } return new Complex( \cosh($complex->getReal()) * \cos($complex->getImaginary()), \sinh($complex->getReal()) * \sin($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function cot($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::inverse(self::tan($complex)); } /** * Returns the hyperbolic cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function coth($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::inverse(self::tanh($complex)); } /** * Returns the cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function csc($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::inverse(self::sin($complex)); } /** * Returns the hyperbolic cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function csch($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::inverse(self::sinh($complex)); } /** * Returns the exponential of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The exponential of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function exp($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && (\abs($complex->getImaginary()) == M_PI)) { return new Complex(-1.0, 0.0); } $rho = \exp($complex->getReal()); return new Complex( $rho * \cos($complex->getImaginary()), $rho * \sin($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the inverse of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If function would result in a division by zero */ public static function inverse($complex): Complex { $complex = clone Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { throw new InvalidArgumentException('Division by zero'); } return $complex->divideInto(1.0); } /** * Returns the natural logarithm of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The natural logarithm of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If the real and the imaginary parts are both zero */ public static function ln($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { throw new InvalidArgumentException(); } return new Complex( \log(self::rho($complex)), self::theta($complex), $complex->getSuffix() ); } /** * Returns the base-2 logarithm of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The base-2 logarithm of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If the real and the imaginary parts are both zero */ public static function log2($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { throw new InvalidArgumentException(); } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { return new Complex(\log($complex->getReal(), 2), 0.0, $complex->getSuffix()); } return self::ln($complex) ->multiply(\log(Complex::EULER, 2)); } /** * Returns the common logarithm (base 10) of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The common logarithm (base 10) of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If the real and the imaginary parts are both zero */ public static function log10($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { throw new InvalidArgumentException(); } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { return new Complex(\log10($complex->getReal()), 0.0, $complex->getSuffix()); } return self::ln($complex) ->multiply(\log10(Complex::EULER)); } /** * Returns the negative of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The negative value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * * @see rho * */ public static function negative($complex): Complex { $complex = Complex::validateComplexArgument($complex); return new Complex( -1 * $complex->getReal(), -1 * $complex->getImaginary(), $complex->getSuffix() ); } /** * Returns a complex number raised to a power. * * @param Complex|mixed $complex Complex number or a numeric value. * @param float|integer $power The power to raise this value to * @return Complex The complex argument raised to the real power. * @throws Exception If the power argument isn't a valid real */ public static function pow($complex, $power): Complex { $complex = Complex::validateComplexArgument($complex); if (!is_numeric($power)) { throw new Exception('Power argument must be a real number'); } if ($complex->getImaginary() == 0.0 && $complex->getReal() >= 0.0) { return new Complex(\pow($complex->getReal(), $power)); } $rValue = \sqrt(($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary())); $rPower = \pow($rValue, $power); $theta = $complex->argument() * $power; if ($theta == 0) { return new Complex(1); } return new Complex($rPower * \cos($theta), $rPower * \sin($theta), $complex->getSuffix()); } /** * Returns the rho of a complex number. * This is the distance/radius from the centrepoint to the representation of the number in polar coordinates. * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The rho value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function rho($complex): float { $complex = Complex::validateComplexArgument($complex); return \sqrt( ($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary()) ); } /** * Returns the secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function sec($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::inverse(self::cos($complex)); } /** * Returns the hyperbolic secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function sech($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::inverse(self::cosh($complex)); } /** * Returns the sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function sin($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\sin($complex->getReal())); } return new Complex( \sin($complex->getReal()) * \cosh($complex->getImaginary()), \cos($complex->getReal()) * \sinh($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the hyperbolic sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function sinh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\sinh($complex->getReal())); } return new Complex( \sinh($complex->getReal()) * \cos($complex->getImaginary()), \cosh($complex->getReal()) * \sin($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the square root of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The Square root of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function sqrt($complex): Complex { $complex = Complex::validateComplexArgument($complex); $theta = self::theta($complex); $delta1 = \cos($theta / 2); $delta2 = \sin($theta / 2); $rho = \sqrt(self::rho($complex)); return new Complex($delta1 * $rho, $delta2 * $rho, $complex->getSuffix()); } /** * Returns the tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If function would result in a division by zero */ public static function tan($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\tan($complex->getReal())); } $real = $complex->getReal(); $imaginary = $complex->getImaginary(); $divisor = 1 + \pow(\tan($real), 2) * \pow(\tanh($imaginary), 2); if ($divisor == 0.0) { throw new InvalidArgumentException('Division by zero'); } return new Complex( \pow(self::sech($imaginary)->getReal(), 2) * \tan($real) / $divisor, \pow(self::sec($real)->getReal(), 2) * \tanh($imaginary) / $divisor, $complex->getSuffix() ); } /** * Returns the hyperbolic tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function tanh($complex): Complex { $complex = Complex::validateComplexArgument($complex); $real = $complex->getReal(); $imaginary = $complex->getImaginary(); $divisor = \cos($imaginary) * \cos($imaginary) + \sinh($real) * \sinh($real); if ($divisor == 0.0) { throw new InvalidArgumentException('Division by zero'); } return new Complex( \sinh($real) * \cosh($real) / $divisor, 0.5 * \sin(2 * $imaginary) / $divisor, $complex->getSuffix() ); } /** * Returns the theta of a complex number. * This is the angle in radians from the real axis to the representation of the number in polar coordinates. * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The theta value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function theta($complex): float { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0) { if ($complex->isReal()) { return 0.0; } elseif ($complex->getImaginary() < 0.0) { return M_PI / -2; } return M_PI / 2; } elseif ($complex->getReal() > 0.0) { return \atan($complex->getImaginary() / $complex->getReal()); } elseif ($complex->getImaginary() < 0.0) { return -(M_PI - \atan(\abs($complex->getImaginary()) / \abs($complex->getReal()))); } return M_PI - \atan($complex->getImaginary() / \abs($complex->getReal())); } } classes/src/Operations.php000064400000016120144760126070011633 0ustar00isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } $real = $result->getReal() + $complex->getReal(); $imaginary = $result->getImaginary() + $complex->getImaginary(); $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Divides two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to divide * @return Complex */ public static function divideby(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { throw new InvalidArgumentException('Division by zero'); } $delta1 = ($result->getReal() * $complex->getReal()) + ($result->getImaginary() * $complex->getImaginary()); $delta2 = ($result->getImaginary() * $complex->getReal()) - ($result->getReal() * $complex->getImaginary()); $delta3 = ($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary()); $real = $delta1 / $delta3; $imaginary = $delta2 / $delta3; $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Divides two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to divide * @return Complex */ public static function divideinto(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } if ($result->getReal() == 0.0 && $result->getImaginary() == 0.0) { throw new InvalidArgumentException('Division by zero'); } $delta1 = ($complex->getReal() * $result->getReal()) + ($complex->getImaginary() * $result->getImaginary()); $delta2 = ($complex->getImaginary() * $result->getReal()) - ($complex->getReal() * $result->getImaginary()); $delta3 = ($result->getReal() * $result->getReal()) + ($result->getImaginary() * $result->getImaginary()); $real = $delta1 / $delta3; $imaginary = $delta2 / $delta3; $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Multiplies two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to multiply * @return Complex */ public static function multiply(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } $real = ($result->getReal() * $complex->getReal()) - ($result->getImaginary() * $complex->getImaginary()); $imaginary = ($result->getReal() * $complex->getImaginary()) + ($result->getImaginary() * $complex->getReal()); $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Subtracts two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to subtract * @return Complex */ public static function subtract(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } $real = $result->getReal() - $complex->getReal(); $imaginary = $result->getImaginary() - $complex->getImaginary(); $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } } composer.json000064400000002077144760126070007303 0ustar00{ "name": "markbaker/complex", "type": "library", "description": "PHP Class for working with complex numbers", "keywords": ["complex", "mathematics"], "homepage": "https://github.com/MarkBaker/PHPComplex", "license": "MIT", "authors": [ { "name": "Mark Baker", "email": "mark@lange.demon.co.uk" } ], "require": { "php": "^7.2 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3", "squizlabs/php_codesniffer": "^3.4", "phpcompatibility/php-compatibility": "^9.0", "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0" }, "autoload": { "psr-4": { "Complex\\": "classes/src/" } }, "scripts": { "style": "phpcs --report-width=200 --standard=PSR2 --report=summary,full classes/src/ unitTests/classes/src -n", "versions": "phpcs --report-width=200 --standard=PHPCompatibility --report=summary,full classes/src/ --runtime-set testVersion 7.2- -n" }, "minimum-stability": "dev" } examples/complexTest.php000064400000005474144760126070011423 0ustar00add(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->add(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->add(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->add(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->add(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->add(new Complex(0, -1)); echo $x, PHP_EOL; echo PHP_EOL, 'Subtract', PHP_EOL; $x = new Complex(123); $x->subtract(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->subtract(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->subtract(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->subtract(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->subtract(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->subtract(new Complex(0, -1)); echo $x, PHP_EOL; echo PHP_EOL, 'Multiply', PHP_EOL; $x = new Complex(123); $x->multiply(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->multiply(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->multiply(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->multiply(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->multiply(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->multiply(new Complex(0, -1)); echo $x, PHP_EOL; echo PHP_EOL, 'Divide By', PHP_EOL; $x = new Complex(123); $x->divideBy(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->divideBy(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->divideBy(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->divideBy(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->divideBy(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->divideBy(new Complex(0, -1)); echo $x, PHP_EOL; echo PHP_EOL, 'Divide Into', PHP_EOL; $x = new Complex(123); $x->divideInto(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->divideInto(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->divideInto(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->divideInto(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->divideInto(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->divideInto(new Complex(0, -1)); echo $x, PHP_EOL; examples/testFunctions.php000064400000002027144760126070011753 0ustar00getMessage(), PHP_EOL; } } echo PHP_EOL; } } examples/testOperations.php000064400000001131144760126070012121 0ustar00 ', $result, PHP_EOL; echo PHP_EOL; echo 'Subtraction', PHP_EOL; $result = Operations::subtract(...$values); echo '=> ', $result, PHP_EOL; echo PHP_EOL; echo 'Multiplication', PHP_EOL; $result = Operations::multiply(...$values); echo '=> ', $result, PHP_EOL; license.md000064400000002125144760126070006517 0ustar00The MIT License (MIT) ===================== Copyright © `2017` `Mark Baker` 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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.