<?php
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
});
bcscale(50); // Количество знаков после запятой по умолчанию
class DivideByZeroException extends \Exception
{
public function __construct($numerator)
{
parent
::__construct
(sprintf("Ошибка: (%s/0) делить на ноль нельзя", $numerator)); }
}
class NegativeBaseRootException extends \Exception
{
public function __construct($base)
{
parent
::__construct
(sprintf("Нельзя получить вещественный корень отрицательного числа %s", $base)); }
}
class InvalidInfixException extends \Exception {}
class Parser
{
public function getTokensFromInfix($infix)
{
$tokensQueue = new \SplQueue();
$lexems = $this->getLexemsFromInfix($infix);
$binaryOperatorsCount = 0;
$operandsCount = 0;
$parenthesisDeep = 0;
foreach ($lexems as $lexem) {
$tokensQueue->push(SimpleFraction::createFromDecimal($lexem));
$operandsCount++;
} elseif (in_array($lexem, ['+', '-'])) { if ($tokensQueue->isEmpty() || $tokensQueue->top() instanceof LeftParenthesis) {
$tokensQueue->push(OperationFactory::getUnaryBySign($lexem));
} elseif ($tokensQueue->top() instanceof SimpleFraction || $tokensQueue->top() instanceof RightParenthesis) {
$tokensQueue->push(OperationFactory::getBinaryBySign($lexem));
$binaryOperatorsCount++;
} else {
throw new InvalidInfixException
(sprintf('В выражении встречаются подряд несколько операторов')); }
} elseif (in_array($lexem, ['*', '/', '^'])) { $tokensQueue->push(OperationFactory::getBinaryBySign($lexem));
$binaryOperatorsCount++;
} elseif ($lexem === '(') {
$tokensQueue->push(new LeftParenthesis());
$parenthesisDeep--;
} elseif ($lexem === ')') {
$tokensQueue->push(new RightParenthesis());
$parenthesisDeep++;
} else {
throw new InvalidInfixException
(sprintf('Нераспознанная лексема %s', $lexem)); }
}
if ($operandsCount !== ++$binaryOperatorsCount) {
throw new InvalidInfixException('Количество бинарных операторов должно быть на один меньше, чем количество чисел');
}
if ($parenthesisDeep !== 0) {
throw new InvalidInfixException('Неправильно расставлены скобки');
}
return $tokensQueue;
}
private function getLexemsFromInfix($infix)
{
preg_match_all('/(\d+(\.\d+)?|([\+\-\/\*\^\(\)]))|(\S)/', $infix, $match); throw new InvalidInfixException
(sprintf( 'В инфиксном выражении %s недопустимые символы %s',
$infix,
));
}
$lexems = $match[0];
return $lexems;
}
}
class ShuntingYard
{
private $operatorStack;
public function __construct()
{
$this->operatorStack = new \SplStack;
}
public function createRpnQueueFromTokens(\SplQueue $tokens)
{
$outputQueue = new \SplQueue();
foreach ($tokens as $token) {
if ($token instanceof SimpleFraction) {
$outputQueue->enqueue($token);
} elseif ($token instanceof AbstractOperator) {
if ($this->operatorStack->isEmpty()) {
$this->operatorStack->push($token);
continue;
}
$operatorTop = $this->operatorStack->top();
if (($operatorTop instanceof AbstractOperator)
&& (($token->isLeftassociative() && $token->getPrecedence() <= $operatorTop->getPrecedence())
|| ($token->isRightAssociative() && $token->getPrecedence() < $operatorTop->getPrecedence()))
) {
$outputQueue->enqueue($this->operatorStack->pop());
}
$this->operatorStack->push($token);
} elseif ($token instanceof LeftParenthesis) {
$this->operatorStack->push($token);
} elseif ($token instanceof RightParenthesis) {
$operatorTop = $this->operatorStack->top();
if (!$operatorTop instanceof LeftParenthesis) {
$outputQueue->enqueue($this->operatorStack->pop());
}
$this->operatorStack->pop();
} else {
throw new \Exception
(sprintf('Invalid type %s given', gettype($token))); }
}
while (!$this->operatorStack->isEmpty()) {
$operatorTop = $this->operatorStack->pop();
if ($operatorTop instanceof LeftParenthesis || $operatorTop instanceof RightParenthesis) {
throw new InvalidInfixException('Mismatched parentheses');
}
$outputQueue->enqueue($operatorTop);
}
return $outputQueue;
}
}
class Division extends BinaryOperator
{
public static $precedence = 3;
public static $isLeftAssociative = true;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
if ($rightOperand->getNumerator() === '0') {
throw new DivideByZeroException($leftOperand);
}
$newRightOperand = $rightOperand->invert();
$multiplication = new Multiplication();
return $multiplication->operate($leftOperand, $newRightOperand);
}
}
abstract class AbstractOperator
{
public function getPrecedence()
{
return static::$precedence;
}
public function isLeftAssociative()
{
return !! static::$isLeftAssociative;
}
public function isRightAssociative()
{
return ! static::$isLeftAssociative;
}
}
class Multiplication extends BinaryOperator
{
public static $precedence = 3;
public static $isLeftAssociative = true;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
$newNumerator = MathHelper::mul($leftOperand->getNumerator(), $rightOperand->getNumerator());
$newDenominator = MathHelper::mul($leftOperand->getDenominator(), $rightOperand->getDenominator());
$isPrecise = $leftOperand->isAccurate() && $rightOperand->isAccurate();
return new SimpleFraction($newNumerator, $newDenominator, $isPrecise);
}
}
class Power extends BinaryOperator
{
public static $precedence = 4;
public static $isLeftAssociative = false;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
// Происходит попытка получить корень отрицательного числа
if ($leftOperand->getSign() === SimpleFraction::SIGN_MINUS && !$rightOperand->isInteger()) {
throw new NegativeBaseRootException($leftOperand);
}
// Происходит попытка возвести в отрицательную степень
if ($rightOperand->getSign() === SimpleFraction::SIGN_MINUS) {
$newDenominator = $this->operate($leftOperand, $rightOperand->changeSign());
$result = (new Division())->operate(SimpleFraction::createFromDecimal('1'), $newDenominator);
return $result;
}
// Если возводим в целую степень, то можно использовать bcpow
if ($rightOperand->isInteger()) {
$newNumerator = MathHelper
::pow($leftOperand->getNumerator(), $rightOperand->getNumerator()); $newDenominator = MathHelper
::pow($leftOperand->getDenominator(), $rightOperand->getDenominator()); $isAccuracy = $leftOperand->isAccurate() && $rightOperand->isAccurate();
} else {
$powerAsFraction = $rightOperand->getNumerator() / $rightOperand->getDenominator();
$newNumerator = pow($leftOperand->getNumerator(), $powerAsFraction); $newDenominator = pow($leftOperand->getDenominator(), $powerAsFraction);
// Определение точности дроби на основе результатов bcpow и pow
if ((MathHelper
::pow($newNumerator, $rightOperand->getDenominator()) !== (string
) pow($newNumerator, $rightOperand->getDenominator())) ) {
$isAccuracy = false;
// Из $newNumerator и $newDenumerator нельзя создать дробь, так как эти переменные содержат неточные десятичные дроби
$inaccuracyDecimal = $newNumerator / $newDenominator;
return SimpleFraction::createFromDecimal($inaccuracyDecimal, $isAccuracy);
} else {
$isAccuracy = true;
}
}
return new SimpleFraction($newNumerator, $newDenominator, $isAccuracy);
}
}
class RightParenthesis {}
class Subtraction extends BinaryOperator
{
public static $precedence = 2;
public static $isLeftAssociative = true;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
$addition = new Addition();
$unaryNegative = new UnaryNegative();
$newRightOperand = $unaryNegative->operate($rightOperand);
return $addition->operate($leftOperand, $newRightOperand);
}
}
abstract class BinaryOperator extends AbstractOperator
{
abstract function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand);
}
class UnaryPositive extends UnaryOperator
{
public static $precedence = 5;
public static $isLeftAssociative = false;
public function operate(SimpleFraction $operand)
{
return $operand;
}
}
class LeftParenthesis {}
class Addition extends BinaryOperator
{
public static $precedence = 2;
public static $isLeftAssociative = true;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
if ($rightOperand->getNumerator() === '0') {
return $leftOperand;
}
$commonDenominator = MathHelper::mul($leftOperand->getDenominator(), $rightOperand->getDenominator());
$newNumerator = MathHelper::add(
MathHelper::mul($leftOperand->getDenominator(), $rightOperand->getNumerator()),
MathHelper::mul($leftOperand->getNumerator(), $rightOperand->getDenominator())
);
$isAccurate = $leftOperand->isAccurate() && $rightOperand->isAccurate();
return new SimpleFraction($newNumerator, $commonDenominator, $isAccurate);
}
}
abstract class UnaryOperator extends AbstractOperator
{
abstract function operate(SimpleFraction $operand);
}
class UnaryNegative extends UnaryOperator
{
public static $precedence = 5;
public static $isLeftAssociative = false;
public function operate(SimpleFraction $operand)
{
$newSimpleFraction = $operand->changeSign();
return $newSimpleFraction;
}
}
class OperationFactory
{
public static function getBinaryBySign($sign)
{
switch ($sign) {
case '+': return new Addition();
case '-': return new Subtraction();
case '*': return new Multiplication();
case '/': return new Division();
case '^': return new Power();
default:
throw new \Exception
(sprintf('Binary operation %s is not presented', $sign)); }
}
public static function getUnaryBySign($sign)
{
switch ($sign) {
case '+': return new UnaryPositive();
case '-': return new UnaryNegative();
default:
throw new \Exception
(sprintf('Unary operation %s is not presented', $sign)); }
}
}
class MathHelper
{
public static function getGreatestCommonDivisor($a, $b)
{
}
// Удаляет лишние нули после дробной части, которые возникли из-за bcscale(50)
public static function removeTrailingZeros($decimal)
{
$decimal = (string) $decimal;
if (strpos($decimal, '.') === false) { return $decimal;
}
}
public static function div($leftString, $rightString)
{
return self::removeTrailingZeros(bcdiv($leftString, $rightString)); }
public static function mul($leftString, $rightString)
{
return self::removeTrailingZeros(bcmul($leftString, $rightString)); }
public static function add($leftString, $rightString)
{
return self::removeTrailingZeros(bcadd($leftString, $rightString)); }
public static function sub($leftString, $rightString)
{
return self::removeTrailingZeros(bcsub($leftString, $rightString)); }
public static
function pow($leftString, $rightString) {
return self::removeTrailingZeros(bcpow($leftString, $rightString)); }
}
class SimpleFraction
{
private $numerator;
private $denominator;
// Флаг точности
private $isAccurate;
const SIGN_PLUS = '+';
const SIGN_MINUS = '-';
const SIGN_INACCURATE = '~';
const SIGN_ACCURATE = '';
public function __construct($numerator, $denominator = 1, $isAccurate = true)
{
$this->numerator = MathHelper::removeTrailingZeros($numerator);
$this->denominator = MathHelper::removeTrailingZeros($denominator);
$this->isAccurate = $isAccurate;
$this->simplify();
}
public static function createFromDecimal($decimal, $isPrecise = true)
{
preg_match_all('/^(\-?\d+)(?:\.(\d+))?$/', (string
) $decimal, $matches); $integerPart = $matches[1][0];
$fractionalPart = empty($matches[2][0]) ?
'0' : $matches[2][0]; $denominator = MathHelper
::pow('10', strlen($fractionalPart)); $numerator = MathHelper::add(MathHelper::mul($integerPart, $denominator), $fractionalPart);
return new self($numerator, $denominator, $isPrecise);
}
public function getDenominator()
{
return $this->denominator;
}
public function getNumerator()
{
return $this->numerator;
}
public function isAccurate()
{
return $this->isAccurate;
}
public function invert()
{
list($this->denominator, $this->numerator) = [$this->numerator, $this->denominator];
return $this;
}
public function getSign()
{
return (strpos($this->numerator, '-') === 0) ?
self::SIGN_MINUS : self::SIGN_PLUS; }
public function changeSign()
{
$this->numerator = MathHelper::mul($this->numerator, '-1');
return $this;
}
// Можно ли дробь представить в виде целого числа
public function isInteger()
{
return !! ($this->denominator === '1' || $this->numerator === '0');
}
public function showAsDecimal()
{
$this->getPrecisionSign(),
MathHelper::div($this->numerator, $this->denominator)
);
}
private function getPrecisionSign()
{
return ($this->isAccurate()) ? self::SIGN_ACCURATE : self::SIGN_INACCURATE;
}
public function __toString()
{
if ($this->numerator === '0') {
$valueAsString = '0';
} elseif (preg_match('/^10*$/', $this->denominator)) { // Если знаменатель - любая целая степень 10-и, то показать дробь как целое число
$valueAsString = $this->showAsDecimal();
} else {
$this->numerator,
$this->denominator
);
}
return $valueAsString;
}
private function simplify()
{
if ($this->numerator === '0') {
return;
}
// Упрощение дроби с использованием НОД
$gcd = MathHelper::getGreatestCommonDivisor($this->numerator, $this->denominator);
$this->numerator = MathHelper::div($this->numerator, $gcd);
$this->denominator = MathHelper::div($this->denominator, $gcd);
// Если знаменатель отрицательный - перенести минус в числитель
if (strpos($this->denominator, '-') === 0) { $this->denominator = substr($this->denominator, 1); $this->numerator = MathHelper::mul($this->numerator, '-1');
}
}
}
class RpnQueueEvaluator
{
private $stack;
public function __construct()
{
$this->stack = new \SplStack;
}
public function evaluate(\SplQueue $tokens)
{
foreach ($tokens as $token) {
if ($token instanceof SimpleFraction) {
$this->stack->push($token);
} elseif ($token instanceof AbstractOperator) {
if ($token instanceof BinaryOperator) {
$rightOperand = $this->stack->pop();
$leftOperand = $this->stack->pop();
$fraction = $token->operate($leftOperand, $rightOperand);
} elseif ($token instanceof UnaryOperator) {
$operand = $this->stack->pop();
$fraction = $token->operate($operand);
} else {
throw new \Exception
(sprintf('Invalid type %s given', gettype($token))); }
$this->stack->push($fraction);
} else {
throw new \InvalidArgumentException();
}
}
return $this->stack->top();
}
}
$infix_correctAnswer = [
'1/1000' => '0.001',
'100/1' => '100',
'123456789123456789123456789/100' => '1234567891234567891234567.89',
'1230/100' => '12.3',
'2^(1/2)' => SimpleFraction
::SIGN_INACCURATE . pow(2, 1/2), '-2 ^ 2' => '4',
'-2 ^ (-1/2)' => 'NegativeBaseRoot',
'8 ^ (-2/3)' => '1/4',
'16 ^ 0.75' => '8',
'(9/1) ^ (1/2)' => '3',
'2/3 ^ 2' => '2/9',
'3 ^ 2 ^ 2' => '81',
'9 ^ 0' => '1',
'9 ^ 2 ^ 0' => '9',
'3-(-2)' => '5',
'(+2)' => '2',
'+(-2)' => '-2',
'-(-1)' => '1',
'(5+7)*(-3-(-1))' => '-24',
'3+-2' => 'InvalidInfix',
'-(+(+(-3)))' => '3',
'2-(-2)' => '4',
'(-2)*(-3)' => '6',
'10/(-1)*(-2)' => '20',
'(-3)-(-3)' => '0',
'2+4-(-2)' => '8',
'0 - (-3)' => '3',
'1 - 3' => '-2',
'0 - 3' => '-3',
'(2 + 6) / (1 - 1)' => 'DivideByZero',
'0 / 0' => 'DivideByZero',
'-0' => '0',
'0/2' => '0',
'17654/342' => '8827/171',
'4 - 3' => ' 1',
'2 + (-3)' => '-1',
'4 * 5' => '20',
'6/4' => '3/2',
'1.2 + 1/2' => '1.7',
'1/(-3)' => '-1/3',
'0.5 + 0.2' => '0.7',
'(((' => 'InvalidInfix',
'(1+2) * (4-2))' => 'InvalidInfix',
'+ +' => 'InvalidInfix',
'-3' => '-3',
'+3-' => 'InvalidInfix',
'1 2 3' => 'InvalidInfix',
'>34%; + 3w' => 'InvalidInfix',
'1)' => 'InvalidInfix',
'0.000000000000000000000000001 + 0.000000000000000000000000002' => '0.000000000000000000000000003',
];
$parser = new Parser;
$shuntingYard = new ShuntingYard;
$rpnQueueEvaluator = new RpnQueueEvaluator;
echo sprintf('Выражение в инфиксной нотации %7s Ожидается %11s Получено', '', ''); foreach ($infix_correctAnswer as $infix => $correctAnswer) {
echo sprintf("\n%29s %17s ", $infix, $correctAnswer); try {
$tokenQueue = $parser->getTokensFromInfix($infix);
$rpnQueue = $shuntingYard->createRpnQueueFromTokens($tokenQueue);
$result = $rpnQueueEvaluator->evaluate($rpnQueue);
} catch (InvalidInfixException $e) {
echo $e->getMessage();
} catch (DivideByZeroException $e) {
echo $e->getMessage();
} catch (NegativeBaseRootException $e) {
echo $e->getMessage();
}
}