32

I want to calculate math expression from a string. I have read that the solution to this is to use eval(). But when I try to run the following code:

<?php

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

?>

It gives me the following error:

Parse error: syntax error, unexpected $end in C:\xampp\htdocs\eclipseWorkspaceWebDev\MandatoryHandinSite\tester.php(4) : eval()'d code on line 1

Does someone know the solution to this problem.

6
  • 6
    You can hack something up using eval(), but no one should ever use eval for anything ever. Check this solution. Commented Sep 18, 2013 at 19:36
  • okay thanks.. what's so bad about using eval() if I may ask? Commented Sep 18, 2013 at 19:45
  • 1
    @user68621: It's very insecure. Where's the $ma string coming from? User input? What if I sent rmdir('/var/www'); or something as my input? Commented Sep 18, 2013 at 19:50
  • ahh I see what you mean :) yes $ma is user input. Commented Sep 18, 2013 at 20:54
  • 5
    Basically because 90% of the times it is used it's to evaluate code pulled in from external sources which is a security concern. 9.9% of the time it's people approaching a problem wrong. The final 0.1% is a mythical unicorn I have yet to witness whose existence I continue to doubt. Also, the above percentages ignore the vast majority of the time where a hacker injects eval() code into a vulnerable web page. Commented Sep 18, 2013 at 21:37

11 Answers 11

81

While I don't suggest using eval for this (it is not the solution), the problem is that eval expects complete lines of code, not just fragments.

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

Should do what you want.


A better solution would be to write a tokenizer/parser for your math expression. Here's a very simple regex-based one to give you an example:

$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;
}
Sign up to request clarification or add additional context in comments.

7 Comments

I've added a suggestion for a better solution. It's not the greatest, but I hope it gives an idea into what you need to do. I'm sure you can Google around for a better solution.
This is really good, except in my function where I have 24.5*12, it's taking the first number as 5 instead of 24.5 since it's looking solely for digits
Replacing the pattern with the following did the trick for me (allows digits, spaces, and a decimal) - note that it will match numbers with spaces in them (i.e. '201.5 + 11 2012 = 212.5', so math wouldn't work entirely correctly without stripping those spaces out (via a str_replace around $matches[1] and $matches[3] or similar). It would be entirely dependent on your usage - this worked for my needs. /([\d\.\s]+)([\+\-\*\/])([\d\.\s]+)/
One step further: /([\d\.\s]+)([\+\-\*\/])(\-?[\d\.\s]+)/ will allow the second number to be a negative value (in the case of 24.5 * -4 for instance). This would have broken as the second group wouldn't have found the negative number.
How would you advise to tackle 100+01? I'm using the eval function but I'm kind of confused on how to write a regex to check for numbers such as 011,090,001 etc etc
|
46

Take a look at this..

I use this in an accounting system where you can write math expressions in amount input fields..

Examples

$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

Code

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;
    }
}

8 Comments

Your Class is very helpfully! Thank you! But can you expand your class that "(5+2)" not returning 0?
can it solve 13/18-1/2 x 40, 13/18 x 40, 14 3/16/ 21 x 40, 16 3/16/ 23 x 40, 16-3/16 / 30 x 60, 9.75 X 21.5/29.5, 1/4 x 5, 1/2 x 6, 7 x 60, 8 Ga. x 4, 22 x 36, 35/64 x 6
I wrote a short test for your expressions @PushpendraSingh If it meets your requirements (e.g. detecting spaces, upper case x, etc) is up to you. gist.github.com/DBX12/2e1d622a0fa0937874ac3cf5eeecef51
Since, create_function is depreciated in php 7.2, how would the compute() function be rewritten? I don't want to use eval but this seams to work: private function compute($input) {return 0 + eval('return '.$input.';');}
@FlorianRichard Because that's the answer. It's because of floating point rounding errors. stackoverflow.com/a/22803403/2415524 Numbers are stored in binary. But 0.3 and many others have no finite binary representation, so the computer has to round a little. In the same way, 2/3 has no finite decimal representation, so we might round to 0.6666666667.
|
7

I recently created a PHP package that provides a math_eval helper function. It does exactly what you need, without the need to use the potentially unsafe eval function.

You just pass in the string version of the mathematical expression and it returns the result.

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

You can also pass in variables, which will be substituted if needed.

$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

Comments

6

Using eval function is very dangerous when you can't control the string argument.

Try Matex for safe Mathematical formulas calculation.

2 Comments

Which standard you've used to name your variables and methods with first capital letters ? It's not conventional. Friendly advice - change to common standard if you want other people to use your library.
@AlexCalm1Kov, oki, will try to adjust to camel, however i don't like it. Currently it is PSR compatible, so should work with auto-loaders.
4

Finding a sweetspot between the dangers of eval and the limitless calculation possibilities I suggest checking the input for only numbers, operators and brackets:

if (preg_match('/^[0-9\+\-\*\/\(\)\.]+$/', $mathString)) {
    $value = eval('return
    ' . $mathString . ';');
} else {
    throw new \Exception('Invalid calc() value: ' . $mathString);
}

It's still easy to use yet relatively save. And it can handle any basic math calulation like (10*(1+0,2)) which isn't possible with most of the mentioned solutions here.

Comments

3

Solved!

<?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;*/
?>

Comments

2

This method has two major drawbacks:

  • Security, php script is being evaluated by the eval function. This is bad, especially when the user wants to inject malicious code.

  • Complexity

I created this, check it out: Formula Interpreter

How does it work ?

First, create an instance of FormulaInterpreter with the formula and its parameters

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

Use the execute() method to interpret the formula. It will return the result:

echo $formulaInterpreter->execute();

in a single line

echo (new FormulaInterpreter("x + y", ["x" => 10, "y" => 20]))->execute();

Examples

# 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;

About the formulas

  1. It must contain at least two operands and an operator.
  2. Operands' name could be in upper or lower case.
  3. By now, math functions as sin, cos, pow… are not included. I'm working to include them.
  4. If your formula is not valid, you will get an error message like: Error, your formula (single_variable) is not valid.
  5. Parameters' values must be numeric.

You can improve it if you want to!

1 Comment

GitHub link is 404 page not found
0

eval Evaluates the given code as PHP. Meaning that it will execute the given paremeter as a PHP piece of code.

To correct your code, use this :

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

Comments

0

Using eval function

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

Comments

-1

I found this cool method on Github, hope it helps someone.

<?php 
function evaluate_math_string($str) {
    $__eval = function ($str) use(&$__eval){
        $error = false;
        $div_mul = false;
        $add_sub = false;
        $result = 0;

        $str = preg_replace('/[^\d.+\-*\/()]/i','',$str);
        $str = rtrim(trim($str, '/*+'),'-');

        /* lets first tackle parentheses */
        if ((strpos($str, '(') !== false &&  strpos($str, ')') !== false)) {
            $regex = '/\(([\d.+\-*\/]+)\)/';
            preg_match($regex, $str, $matches);
            if (isset($matches[1])) {
                return $__eval(preg_replace($regex, $__eval($matches[1]), $str, 1));
            }
        }

        /* Remove unwanted parentheses */
        $str = str_replace(array('(',')'), '', $str);
        /* now division and multiplication */
        if ((strpos($str, '/') !== false ||  strpos($str, '*') !== false)) {
            $div_mul = true;
            $operators = array('*','/');
            while(!$error && $operators) {
                $operator = array_pop($operators);
                while($operator && strpos($str, $operator) !== false) {
                   if ($error) {
                      break;
                   }
                   $regex = '/([\d.]+)\\'.$operator.'(\-?[\d.]+)/';
                   preg_match($regex, $str, $matches);
                   if (isset($matches[1]) && isset($matches[2])) {
                          if ($operator=='+') $result = (float)$matches[1] + (float)$matches[2];
                          if ($operator=='-') $result = (float)$matches[1] - (float)$matches[2]; 
                          if ($operator=='*') $result = (float)$matches[1] * (float)$matches[2]; 
                          if ($operator=='/') {
                             if ((float)$matches[2]) {
                                $result = (float)$matches[1] / (float)$matches[2];
                             } else {
                                $error = true;
                             }
                          }
                          $str = preg_replace($regex, $result, $str, 1);
                          $str = str_replace(array('++','--','-+','+-'), array('+','+','-','-'), $str);
                   } else {
                      $error = true;
                   }
                }
            }
        }
         
        if (!$error && (strpos($str, '+') !== false ||  strpos($str, '-') !== false)) {
            //tackle duble negation 
            $str = str_replace('--', '+', $str);
            $add_sub = true;
            preg_match_all('/([\d\.]+|[\+\-])/', $str, $matches);
            if (isset($matches[0])) {
                $result = 0;
                $operator = '+';
                $tokens = $matches[0];
                $count = count($tokens);
                for ($i=0; $i < $count; $i++) { 
                    if ($tokens[$i] == '+' || $tokens[$i] == '-') {
                        $operator = $tokens[$i];
                    } else {
                        $result = ($operator == '+') ? ($result + (float)$tokens[$i]) : ($result - (float)$tokens[$i]);
                    }
                }
            }
        }
        if (!$error && !$div_mul && !$add_sub) {
             $result = (float)$str;
        }
        return $error ? 0 : $result;
    };
    return $__eval($str);
}
?>

Here is an example how to use it:

<?php 
require('evaluate_math.php');

/* Test cases */
$tests = array(
    '-3',
    '+5', 
    '5+2', 
    '-5-1', 
    '5-1', 
    '2 + 2.5 + 3 / 3 + 3 * 3', 
    '5 + 10 /-2 - 5 *2',
    '((5 - 2.5) + 2) * ((5 - 2.5) + 2)',
    '(((5 - 2.5) + 2) * ( 10 / 5))',
    '5-(-2)'
);
foreach($tests as $test) {
    echo $test . '= '. evaluate_math_string($test) . '<br />';
}
?>

AND FINALLY HERE IS A REF LINK TO GITHUB https://github.com/samirkumardas/evaluate_math_string

1 Comment

It looks like an answer now. If you want to make it a good answer please try for How to Answer and explain it, How does it work? Why does it help to solve the problem?
-2

An eval'd expression should end with ";"

Try this :

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

By the way, this is out of scope but the 'eval' function won't return the value of the expression. eval('2+10') won't return 12. If you want it to return 12, you should eval('return 2+10;');

2 Comments

that will show a blank page, because $p variable is empty. add echo inside : $ma ="echo 2+10;";
@mansoulx, It is exactly what was said in the answer above ('return 2+10').

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.