0

为什么 PHP 中的这个数学表达式解析器和计算器不对浮点运算执行操作?以及如何修复它以使其执行浮点运算。

通过浮点运算,我的意思是

  • 4.0 * 2

,

  • 5.0+7.6

, 或者

  • (8.1-10) * (3.0')

这是代码:

 <?php

 require_once 'math.php';

 $math = new Math();

 if(isset($_GET['calc'])){
     $answer = $math->calculate($_GET['calc']);
     echo $answer;
 }
 ?>
 <!Doctype HTML>
 <html lang="en">
      <head>
          <meta charset="utf-8" />
          <title>calculator</title>
  </head>
  <body>
     <form method="get">
        <input type="text" name="calc" autocomplete="off" autofocus />
     </form>
  </body>
 </html>

这是math.php:

 <?php
 require_once 'stack.php';
 require_once 'terminalExpression.php';
 require_once 'expressions.php';

 class Math {

    protected $variables = array();

    public function calculate($string) {
        $stack = $this->parse($string);
        return $this->run($stack);
    }

    public function parse($string) {
        $tokens = $this->tokenize($string);
        $output = new Stack();
        $operators = new Stack();
        foreach ($tokens as $token) {
            $token = $this->extractVariables($token);
            $expression = TerminalExpression::factory($token);
            if ($expression->isOperator()) {
                 $this->parseOperator($expression, $output, $operators);
            } elseif ($expression->isParenthesis()) {
                $this->parseParenthesis($expression, $output, $operators);
            } else {
                $output->push($expression);
            }
        }
        while (($op = $operators->pop())) {
             if ($op->isParenthesis()) {
                 throw new RuntimeException('Mismatched Parenthesis');
             }
             $output->push($op);
         }
         return $output;
      }

     public function registerVariable($name, $value) {
        $this->variables[$name] = $value;
     }

     public function run(Stack $stack) {
        while (($operator = $stack->pop()) && $operator->isOperator()) {
             $value = $operator->operate($stack);
            if (!is_null($value)) {
                 $stack->push(TerminalExpression::factory($value));
            }
         }
         return $operator ? $operator->render() : $this->render($stack);
    }

    protected function extractVariables($token) {
        if ($token[0] == '$') {
            $key = substr($token, 1);
             return isset($this->variables[$key]) ? $this->variables[$key] : 0;
        }
         return $token;
    }

     protected function render(Stack $stack) {
        $output = '';
        while (($el = $stack->pop())) {
            $output .= $el->render();
         }
        if ($output) {
             return $output;
        }
         throw new RuntimeException('Could not render output');
     }

     protected function parseParenthesis(TerminalExpression $expression, Stack $output, Stack $operators) {
         if ($expression->isOpen()) {
             $operators->push($expression);
         } else {
              $clean = false;
              while (($end = $operators->pop())) {
                  if ($end->isParenthesis()) {
                     $clean = true;
                    break;
                  } else {
                     $output->push($end);
                 }
              }
              if (!$clean) {
            throw new RuntimeException('Mismatched Parenthesis');
        }
    }
}

protected function parseOperator(TerminalExpression $expression, Stack $output, Stack $operators) {
    $end = $operators->poke();
    if (!$end) {
        $operators->push($expression);
    } elseif ($end->isOperator()) {
        do {
            if ($expression->isLeftAssoc() && $expression->getPrecidence() <= $end->getPrecidence()) {
                $output->push($operators->pop());
            } elseif (!$expression->isLeftAssoc() && $expression->getPrecidence() < $end->getPrecidence()) {
                $output->push($operators->pop());
            } else {
                break;
            }
        } while (($end = $operators->poke()) && $end->isOperator());
        $operators->push($expression);
    } else {
        $operators->push($expression);
    }
}

protected function tokenize($string) {
    $parts = preg_split('((\d+|\+|-|\(|\)|\*|/)|\s+)', $string, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    $parts = array_map('trim', $parts);
    return $parts;
}

 }
?>

stack.php 是:

<?php
class Stack {

    protected $data = array();

     public function push($element) {
         $this->data[] = $element;
     }

     public function poke() {
         return end($this->data);
     }

     public function pop() {
         return array_pop($this->data);
    }

 }
?>

和 terminalExpressions.php:

<?php
abstract class TerminalExpression {

protected $value = '';

public function __construct($value) {
    $this->value = $value;
}

public static function factory($value) {
    if (is_object($value) && $value instanceof TerminalExpression) {
        return $value;
    } elseif (is_numeric($value)) {
        return new Number($value);
    } elseif ($value == '+') {
        return new Addition($value);
    } elseif ($value == '-') {
        return new Subtraction($value);
    } elseif ($value == '*') {
        return new Multiplication($value);
    } elseif ($value == '/') {
        return new Division($value);
    } elseif (in_array($value, array('(', ')'))) {
        return new Parenthesis($value);
    }
    throw new Exception('Undefined Value ' . $value);
}

abstract public function operate(Stack $stack);

public function isOperator() {
    return false;
}

public function isParenthesis() {
    return false;
}

public function isNoOp() {
    return false;
}

public function render() {
    return $this->value;
}
}
?>

最后是expressions.php:

<?php
class Parenthesis extends TerminalExpression {

protected $precidence = 6;

public function operate(Stack $stack) {
}

public function getPrecidence() {
    return $this->precidence;
}

public function isNoOp() {
    return true;
}

public function isParenthesis() {
    return true;
}

public function isOpen() {
    return $this->value == '(';
}

}

class Number extends TerminalExpression {

public function operate(Stack $stack) {
    return $this->value;
}

}

abstract class Operator extends TerminalExpression {

protected $precidence = 0;
protected $leftAssoc = true;

public function getPrecidence() {
    return $this->precidence;
}

public function isLeftAssoc() {
    return $this->leftAssoc;
}

public function isOperator() {
    return true;
}

 }

class Addition extends Operator {

protected $precidence = 4;

public function operate(Stack $stack) {
    return $stack->pop()->operate($stack) + $stack->pop()->operate($stack);
}

}

class Subtraction extends Operator {

protected $precidence = 4;

public function operate(Stack $stack) {
    $left = $stack->pop()->operate($stack);
    $right = $stack->pop()->operate($stack);
    return $right - $left;
}

}

class Multiplication extends Operator {

protected $precidence = 5;

public function operate(Stack $stack) {
    return $stack->pop()->operate($stack) * $stack->pop()->operate($stack);
}

}

 class Division extends Operator {

protected $precidence = 5;

public function operate(Stack $stack) {
    $left = $stack->pop()->operate($stack);
    $right = $stack->pop()->operate($stack);
    return $right / $left;
}

 }
    ?>
4

2 回答 2

1

Patashu 的解决方案更完整,但您仍然需要做更多的事情才能完成它。

如果您只希望浮点数起作用,只需更改他提到的行

$parts = preg_split('((\d+|\+|-|\(|\)|\*|/)|\s+)', $string, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);

对这个

$parts = preg_split('(([0-9]*\.[0-9]+|[0-9]+|\+|-|\(|\)|\*|/)|\s+)', $string, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);

我用您的案例对其进行了测试,它甚至可以像这样 .2*.4+1.4

我再说一遍,这个解决方案仅适用于浮点数,如果您使用 Patashu 的答案,您将制作出一个了不起的计算器;)

于 2013-06-06T00:20:24.727 回答
1

在这里,我们看到标记化是通过期望一个或多个数字、a +、a -、a (、a )、a *、a / 或一个或多个空格来完成的:

$parts = preg_split('((\d+|\+|-|\(|\)|\*|/)|\s+)', $string, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);

所以它目前不会识别浮点值。

你需要改变这个

\d+

到您选择的浮点数值正则表达式,例如

((\d*)?\.)?\d+([eE][+\-]?\d+)?|[nN]a[nN]|[iI]nf(inity)?

是我过去用来覆盖尽可能多的案例的一种方法。

在这里,如果它是数字,它将令牌转换为数字,所以如果它解析但没有正确转换为正确的令牌/数字,我接下来会在这里查看:

public static function factory($value) {
    if (is_object($value) && $value instanceof TerminalExpression) {
        return $value;
    } elseif (is_numeric($value)) {
        return new Number($value);
于 2013-06-05T23:57:52.627 回答