20

I need to evaluate a mathmatical expression that is presented to me as a string in C#. Example noddy but gets the point across that the string as the expression.

I need the evaluate to then populate an int.

There is no Eval() in C# like in others langugaes...

String myString = "3*4";

Edit:

I am on VS2008

Tried the Microsoft.JScript. = Its deprecated method (but still complies - warning)

However the Microsoft.JScript dll that I have doens work on

public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args);

Complains that there is a missing ";" go figure...

EDIT 2

Solution - was the codeDom one - it worked for as there are no security issue - only me ever going to be running the code. Many thanks for the replies ...

And the link to the new Dragon Book awesome

EDIT 3

Matt dataTable.Compute() also works - even better for the security conscious. (parameter checking noted)

2
  • You can use the jscript interpreter. A great article for it is here: http://www.odetocode.com/Articles/80.aspx Commented Oct 6, 2008 at 15:08
  • You could also look at Ncalc.codeplex.com as I suggested in my answer which seems to have been deleted for reasons I don't understand. Commented Nov 26, 2013 at 9:51

9 Answers 9

23

All the other answers are possible overkill.

If all you need is simple arithmetic, do this.

DataTable dummy = new DataTable();
Console.WriteLine(dummy.Compute("15 / 3",string.Empty));

EDIT: a little more information. Check out the MSDN documentation for the Expression property of the System.Data.DataColumn class. The stuff on "Expression Syntax" outlines a list of commands you can use in addition to the arithmetic operators. (ex. IIF, LEN, etc.). Thanks everyone for voting up my first posted answer!

Sign up to request clarification or add additional context in comments.

2 Comments

nice little hack. Just curious: how will this behave for bad inputs?
it throws an System.Data.EvaluateException or System.Data.SyntaxErrorException telling you what's wrong.
13

The way I see it, you have two options - use an expression evaluator or construct, compile and run C# code on the fly.

I would go with an expression evaluator library, as you do not have to worry about any security issues. That is, you might not be able to use code generation in medium trust environments, such as most shared hosting servers.

Here is an example for generating code to evaluate expressions: http://www.vbforums.com/showthread.php?t=397264

3 Comments

Hi - the dotMath library seems to have not been migrated from workspace.gotdotnet.com - when they have transition to msdn...
Yes :( Seems to be available on CodePlex though: codeplex.com/dotMath/Release/ProjectReleases.aspx?ReleaseId=875
You could also look at Ncalc.codeplex.com as I suggested in my answer which seems to have been deleted for reasons I don't understand.
11

I did this as a personal exercise in C# a few weeks ago.

It is quite a bit of code and is poorly commented in places. But it did work with a lot of test cases.

Enjoy!

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace StackOverflow
{
    class Start
    {
        public static void Main(string[] args)
        {
            Evaluator ev;
            string variableValue, eq;
        Console.Write("Enter equation:  ");
        eq = Console.ReadLine();

        while (eq != "quit")
        {
            ev = new Evaluator(eq);
            foreach (Variable v in ev.Variables)
            {
                Console.Write(v.Name + " = ");
                variableValue = Console.ReadLine();
                ev.SetVariable(v.Name, Convert.ToDecimal(variableValue));
            }

            Console.WriteLine(ev.Evaluate());

            Console.Write("Enter equation:  ");
            eq = Console.ReadLine();
        }
    }
}

class EvalNode
{
    public virtual decimal Evaluate()
    {
        return decimal.Zero;
    }
}

class ValueNode : EvalNode
{
    decimal value;

    public ValueNode(decimal v)
    {
        value = v;
    }

    public override decimal Evaluate()
    {
        return value;
    }

    public override string ToString()
    {
        return value.ToString();
    }
}

class FunctionNode : EvalNode
{
    EvalNode lhs = new ValueNode(decimal.Zero);
    EvalNode rhs = new ValueNode(decimal.Zero);
    string op = "+";

    public string Op
    {
        get { return op; }
        set
        {
            op = value;
        }
    }

    internal EvalNode Rhs
    {
        get { return rhs; }
        set
        {
            rhs = value;
        }
    }

    internal EvalNode Lhs
    {
        get { return lhs; }
        set
        {
            lhs = value;
        }
    }

    public override decimal Evaluate()
    {
        decimal result = decimal.Zero;

        switch (op)
        {
            case "+":
                result = lhs.Evaluate() + rhs.Evaluate();
                break;

            case "-":
                result = lhs.Evaluate() - rhs.Evaluate();
                break;

            case "*":
                result = lhs.Evaluate() * rhs.Evaluate();
                break;

            case "/":
                result = lhs.Evaluate() / rhs.Evaluate();
                break;

            case "%":
                result = lhs.Evaluate() % rhs.Evaluate();
                break;

            case "^":
                double x = Convert.ToDouble(lhs.Evaluate());
                double y = Convert.ToDouble(rhs.Evaluate());

                result = Convert.ToDecimal(Math.Pow(x, y));
                break;

            case "!":
                result = Factorial(lhs.Evaluate());
                break;
        }

        return result;
    }

    private decimal Factorial(decimal factor)
    {
        if (factor < 1)
            return 1;

        return factor * Factorial(factor - 1);
    }

    public override string ToString()
    {
        return "(" + lhs.ToString() + " " + op + " " + rhs.ToString() + ")";
    }
}

public class Evaluator
{
    string equation = "";
    Dictionary<string, Variable> variables = new Dictionary<string, Variable>();

    public string Equation
    {
        get { return equation; }
        set { equation = value; }
    }

    public Variable[] Variables
    {
        get { return new List<Variable>(variables.Values).ToArray(); }
    }

    public void SetVariable(string name, decimal value)
    {
        if (variables.ContainsKey(name))
        {
            Variable x = variables[name];
            x.Value = value;
            variables[name] = x;
        }
    }

    public Evaluator(string equation)
    {
        this.equation = equation;
        SetVariables();
    }

    public decimal Evaluate()
    {
        return Evaluate(equation, new List<Variable>(variables.Values));
    }

    public decimal Evaluate(string text)
    {
        decimal result = decimal.Zero;
        equation = text;
        EvalNode parsed;

        equation = equation.Replace(" ", "");

        parsed = Parse(equation, "qx");

        if (parsed != null)
            result = parsed.Evaluate();

        return result;
    }

    public decimal Evaluate(string text, List<Variable> variables)
    {
        foreach (Variable v in variables)
        {
            text = text.Replace(v.Name, v.Value.ToString());
        }

        return Evaluate(text);
    }

    private static bool EquationHasVariables(string equation)
    {
        Regex letters = new Regex(@"[A-Za-z]");

        return letters.IsMatch(equation);
    }

    private void SetVariables()
    {
        Regex letters = new Regex(@"([A-Za-z]+)");
        Variable v;

        foreach (Match m in letters.Matches(equation, 0))
        {
            v = new Variable(m.Groups[1].Value, decimal.Zero);

            if (!variables.ContainsKey(v.Name))
            {
                variables.Add(v.Name, v);
            }
        }
    }

    #region Parse V2

    private Dictionary<string, string> parenthesesText = new Dictionary<string, string>();

    /*
     * 1.  All the text in first-level parentheses is replaced with replaceText plus an index value.
     *      (All nested parentheses are parsed in recursive calls)
     * 2.  The simple function is parsed given the order of operations (reverse priority to 
     *      keep the order of operations correct when evaluating).
     *      a.  Addition (+), subtraction (-)                   -> left to right
     *      b.  Multiplication (*), division (/), modulo (%)    -> left to right
     *      c.  Exponents (^)                                   -> right to left
     *      d.  Factorials (!)                                  -> left to right
     *      e.  No op (number, replaced parentheses) 
     * 3.  When an op is found, a two recursive calls are generated -- parsing the LHS and 
     *      parsing the RHS.
     * 4.  An EvalNode representing the root node of the evaluations tree is returned.
     * 
     * Ex.  3 + 5                   (3 + 5) * 8
     *           +                          *
     *          / \                        / \
     *         3   5                      +   8
     *                                   / \ 
     *      3 + 5 * 8                   3   5
     *            +
     *           / \
     *          3   *
     *             / \
     *            5   8
     */

    /// <summary>
    /// Parses the expression and returns the root node of a tree.
    /// </summary>
    /// <param name="eq">Equation to be parsed</param>
    /// <param name="replaceText">Text base that replaces text in parentheses</param>
    /// <returns></returns>
    private EvalNode Parse(string eq, string replaceText)
    {
        int randomKeyIndex = 0;

        eq = eq.Replace(" ", "");
        if (eq.Length == 0)
        {
            return new ValueNode(decimal.Zero);
        }

        int leftParentIndex = -1;
        int rightParentIndex = -1;
        SetIndexes(eq, ref leftParentIndex, ref rightParentIndex);

        //remove extraneous outer parentheses
        while (leftParentIndex == 0 && rightParentIndex == eq.Length - 1)
        {
            eq = eq.Substring(1, eq.Length - 2);
            SetIndexes(eq, ref leftParentIndex, ref rightParentIndex);
        }

        //Pull out all expressions in parentheses
        replaceText = GetNextReplaceText(replaceText, randomKeyIndex);

        while (leftParentIndex != -1 && rightParentIndex != -1)
        {
            //replace the string with a random set of characters, stored extracted text in dictionary keyed on the random set of chars

            string p = eq.Substring(leftParentIndex, rightParentIndex - leftParentIndex + 1);
            eq = eq.Replace(p, replaceText);
            parenthesesText.Add(replaceText, p);

            leftParentIndex = 0;
            rightParentIndex = 0;

            replaceText = replaceText.Remove(replaceText.LastIndexOf(randomKeyIndex.ToString()));
            randomKeyIndex++;
            replaceText = GetNextReplaceText(replaceText, randomKeyIndex);

            SetIndexes(eq, ref leftParentIndex, ref rightParentIndex);
        }

        /*
         * Be sure to implement these operators in the function node class
         */
        char[] ops_order0 = new char[2] { '+', '-' };
        char[] ops_order1 = new char[3] { '*', '/', '%' };
        char[] ops_order2 = new char[1] { '^' };
        char[] ops_order3 = new char[1] { '!' };

        /*
         * In order to evaluate nodes LTR, the right-most node must be the root node
         * of the tree, which is why we find the last index of LTR ops.  The reverse 
         * is the case for RTL ops.
         */

        int order0Index = eq.LastIndexOfAny(ops_order0);

        if (order0Index > -1)
        {
            return CreateFunctionNode(eq, order0Index, replaceText + "0");
        }

        int order1Index = eq.LastIndexOfAny(ops_order1);

        if (order1Index > -1)
        {
            return CreateFunctionNode(eq, order1Index, replaceText + "0");
        }

        int order2Index = eq.IndexOfAny(ops_order2);

        if (order2Index > -1)
        {
            return CreateFunctionNode(eq, order2Index, replaceText + "0");
        }

        int order3Index = eq.LastIndexOfAny(ops_order3);

        if (order3Index > -1)
        {
            return CreateFunctionNode(eq, order3Index, replaceText + "0");
        }

        //no operators...
        eq = eq.Replace("(", "");
        eq = eq.Replace(")", "");

        if (char.IsLetter(eq[0]))
        {
            return Parse(parenthesesText[eq], replaceText + "0");
        }

        return new ValueNode(decimal.Parse(eq));
    }

    private string GetNextReplaceText(string replaceText, int randomKeyIndex)
    {
        while (parenthesesText.ContainsKey(replaceText))
        {
            replaceText = replaceText + randomKeyIndex.ToString();
        }
        return replaceText;
    }

    private EvalNode CreateFunctionNode(string eq, int index, string randomKey)
    {
        FunctionNode func = new FunctionNode();
        func.Op = eq[index].ToString();
        func.Lhs = Parse(eq.Substring(0, index), randomKey);
        func.Rhs = Parse(eq.Substring(index + 1), randomKey);

        return func;
    }

    #endregion

    /// <summary>
    /// Find the first set of parentheses
    /// </summary>
    /// <param name="eq"></param>
    /// <param name="leftParentIndex"></param>
    /// <param name="rightParentIndex"></param>
    private static void SetIndexes(string eq, ref int leftParentIndex, ref int rightParentIndex)
    {
        leftParentIndex = eq.IndexOf('(');
        rightParentIndex = eq.IndexOf(')');
        int tempIndex = eq.IndexOf('(', leftParentIndex + 1);

        while (tempIndex != -1 && tempIndex < rightParentIndex)
        {
            rightParentIndex = eq.IndexOf(')', rightParentIndex + 1);
            tempIndex = eq.IndexOf('(', tempIndex + 1);
        }
    }
}

public struct Variable
{
    public string Name;
    public decimal Value;

    public Variable(string n, decimal v)
    {
        Name = n;
        Value = v;
        }
    }
}

3 Comments

Why not make EvalNode an interface? Still +1.
With the initial work on evaluation I just wanted the code to return something. The evolution of the code got me to what was posted and you're right, EvalNode could be changed to an interface.
Fails with brackets. E.g: (1+3)+2
1

When you say, "like in other languages" you should instead say, "like in dynamic languages".

For dynamic languages like python, ruby, and many interpreted languages, an Eval() function is a natural element. In fact, it's probably even pretty trivial to implement your own.

Howver, .Net is at it's core a static, strongly-typed, compiled platform (at least until the Dynamic Language Runtime gets more support). This has natural advantages like code-injection security and compile-time type checking that are hard to ignore. But it means an Eval() function isn't such a good fit- it wants to be able to compile the expression ahead of time. In this kind of platform, there are generally other, safer, ways to accomplish the same task.

3 Comments

Gambit Scheme is a dynamic, strongly-typed, (optionally) compiled platform, and it does have eval.
I believe you meant "like in interpreted languages"
Incorporated the 'interpreted' suggestion in my answer- though not exactly as requested.
1

MS has a sample called Dynamic Query Library. It is provided by the LINQ team to dynamically construct LINQ queries such as: Dim query = Northwind.Products.Where("CategoryID=2") You might check to see if it offers rudimentary math capabilities.

Comments

1

You can use a Parsing Expression Grammar (PEG). For a simple grammar:

Expr    ::= Sum
Sum     ::= Product ( ( '+' | '-' ) Product ) *
Product ::= Primary ( ( '*' | '/' ) Primary ) *
Primary ::= Number | '(' Expr ')'

It can be implemented as follows:

// Parser.cs
public class Parser
{
    public string Expr { get; internal set; } = string.Empty;
    public int ExprIndex { get; internal set; } = 0;
    public Match? LastMatch { get; internal set; } = null;
    public Stack<double> Results { get; } = new Stack<double>();
    public Dictionary<string, Func<double, double, double>> Operators { get; } = new Dictionary<string, Func<double, double, double>>()
    {
        { "+", (a, b) => a + b },
        { "-", (a, b) => a - b },
        { "*", (a, b) => a * b },
        { "/", (a, b) => a / b }
    };

    public double Execute(string expr)
    {
        Expr = expr;
        ExprIndex = 0;
        Results.Clear();
        if (!ParseExpr() || ExprIndex < Expr.Length) throw new Exception("Invalid expression");
        return Results.Pop();
    }
    private bool ParseExpr() => ParseSum();
    private bool ParseSum() => ParseBinaryOp(@"^\s*(\+|\-)\s*", ParseProduct);
    private bool ParseProduct() => ParseBinaryOp(@"^\s*(\*|\/)\s*", ParsePrimary);
    private bool ParseBinaryOp(string pattern, Func<bool> parseNextPriority)
    {
        if (!parseNextPriority()) return false;
        while (ParsePattern(pattern))
        {
            string op = LastMatch!.Groups[1].Value;
            if (!parseNextPriority()) return false;
            var b = Results.Pop();
            var a = Results.Pop();
            Results.Push(Operators[op](a, b));
        }
        return true;
    }
    private bool ParsePrimary()
    {
        if (ParsePattern(@"^\s*(\d+(\.\d+)?)\s*"))
        {
            Results.Push(double.Parse(LastMatch.Groups[1].Value));
            return true;
        }
        if (ParsePattern(@"^\s*\(\s*"))
        {
            if (!ParseExpr()) return false;
            if (!ParsePattern(@"^\s*\)\s*")) return false;
            return true;
        }
        return false;
    }
    public bool ParsePattern(string pattern)
    {
        var regex = new Regex(pattern);
        LastMatch = regex.Match(Expr.Substring(ExprIndex));
        if (!LastMatch.Success) return false;
        ExprIndex += LastMatch.Groups[0].Length;
        return true;
    }
}

Here's how you may use the above parser:

Parser parser = new Parser();
double fourtySeven = parser.Execute("5 + 6 * 7"); // 47
double eleven = parser.Execute("5 + 6"); // 11

To extend the implementation above, you can refactor the Parse() step to avoid modifying the stack. Instead, it populates a list of detected tokens. The Execute() step then processes this list, which is useful if you need to run the Execute() step multiple times. This is useful if you want to rerun formulas with different inputs.

References:

Comments

0

In an interpreted language you might have a chance of evaluating the string using the interpreter. In C# you need a parser for the language the string is written in (the language of mathematical expressions). This is a non-trivial exercise. If you want to do it, use a recursive-descent parser. The early chapters of the "Dragon Book" (Compilers: Design, etc. by Aho, Sethi and Ullman - 1st ed 1977 or 2nd ed 2007) have a good explanation of what you need to do.

An alternative might be to include in your project a component written in perl, which is supposed to be available for .NET now, and use perl to do the evaluation.

Comments

0

The jscript interpreter could do, or you can write your own parser if the expression is simple (beware, it becomes complicated really fast).

I'm pretty sure there is no direct "Eval(string)" method in C# since it is not interpreted.

Keep in mind though that code interpretation is subject to code injection, be extra careful :)

Comments

0

Some other suggestions:

  • Mono 2.0 (came out today) has an eval method.
  • You can easily write a small domain specific in boo.
  • You can create an old school recursive-descent EBNF parser.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.