You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
11 KiB
388 lines
11 KiB
<?php |
|
|
|
/** |
|
* |
|
* Class for the management of Complex numbers |
|
* |
|
* @copyright Copyright (c) 2013-2018 Mark Baker (https://github.com/MarkBaker/PHPComplex) |
|
* @license https://opensource.org/licenses/MIT MIT |
|
*/ |
|
namespace Complex; |
|
|
|
/** |
|
* Complex Number object. |
|
* |
|
* @package Complex |
|
* |
|
* @method float abs() |
|
* @method Complex acos() |
|
* @method Complex acosh() |
|
* @method Complex acot() |
|
* @method Complex acoth() |
|
* @method Complex acsc() |
|
* @method Complex acsch() |
|
* @method float argument() |
|
* @method Complex asec() |
|
* @method Complex asech() |
|
* @method Complex asin() |
|
* @method Complex asinh() |
|
* @method Complex atan() |
|
* @method Complex atanh() |
|
* @method Complex conjugate() |
|
* @method Complex cos() |
|
* @method Complex cosh() |
|
* @method Complex cot() |
|
* @method Complex coth() |
|
* @method Complex csc() |
|
* @method Complex csch() |
|
* @method Complex exp() |
|
* @method Complex inverse() |
|
* @method Complex ln() |
|
* @method Complex log2() |
|
* @method Complex log10() |
|
* @method Complex negative() |
|
* @method Complex pow(int|float $power) |
|
* @method float rho() |
|
* @method Complex sec() |
|
* @method Complex sech() |
|
* @method Complex sin() |
|
* @method Complex sinh() |
|
* @method Complex sqrt() |
|
* @method Complex tan() |
|
* @method Complex tanh() |
|
* @method float theta() |
|
* @method Complex add(...$complexValues) |
|
* @method Complex subtract(...$complexValues) |
|
* @method Complex multiply(...$complexValues) |
|
* @method Complex divideby(...$complexValues) |
|
* @method Complex divideinto(...$complexValues) |
|
*/ |
|
class Complex |
|
{ |
|
/** |
|
* @constant Euler's Number. |
|
*/ |
|
const EULER = 2.7182818284590452353602874713526624977572; |
|
|
|
/** |
|
* @constant Regexp to split an input string into real and imaginary components and suffix |
|
*/ |
|
const NUMBER_SPLIT_REGEXP = |
|
'` ^ |
|
( # Real part |
|
[-+]?(\d+\.?\d*|\d*\.?\d+) # Real value (integer or float) |
|
([Ee][-+]?[0-2]?\d{1,3})? # Optional real exponent for scientific format |
|
) |
|
( # Imaginary part |
|
[-+]?(\d+\.?\d*|\d*\.?\d+) # Imaginary value (integer or float) |
|
([Ee][-+]?[0-2]?\d{1,3})? # Optional imaginary exponent for scientific format |
|
)? |
|
( # Imaginary part is optional |
|
([-+]?) # Imaginary (implicit 1 or -1) only |
|
([ij]?) # Imaginary i or j - depending on whether mathematical or engineering |
|
) |
|
$`uix'; |
|
|
|
/** |
|
* @var float $realPart The value of of this complex number on the real plane. |
|
*/ |
|
protected $realPart = 0.0; |
|
|
|
/** |
|
* @var float $imaginaryPart The value of of this complex number on the imaginary plane. |
|
*/ |
|
protected $imaginaryPart = 0.0; |
|
|
|
/** |
|
* @var string $suffix The suffix for this complex number (i or j). |
|
*/ |
|
protected $suffix; |
|
|
|
|
|
/** |
|
* Validates whether the argument is a valid complex number, converting scalar or array values if possible |
|
* |
|
* @param mixed $complexNumber The value to parse |
|
* @return array |
|
* @throws Exception If the argument isn't a Complex number or cannot be converted to one |
|
*/ |
|
private static function parseComplex($complexNumber) |
|
{ |
|
// Test for real number, with no imaginary part |
|
if (is_numeric($complexNumber)) { |
|
return [$complexNumber, 0, null]; |
|
} |
|
|
|
// Fix silly human errors |
|
$complexNumber = str_replace( |
|
['+-', '-+', '++', '--'], |
|
['-', '-', '+', '+'], |
|
$complexNumber |
|
); |
|
|
|
// Basic validation of string, to parse out real and imaginary parts, and any suffix |
|
$validComplex = preg_match( |
|
self::NUMBER_SPLIT_REGEXP, |
|
$complexNumber, |
|
$complexParts |
|
); |
|
|
|
if (!$validComplex) { |
|
// Neither real nor imaginary part, so test to see if we actually have a suffix |
|
$validComplex = preg_match('/^([\-\+]?)([ij])$/ui', $complexNumber, $complexParts); |
|
if (!$validComplex) { |
|
throw new Exception('Invalid complex number'); |
|
} |
|
// We have a suffix, so set the real to 0, the imaginary to either 1 or -1 (as defined by the sign) |
|
$imaginary = 1; |
|
if ($complexParts[1] === '-') { |
|
$imaginary = 0 - $imaginary; |
|
} |
|
return [0, $imaginary, $complexParts[2]]; |
|
} |
|
|
|
// If we don't have an imaginary part, identify whether it should be +1 or -1... |
|
if (($complexParts[4] === '') && ($complexParts[9] !== '')) { |
|
if ($complexParts[7] !== $complexParts[9]) { |
|
$complexParts[4] = 1; |
|
if ($complexParts[8] === '-') { |
|
$complexParts[4] = -1; |
|
} |
|
} else { |
|
// ... or if we have only the real and no imaginary part |
|
// (in which case our real should be the imaginary) |
|
$complexParts[4] = $complexParts[1]; |
|
$complexParts[1] = 0; |
|
} |
|
} |
|
|
|
// Return real and imaginary parts and suffix as an array, and set a default suffix if user input lazily |
|
return [ |
|
$complexParts[1], |
|
$complexParts[4], |
|
!empty($complexParts[9]) ? $complexParts[9] : 'i' |
|
]; |
|
} |
|
|
|
|
|
public function __construct($realPart = 0.0, $imaginaryPart = null, $suffix = 'i') |
|
{ |
|
if ($imaginaryPart === null) { |
|
if (is_array($realPart)) { |
|
// We have an array of (potentially) real and imaginary parts, and any suffix |
|
list ($realPart, $imaginaryPart, $suffix) = array_values($realPart) + [0.0, 0.0, 'i']; |
|
} elseif ((is_string($realPart)) || (is_numeric($realPart))) { |
|
// We've been given a string to parse to extract the real and imaginary parts, and any suffix |
|
list($realPart, $imaginaryPart, $suffix) = self::parseComplex($realPart); |
|
} |
|
} |
|
|
|
if ($imaginaryPart != 0.0 && empty($suffix)) { |
|
$suffix = 'i'; |
|
} elseif ($imaginaryPart == 0.0 && !empty($suffix)) { |
|
$suffix = ''; |
|
} |
|
|
|
// Set parsed values in our properties |
|
$this->realPart = (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'); |
|
} |
|
}
|
|
|