tags:

views:

112

answers:

8

A user is allowed to enter any mathematical equation they like (with one variable):

x + 5

1 - x/2

(x/3) * (56/13)

These are stored as strings in the database. When they are retrieved I need to substitute 'x' for a number and check the value of the equation.

How could I do this?

I was considering writing a parser to deconstruct the strings and turn them into equations, however this sounds expensive and problematic. The other option is to pass them through eval (but I'm not a great fan of using eval if I can help it).

Any ideas?

UPDATE: I also need to be able to get the boolean value of something like "(x > 5)". This is not possible with evalMath

UPDATE 2: I need to fire lots of these a second. I've been looking into eval in php but cant get it to return a boolean for (5 > 4) however I noticed js would do it... maybe I should investigate node.js...

UPDATE 3: After having some fun trying out node.js (and getting it to work) I went back and got eval to work in PHP see: http://stackoverflow.com/questions/4028043/can-php-eval-return-a-boolean-value

So I will go with eval with a very very hardcore filter on user input.

A: 

eval and writing a parser is somewhat the same approach

Itay Moav
Nowhere near each other in terms of the number of things that can go wrong.
Hammerite
@Hammerite - ....approach.... I didn't even suggest this as a solution. a.k.a community wiki
Itay Moav
A: 

Even if you pass through eval, you will have to replace x with some number. The kind of strategy I would have is to pass value for x, and see what is the evaluated value. If it is more than 0, then I would try a smaller number, and if it is less than 0, I would try a bigger number recursively until it satisfies an error margin (<>0.001%).

tanjir
A: 

eval()

Depends what you have to do but anyway, the cheapest way to doing it's using a replace function for the variables and then run the expression by using the eval().
Of course you need first to make sure that your formulas are in php syntax.
The good thing is that you can use any mathematical function supported by php, the bad thing is, it's never nice to use the eval() :)

PHPClasses

The other good option it's surfing the web until you find a parser :P
http://www.phpclasses.org/package/2695-PHP-Safely-evaluate-mathematical-expressions.html

Cesar
-1 for suggesting use of eval() on user input
Hammerite
-1 nice!!! It's downright dangerous with user input, as has been stated as a requirement in this question.
Mark Baker
@Hammerite, @Mark Baker: hey, i write that it's never nice to use the eval...
Cesar
Unfortunately evalMath does not handle things like returning a boolean for (5 > 3)
ae
A: 

Depends...

What is the complexity it will accept? Because for common mathematical equations (like the ones you posted), I do not see too much problem in writing a parser. The main problematic question would be round the numbers and place correct parenthesis.

But if the equations are going to accept "advanced" inputs, like {[()]}, or X², X³, or getting further, diferential calculus and college maths, so things can go crazy.

If the complexity reach symbolic handling, try read and search something about CAS (Calculate Algebra Systems).

Of course, I extremely recommend you to make your own system for inputs, validate against it, and evangelize users to tie inputs to it. Nothing too complex, but enough to make you (and others) comfortable and safe to reach what you need.

Dave
+4  A: 

My standard answer to this question whenever it crops up:

Don't use eval (especially as you're stating that this is user input) or reinvent the wheel by writing your own formula parser.

Take a look at the evalMath class on PHPClasses. It should do everything that you've listed here.

EDIT

re: Unfortunately evalMath does not handle things like (x > 5)

change lines 177-179 to

$ops   = array('+', '-', '*', '/', '^', '_', '>', '<', '=');
$ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>0, '>' => 0, '<' => 0, '=' => 0); // right-associative operator?
$ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2, '>' => 0, '<' => 0, '=' => 0); // operator precedence

change line 184 to

if (preg_match("/[^\w\s+*^\/()\.,-<>=]/", $expr, $matches)) { // make sure the characters are all good

add

case '>':
     $stack->push($op1 > $op2); break;
case '<':
     $stack->push($op1 < $op2); break;
case '=':
     $stack->push($op1 == $op2); break;

after line 321

and evalMath will now handle (x > 5), (x < 5) or (x = 5)

// instantiate a new EvalMath
$m = new EvalMath;
$m->suppress_errors = true;
// set the value of x
$m->evaluate('x = 3');
var_dump($m->evaluate('y = (x > 5)'));

Further Edit

Missed line 307 that should be modified to read:

if (in_array($token, array('+', '-', '*', '/', '^', '>', '<', '='))) {
Mark Baker
Unfortunately evalMath does not handle things like (x > 5)
ae
@ae - there's not a lot of work involved to get evalMath to handle (x > 5), etc. It would seem you'd far rather use eval() despite the dangers, but see my edit for the changes involved.
Mark Baker
Thats great! I'll give it a while and see how it goes.
ae
+1  A: 

If you're dealing with user input I'd stay away from eval. Write a parser and break the formula into nested arrays.

1 - x/2

becomes

Array
(
    [0] => -
    [1] => 1
    [2] => Array
        (
            [0] => /
            [1] => x
            [2] => 2
        )
)

It's a little tricky to write the parser, but it's really easy to evaluate a parsed formula.

Mike C
A: 

Eval is not Evil!!!!!

Yes it can stuff your system up completely if you write bad code - but recent PHP versions can parse an invalid expression without crashing the whole script. And there are many other ways of exposing your system by writing bad code.

That just leaves the possiblity of code injection attacks - which can easily be avoided by doing a preg_replace on everythnig which is not a safe character (i.e. 0....9, (, ), +, -, *, /, ^, .)

symcbean
preg_replace to make the expression "safe" becomes more awkward if the expression can contain functions such as log(), sin()... OP doesn't indicate whether or not this is the case.
Mark Baker
Agreed - its more awkward - but far from impossible to implement. The important thing is to use whitelisting rather than blacklisting.
symcbean
Thanks for a Eval is not Evil response :)
ae
However - I cant get eval to return a boolean for '(5 > 12)' - any ideas?
ae
Got it working... forgot the semicolon on the eval statement
ae
A: 

slightly risky possibility if you were running your code on a linux box is to use the bc command (making sure to escape your inputs properly before giving it to the system cmd). i can't say using system is much better than the risks of eval, so I am expecting some downvotes here.

jellyfishtree