calculate math expression from a string using eval
Asked Answered
A

10

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.

Axum answered 18/9, 2013 at 19:33 Comment(7)
You can hack something up using eval(), but no one should ever use eval for anything ever. Check this solution.Priestly
okay thanks.. what's so bad about using eval() if I may ask?Axum
@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?Germangermana
ahh I see what you mean :) yes $ma is user input.Axum
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.Priestly
Possible duplicate of PHP function to evaluate string like "2-1" as arithmetic 2-1=1Breathed
RelatedRandi
G
80

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;
}
Germangermana answered 18/9, 2013 at 19:36 Comment(7)
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.Germangermana
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 digitsConduction
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]+)/Conduction
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.Conduction
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 etcAble
@Able The regex in the answer will work as-is. It just looks for a series of things that are digits. Please read up on how regular expressions work; there are plenty of great tutorials.Berkow
@TeguhSuryoSantoso The "tokenizer" shown isn't very sophisticated. The regex can only parse things like 1+2. It was given as an example and to be expanded upon when used.Germangermana
T
45

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;
    }
}
Taylor answered 22/11, 2014 at 12:29 Comment(8)
Your Class is very helpfully! Thank you! But can you expand your class that "(5+2)" not returning 0?Lather
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 6Adoptive
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/2e1d622a0fa0937874ac3cf5eeecef51Reticle
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.';');}Brodsky
Add if(is_numeric($input)){return $input;} after if(preg_match(self::PATTERN test if you want it doesn't return 0 for such expressions with global parenthesis "(1+1)" (I modified the code)Costar
I have a problem with basic calcul like : $Cal->calculate('0.37+0.31-0.68'), the result must be "0" but the function return : -1.1102230246252E-16, have you an idea of why ?Madel
@FlorianRichard Because that's the answer. It's because of floating point rounding errors. https://mcmap.net/q/453787/-php-floating-point-error-with-basic-arithmetic-operation-duplicate 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.Autocracy
You can fix that issue by multiplying each number by a large integer first, like 100, then dividing after adding. Or the better way is to use BC Math or Decimal.Autocracy
O
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

Obtund answered 1/11, 2018 at 14:43 Comment(0)
D
6

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

Try Matex for safe Mathematical formulas calculation.

Descendant answered 20/9, 2017 at 16:37 Comment(2)
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.Ofris
@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.Descendant
M
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;*/
?>
Midlothian answered 16/4, 2017 at 7:37 Comment(0)
B
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!

Benedictine answered 20/3, 2019 at 10:17 Comment(1)
GitHub link is 404 page not foundKudva
D
2

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.

Dwightdwindle answered 20/1, 2023 at 19:50 Comment(0)
M
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);
Mortar answered 18/9, 2013 at 19:40 Comment(0)
D
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
Donovan answered 26/4, 2019 at 12:20 Comment(0)
G
-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;');

Groundhog answered 18/9, 2013 at 19:42 Comment(2)
that will show a blank page, because $p variable is empty. add echo inside : $ma ="echo 2+10;";Mortar
@mansoulx, It is exactly what was said in the answer above ('return 2+10').Groundhog

© 2022 - 2024 — McMap. All rights reserved.