使用eval函数从字符串中计算数学表达式

31

我想从字符串计算数学表达式。我已经了解到解决方法是使用eval()。但是当我尝试运行以下代码时:

<?php

$ma ="2+10";
$p = eval($ma);
print $p;

?>

我遇到了以下错误:

 

解析错误:语法错误,意外的 $end,位于 C:\xampp\htdocs\eclipseWorkspaceWebDev\MandatoryHandinSite\tester.php(4) : eval()‘d code on line 1

有人知道解决这个问题的方法吗?


6
你可以使用 eval() 来完成某些事情,但绝不能在任何情况下使用 eval()看看这个解决方案 - Sammitch
好的,谢谢。如果我问一下,使用eval()有什么不好的呢? - Langkiller
1
@user68621:这非常不安全。$ma字符串来自哪里?用户输入?如果我发送rmdir('/var/www');或其他内容作为我的输入,会怎样? - gen_Eric
啊,我明白你的意思了 :) 是的,$ma是用户输入。 - Langkiller
4
基本上,由于它被用于评估从外部来源引入的代码,这会带来安全问题,因此90%的情况下会使用它。有9.9%的情况是人们错误地解决了问题。最后的0.1%是我尚未见过其存在并且继续怀疑的神话独角兽。此外,以上百分比忽略了黑客将“eval()”代码注入到易受攻击的网页中的大多数时间。 - Sammitch
显示剩余2条评论
10个回答

77

虽然我不建议使用 eval(这不是解决方案),但问题在于 eval 需要完整的代码行,而不仅仅是片段。

$ma ="2+10";
$p = eval('return '.$ma.';');
print $p;

应该做你想要的事情。


更好的解决方案是为您的数学表达式编写一个分词/解析器。以下是一个非常简单的基于正则表达式的示例:

$ma = "2+10";

if(preg_match('/(\d+)(?:\s*)([\+\-\*\/])(?:\s*)(\d+)/', $ma, $matches) !== FALSE){
    $operator = $matches[2];

    switch($operator){
        case '+':
            $p = $matches[1] + $matches[3];
            break;
        case '-':
            $p = $matches[1] - $matches[3];
            break;
        case '*':
            $p = $matches[1] * $matches[3];
            break;
        case '/':
            $p = $matches[1] / $matches[3];
            break;
    }

    echo $p;
}

1
我提供了一个更好的解决方案建议。虽然不是最好的,但我希望它能给你提供需要做什么的想法。我相信你可以在谷歌上找到更好的解决方案。 - gen_Eric
1
这真的很好,除了我的函数中有24.5*12,它将第一个数字视为5而不是24.5,因为它仅寻找数字。 - Warren Sergent
2
将以下模式替换为我(允许数字、空格和小数点)的技巧 - 请注意,它将匹配其中包含空格的数字(即“201.5 + 11 2012 = 212.5”),因此如果不通过在 $matches[1]$matches[3] 或类似位置周围使用 str_replace 来去掉这些空格,那么计算就无法完全正确。这将完全依赖于您的用法 - 这适合我的需求。 /([\d\.\s]+)([\+\-\*\/])([\d\.\s]+)/ - Warren Sergent
1
更进一步:/([\d\.\s]+)([\+\-\*\/])(\-?[\d\.\s]+)/ 将允许第二个数字是负值(例如 24.5 * -4 的情况)。否则,由于第二个分组无法找到负数,这将会出错。 - Warren Sergent
@halapgos1 答案中的正则表达式可以直接使用。它只是查找一系列数字。请了解正则表达式的工作原理;有很多优秀的教程可供参考。 - anon
显示剩余2条评论

44

看一下这个..

我在一个会计系统中使用它,您可以在金额输入字段中编写数学表达式..

示例

$Cal = new Field_calculate();

$result = $Cal->calculate('5+7'); // 12
$result = $Cal->calculate('(5+9)*5'); // 70
$result = $Cal->calculate('(10.2+0.5*(2-0.4))*2+(2.1*4)'); // 30.4

代码

class Field_calculate {
    const PATTERN = '/(?:\-?\d+(?:\.?\d+)?[\+\-\*\/])+\-?\d+(?:\.?\d+)?/';

    const PARENTHESIS_DEPTH = 10;

    public function calculate($input){
        if(strpos($input, '+') != null || strpos($input, '-') != null || strpos($input, '/') != null || strpos($input, '*') != null){
            //  Remove white spaces and invalid math chars
            $input = str_replace(',', '.', $input);
            $input = preg_replace('[^0-9\.\+\-\*\/\(\)]', '', $input);

            //  Calculate each of the parenthesis from the top
            $i = 0;
            while(strpos($input, '(') || strpos($input, ')')){
                $input = preg_replace_callback('/\(([^\(\)]+)\)/', 'self::callback', $input);

                $i++;
                if($i > self::PARENTHESIS_DEPTH){
                    break;
                }
            }

            //  Calculate the result
            if(preg_match(self::PATTERN, $input, $match)){
                return $this->compute($match[0]);
            }
            // To handle the special case of expressions surrounded by global parenthesis like "(1+1)"
            if(is_numeric($input)){
                return $input;
            }

            return 0;
        }

        return $input;
    }

    private function compute($input){
        $compute = create_function('', 'return '.$input.';');

        return 0 + $compute();
    }

    private function callback($input){
        if(is_numeric($input[1])){
            return $input[1];
        }
        elseif(preg_match(self::PATTERN, $input[1], $match)){
            return $this->compute($match[0]);
        }

        return 0;
    }
}

你的类非常有用!谢谢!但是你能否扩展你的类,使得“(5+2)”不返回0吗? - Flo
我为你的表达式编写了一个简短的测试@PushpendraSingh。它是否符合您的要求(例如检测空格,大写字母x等)由您决定。https://gist.github.com/DBX12/2e1d622a0fa0937874ac3cf5eeecef51 - DBX12
5
由于在 PHP 7.2 中,create_function 函数已被弃用,因此需要重写 compute() 函数。如果不想使用 eval 函数,可以将函数重写为:private function compute($input) {return 0 + (function() use ($input){return $input;})();}。 - Brainware
我有一个基本计算问题,例如:$Cal->calculate('0.37+0.31-0.68'),结果应该是“0”,但函数返回:-1.1102230246252E-16,你有什么想法为什么会这样? - Florian Richard
1
@FlorianRichard 因为这就是答案。 这是由于浮点数舍入误差引起的。 https://stackoverflow.com/a/22803403/2415524 数字以二进制形式存储。但是,0.3和许多其他数字没有有限的二进制表示,因此计算机必须进行一些舍入。同样地,2/3没有有限的十进制表示,因此我们可能会将其舍入为0.6666666667 - mbomb007
显示剩余3条评论

6

最近我创建了一个PHP包,提供了一个名为math_eval的辅助函数。它能够精确地完成你需要的功能,而无需使用可能不安全的eval函数。

只需传入数学表达式的字符串版本,就可以返回结果。

$two   = math_eval('1 + 1');
$three = math_eval('5 - 2');
$ten   = math_eval('2 * 5');
$four  = math_eval('8 / 2');

您还可以传递变量,如果需要,这些变量将被替换。

$ten     = math_eval('a + b', ['a' => 7, 'b' => 3]);
$fifteen = math_eval('x * y', ['x' => 3, 'y' => 5]);

Link: https://github.com/langleyfoxall/math_eval


5

当您无法控制字符串参数时,使用 eval 函数非常危险。

尝试使用 Matex 进行安全的数学公式计算。


1
你使用了哪个标准来将变量和方法的首字母大写命名?这不是常规的。友情建议-如果你希望其他人使用你的库,请改用通用标准。 - Alex Kalmikov
@AlexCalm1Kov,好的,我会尝试调整为驼峰命名法,但我不喜欢它。目前它是PSR兼容的,因此应该可以与自动加载器一起使用。 - Marcodor

2

解决了!

<?php 
function evalmath($equation)
{
    $result = 0;
    // sanitize imput
    $equation = preg_replace("/[^a-z0-9+\-.*\/()%]/","",$equation);
    // convert alphabet to $variabel 
    $equation = preg_replace("/([a-z])+/i", "\$$0", $equation); 
    // convert percentages to decimal
    $equation = preg_replace("/([+-])([0-9]{1})(%)/","*(1\$1.0\$2)",$equation);
    $equation = preg_replace("/([+-])([0-9]+)(%)/","*(1\$1.\$2)",$equation);
    $equation = preg_replace("/([0-9]{1})(%)/",".0\$1",$equation);
    $equation = preg_replace("/([0-9]+)(%)/",".\$1",$equation);
    if ( $equation != "" ){
        $result = @eval("return " . $equation . ";" );
    }
    if ($result == null) {
        throw new Exception("Unable to calculate equation");
    }
    echo $result;
   // return $equation;
}


$a = 2;
$b = 3;
$c = 5;
$f1 = "a*b+c";

$f1 = str_replace("a", $a, $f1);
$f1 = str_replace("b", $b, $f1);
$f1 = str_replace("c", $c, $f1);

evalmath($f1);
/*if ( $equation != "" ){

    $result = @eval("return " . $equation . ";" );
}
if ($result == null) {

    throw new Exception("Unable to calculate equation");
}
echo $result;*/
?>

1
在 eval 的危险性和无限的计算可能性之间找到一个甜点,我建议检查输入是否只包含数字、运算符和括号:
if (preg_match('/^[0-9\+\-\*\/\(\)\.]+$/', $mathString)) {
    $value = eval('return
    ' . $mathString . ';');
} else {
    throw new \Exception('Invalid calc() value: ' . $mathString);
}

它仍然易于使用,但相对安全。它可以处理任何基本的数学计算,例如(10*(1+0.2)),这在这里提到的大多数解决方案中都不可能实现。


1
这种方法有两个主要缺点:
  • 安全性,php脚本正在被eval函数评估。这很糟糕,特别是当用户想要注入恶意代码时。

  • 复杂性

我创建了这个,请查看:公式解释器

它是如何工作的?

首先,使用公式及其参数创建FormulaInterpreter的实例

$formulaInterpreter = new FormulaInterpreter("x + y", ["x" => 10, "y" => 20]);

使用execute()方法来解释公式,它将返回结果:
echo $formulaInterpreter->execute();

在一行中。
echo (new FormulaInterpreter("x + y", ["x" => 10, "y" => 20]))->execute();

示例

# Formula: speed = distance / time
$speed = (new FormulaInterpreter("distance/time", ["distance" => 338, "time" => 5]))->execute() ;
echo $speed;


#Venezuela night overtime (ordinary_work_day in hours): (normal_salary * days_in_a_work_month)/ordinary_work_day
$parameters = ["normal_salary" => 21000, "days_in_a_work_month" => 30, "ordinary_work_day" => 8];
$venezuelaLOTTTArt118NightOvertime = (new FormulaInterpreter("(normal_salary/days_in_a_work_month)/ordinary_work_day", $parameters))->execute();
echo $venezuelaLOTTTArt118NightOvertime;


#cicle area
$cicleArea = (new FormulaInterpreter("3.1416*(radio*radio)", ["radio" => 10]))->execute();
echo $cicleArea;

关于公式

  1. 公式必须包含至少两个操作数和一个运算符。
  2. 操作数的名称可以是大写或小写。
  3. 目前,不包括诸如sin、cos、pow等数学函数。我正在努力将它们包含在内。
  4. 如果您的公式无效,您将收到错误消息,例如:错误,您的公式(single_variable)无效。
  5. 参数的值必须为数字。

如果您愿意,您可以改进它!


4
GitHub的链接是404页面未找到。 - Daniel W.

0
使用 eval 函数
 protected function getStringArthmeticOperation($value, $deduct)
{
    if($value > 0){
        $operator = '-';
    }else{
        $operator = '+';
    }
    $mathStr = '$value $operator $deduct';
    eval("\$mathStr = \"$mathStr\";");
    $userAvailableUl = eval('return '.$mathStr.';');
    return $userAvailableUl;
}

$this->getStringArthmeticOperation(3, 1); //2

0

eval 函数将给定的代码作为PHP代码进行评估。这意味着它将执行给定的参数作为PHP代码片段。

要更正您的代码,请使用以下内容:

$ma ="print (2+10);";
eval($ma);

-2

eval() 函数中的表达式应该以 ";" 结尾。

尝试以下代码:

$ma ="2+10;";
$p = eval($ma);
print $p;

顺便提一下,这超出了范围,但是 'eval' 函数不会返回表达式的值。 eval('2 + 10')不会返回12。 如果您希望它返回12,则应该使用 eval('return 2 + 10;');

返回翻译后的文本:这将显示一个空白页面,因为$p变量为空。在内部添加echo$ma ="echo 2+10;"; - S.Thiongane
@mansoulx,这正是上面回答中所说的('return 2+10')。 - ibtarek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接