C# Code, Tutorials and Full Visual Studio Projects

C# Formula Evaluator

Posted by on Nov 16, 2010 in Math | 25 comments

C# Formula Evaluator

Ever need to give your users the ability to enter in simple formulas into a TextBox and have it evaluated?  This is something I keep running across and after looking around a bit I couldn’t find a simple light-weight formula evaluator for C#, so I decided to build one.

What I wanted as a simple to use evaluator that accepts a string that looks similiar to this: “5 * 2 / 3″, and returns the correct answer.

The formula evaluator I created supports multiplication, division, addition, subtraction, powers, sin, cos and obeys brackets following the correct order of operations.

Formula Evaluator


using System;
using System.Text.RegularExpressions;
 
namespace JarlooFormulaEvaluator
{
    public class FormulaEvaluator
    {
        private readonly Regex bracketsRegex = new Regex(@"([a-z]*)\(([^\(\)]+)\)(\^|!?)", RegexOptions.Compiled);
        private readonly Regex cosRegex = new Regex(@"cos(-?\d+.?\d*)", RegexOptions.Compiled);
        private readonly Regex sinRegex = new Regex(@"sin(-?\d+.?\d*)", RegexOptions.Compiled);
        private readonly Regex powerRegex = new Regex(@"(-?\d+\.?\d*)\^(-?\d+\.?\d*)", RegexOptions.Compiled);
        private readonly Regex multiplyRegex = new Regex(@"(-?\d+\.?\d*)\*(-?\d+\.?\d*)", RegexOptions.Compiled);
        private readonly Regex divideRegex = new Regex(@"(-?\d+\.?\d*)/(-?\d+\.?\d*)", RegexOptions.Compiled);
        private readonly Regex addRegex = new Regex(@"(-?\d+\.?\d*)\+(-?\d+\.?\d*)", RegexOptions.Compiled);
        private readonly Regex subtractRegex = new Regex(@"(-?\d+\.?\d*)-(-?\d+\.?\d*)", RegexOptions.Compiled);
        
        public double Evaluate(string expr)
        {
            expr = expr.Replace(" ", "").ToLower();
            
            Match m = bracketsRegex.Match(expr);
            while (m.Success)
            {
                expr = expr.Replace("(" + m.Groups[2].Value + ")", Solve(m.Groups[2].Value));
                m = bracketsRegex.Match(expr);
            }
          
            return Convert.ToDouble(Solve(expr));
        }

        private string Solve(string expr)
        {
            if (expr.IndexOf("cos") != -1) expr = Do(cosRegex, expr, (x) =>
                Math.Cos(Convert.ToDouble(x.Groups[1].Value)).ToString());

            if (expr.IndexOf("sin") != -1) expr = Do(sinRegex, expr, (x) => 
                Math.Sin(Convert.ToDouble(x.Groups[1].Value)).ToString());
            
            if (expr.IndexOf("^") != -1) expr = Do(powerRegex, expr, (x) => 
                Math.Pow(Convert.ToDouble(x.Groups[1].Value), Convert.ToDouble(x.Groups[2].Value)).ToString());

            if (expr.IndexOf("/") != -1) expr = Do(divideRegex, expr, (x) => 
                (Convert.ToDouble(x.Groups[1].Value) / Convert.ToDouble(x.Groups[2].Value)).ToString());

            if (expr.IndexOf("*") != -1) expr = Do(multiplyRegex, expr, (x) => 
                (Convert.ToDouble(x.Groups[1].Value) * Convert.ToDouble(x.Groups[2].Value)).ToString());

            if (expr.IndexOf("+") != -1) expr = Do(addRegex, expr, (x) => 
                (Convert.ToDouble(x.Groups[1].Value) + Convert.ToDouble(x.Groups[2].Value)).ToString());

            if (expr.IndexOf("-") != -1) expr = Do(subtractRegex, expr, (x) => 
                (Convert.ToDouble(x.Groups[1].Value) - Convert.ToDouble(x.Groups[2].Value)).ToString());

            return expr;
        }

        private static string Do(Regex regex, string formula, Func<Match, string> func)
        {
            MatchCollection collection = regex.Matches(formula);
            
            if (collection.Count == 0) return formula;
            
            for (int i = 0; i < collection.Count;i++ ) 
                formula = formula.Replace(collection[i].Groups[0].Value, func(collection[i]));

            formula = Do(regex, formula, func);
            
            return formula;
        }
    }
}

And to use the code you need could do something like this:

namespace JarlooFormulaEvaluator
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            FormulaEvaluator eval = new FormulaEvaluator();

            //Simple math
            Console.WriteLine(string.Format("5*2={0}", eval.Evaluate("5*2")));

            //Default order of operations
            Console.WriteLine(string.Format("5*2+3={0}", eval.Evaluate("5*2+3")));

            //Brackets are solved first
            Console.WriteLine(string.Format("5*(2+3)={0}", eval.Evaluate("5*(2+3)")));

            //Can use sin / cos
            Console.WriteLine(string.Format("5*(2+sin(3))={0}", eval.Evaluate("5*(2+sin(3))")));

            //Can raise to the power of
            Console.WriteLine(string.Format("5*(2+sin(3))/2^2={0}", eval.Evaluate("5*(2+sin(3))/2^2")));

            Console.ReadLine();
        }
    }

You can easily add new functions by including a new Regex and making a new entry in the Solve method.

Works under Microsoft .NET Framework version 4.

25 Comments

Join the conversation and post a comment.

  1. Ryan

    I’m new to C# and wanted to understand the logic behind your parser. However i can’t seem to understand the source code. I tried to compile the code to sebug and step through however i ge many errors, particularly in the Solve and Do methods. It seems that there are random =&gt and &lt etc in the code that dont compile. I also cant make out what the parameters are for the Do method. Would you mind posting again so i can get to grips with this project.

    By the way i have found your site very useful and the design is excellent.

    Regards

  2. kelias

    Sorry about that. I changed the module used to display code on the site and obviously missed updating this article. It’s now fixed.

  3. Ryan

    Thanks! I think i understand delegates and lambda expressions a lot more now. Just out of interest how would you implement the evaluation of Pi or other constants in this example? Would it be best to replace them with the actual value before evaluation or create another Regex to deal with them?

  4. kelias

    I would create another regex just like the COS and SIN ones to handle PI or constants.

  5. Ryan

    Wouldn’t they have to be slightly different though as you don’t have to have two operands when using constants. Would it end up replacing the words with the constant via a Regex rather than using the .Replace method?

  6. kelias

    Your right. Replacing would be easier.
    For example in the Evaluate method you could do this for PI:
    expr = expr.Replace(“pi”, Math.PI.ToString()).ToLower();

    You could do the same for other constants, or better yet write a method that lets you assign constants to the engine. Could be as simple as a public method like:
    public Dictionary Constants {get; set;}

    Then iterate it at the beginning of the Evaluate method and do the replacements.

  7. Ryan

    Great suggestion! Ona slightly different note is there a way to slightly modify the Regex’s so that they accept the operand in brackets as well as without? e.g sin(45) rather than just sin45.

    Thanks

  8. kelias

    It already does. Since it does order of operations it will solve for the brackets first then essentially replace the brackets and their contents with the value. Then it will solve from there.
    So if you enter sin(45), it will replace (45) with 45, so you have sin45, then it will solve that and it will match the regex and process properly.

    You could also do sin(45-10) and it will work properly since it does the brackets first.

  9. Ryan

    I was just thinking of a method to put brackets around the whole expression e.g (sin(45) instead of sin(45) as there is a bug if you do for example sin(45) + cos(45), fixed by putting brackets around each individual expression

  10. Ryan

    Managed to fix the bug by looping through all of the regex’s in a set and inserting brackets encapsulating each expression found in a MatchCollection. Also had to do this twice – once at the beginning of the Evaluate method and once after the brackets had been matched. Also had to match brackets again after brackets had been put in.

    Not the best fix in terms of efficiency, but it works well.

  11. SMiGL

    Write unit test for FormulaEvaluator. Wrong calculation for decimals value. Example, 5,1-2,9
    To fix bug change “.?” to “.?” in all regexp!

  12. Ryan

    What do you mean by wrong calculation for decimals value? All tests that iv done turn out perfecty. What do you mean by 5,1-2,9?

  13. Ryan

    Oh sorry, i see what you mean now. Its because this evaluator does not support comma’s as a method of signifying decimal points, only periods. As you said if you want to change it to accept comma’s, you can modify the regex expressions

  14. kelias

    Is this a localization issue? Are the “,” in your example used to denote the difference between dollars and cents? ie: a decimal place like “.” for U.S.

  15. Ryan

    Hi again,

    Sorry to bother you again but i was wondering if you might give me some pointers. Its kind of related to this topic in terms of Regex’s.

    I am in the middle of a project involving some functions on equations such as gradient(x^2 + 7x +12). I was thinking that i could modify some of the Regex expressions above to return the value 7 in this case. However i came to a halt in the large matter of the brackets. At the moment, like above, the brackets are done first (which is fine) however when it comes to doing the calculation on the equation, the program doesnt know where the equation ends and would simply go on and on to the rest of the expression – resulting in the complete wrong result.

    I was wondering if you knew a possible solution to this, where brackets are still taken into consideration, yet the gradient function for example can still work properly.

    Thanks again

  16. Stan

    Formula below won’t work for example…I believe somehow the exponent E is messing it up.

    (-4.1855)^2+(0.0000005)^3+(-2.1521)^2

  17. Clifford Nelson

    Your regular expression definitions are missing the back slashes before the ‘d’ and special characters. Fixed that but still having problems.

  18. admin

    As you mentioned the regex were missing some of the backslashes. (format issue when I posted it. sorry about that.)

    I’ve corrected the issue. Should work for you now.

  19. Phil

    Merci cent millions de fois! Très puissant!

  20. HASAN

    my english is not very good: (
    receive health care. very nice sharing.
    I’m new in c #. I got an error like this program.
    Convert.ToDouble return (Solve (expr)); / Input string was not in a correct format.
    If you help rejoice. Thank you.

  21. Fred Knot

    HASAN , check the formula you are passing in. Could it be incorrect?

    Try something simple like: eval.Evaluate(“5*2″)

    I got the same error that you got when I had an incorrect forumla. I had more “(“‘s than “)”‘s so it couldn’t evaluate properly- when I fixed my formula it worked.

    Good luck.

  22. Judi

    that’s nice, thank u so much :)
    Good luck

  23. venu

    Hi,

    i am using your formula evaluator.it is not working in some cases .could you please help me.
    for example
    0+0+0.00+0.00+0.00

    this format not working

  24. Igor

    I´ve found a problem

    something like 1+1+5+1+1000
    on the replace returned 2+5+2000

    then, i´ve changed the code:
    for (int i = 0; i < collection.Count;i++ )
    formula = formula.Replace(collection[i].Groups[0].Value, func(collection[i]));

    for the code:
    for (int i = 0; i < collection.Count; i++)
    {
    //formula = formula.Replace(collection[i].Groups[0].Value, func(collection[i]));
    string first_operation = collection[i].Groups[0].Value;
    int len = first_operation.Length;
    int pos = formula.IndexOf(first_operation);
    string formula_before_operation = formula.Substring(0, pos);
    string formula_after_operation = formula.Substring(pos+len, formula.Length – (pos+len));
    formula = formula_before_operation + func(collection[i]) + formula_after_operation;
    }

  25. Brice

    I have some problems. If i try 1-2^2, it returns 14 because it calculates (-2^2) = 4 and concatenates to the “1” remaining. Can’t find a way to resolve it …

Leave a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>