Click here to Skip to main content
Click here to Skip to main content
Go to top

Basic mathematical expression evaluator

, 12 Dec 2011
Rate this:
Please Sign up or sign in to vote.
The article covers the creation process of a C# class which can be used to evaluate basic mathematical expressions
Sample Image - maximum width is 600 pixels

Table of Contents

Introduction

This article covers the creation process of a C# class which can be used to evaluate basic mathematical expressions. The evaluator will be able to understand the 4 basic arithmetic operations (+, -, *, /) and parentheses. This provides just a basic functionality, but lot of the times this can be sufficient, and it also keeps the code simple. So at the end, we will have a class which can be used to get the decimal value of a string expression with a simple method call.

Structure of the Article

Please note that this is a beginners' article, at some places, it contains explanations about topics which might be new to beginners, these paragraphs are indented. If you are familiar with the topic which is shortly described there you can just skip them, but if they sound new for you I recommend further reading on the subject since these are not the subject of this article, I just included as much that is necessary to understand the problem. Also the topic, parsing a mathematical expression, can be discussed in a much more advanced level than it is done here. I hope that this article will be easy to understand for everyone, even without any special knowledge about the subject.

The article follows a tutorial layout, so if you follow every step, you will end up with the same result as included in the downloads. However the article shows the way how I solved the problem, and the steps I went through, and not necessarily the steps required to reach the same solution. What I mean is, there will be places where I will tell you to replace some previously written code for example. I know that reading things like this raises the question in you 'So why did I have to write that code on the first time?', the answer is simple: because that time that was the proper code to write. These situations will happen when we first try to put the whole thing together, but to not get lost in details we keep some things to deal with it later. I believe these make the whole article easier to follow.

The following technologies, techniques are used in the article which might be new to you, if you are quite new to C# or programming, however I hope that I can give you an introduction about these subjects and encourage you for further learning: static methods and classes, recursive algorithms, LINQ extension methods, lambda expressions.

Background

I have to mention that there are a lot of other solutions available, here on CodeProject too, most of them give far more functionality than mine, but this usually also results in a more complex code.

I was working on a project where I had a TextBox on a form, to take a quantity for input, I wanted to provide the option for the user to input some simple mathematical expressions (e.g. 5*12 or 45+4*5). Since I didn't expect any complex input, and I like creating my own solutions for problems, and also I had some time to "waste" on trying (I could have still used a ready made solution if I wouldn't succeed), so I decided that I will implement an evaluator to calculate the value of the input. I found creating it interesting and fun, I thought that I will share the whole process I went through.

Finally, I created a whole new control called NumericTextBox relying on the class presented in this article, but it also adds some other extra functionality to a simple TextBox control. I wrote about it in this post.

Using the Code

The result of this tutorial will be a static class called MathsEvaluator, with the following public static methods:

  • decimal Parse(string expression)
  • bool TryParse(string expression, out decimal value)
  • bool IsExpression(string s)

The Parse method can be used to evaluate an expression, given as string, and get its value as a decimal number. It throws ArgumentException if the expression is not valid.

The TryParse method will determine if the expression provided can be evaluated and indicate this in the return value, and also give the evaluated value in an out parameter. It is recommended to use this method.

The IsExpression will indicate in its return value, whether the given expression contains only valid characters. Note that this method only checks if there aren't any illegal characters present, however this doesn't guarantee that the expression is valid.

See some example usage:

decimal d = MathsEvaluator.Parse("(1+2)*5-12");
//returns 3

MathsEvaluator.IsExpression("5*2+b");
//returns false

MathsEvaluator.IsExpression("12,4+*");
//!returns true!

bool b = MathsEvaluator.TryParse("1+2+3+4+5*0", out d);
//returns true, d will equal 10

The Problem in Details

Required Functionality

My requirements were quite simple, to end up with a method that accepts an expression of type string as parameter and returns the value of the expression as a decimal. Also since the expression origins from user input, check if it's correct, and throw exceptions if not. The operators I expected to be understood are the following: +, -, *, /, ().

An Expression

So how does an expression look? It stands from operations. Each operation has an operator, and (in this case) 2 operands. An operand can be either a decimal number or another expression. See Figure-1 for an example.

The schema of an expression

Figure-1 The structure of an expression

So my idea was to use recursion to evaluate an expression. An expression first would be split into pieces, recursively evaluating each operand, and when we have decimal values returned by the function we can simply use the correct operator to calculate the value of the operation and return it. See Figure-2 below showing an example evaluation process.

Evaluation process

Figure-2 The evaluation process of an expression

Create the Project

Create a new Windows Forms Application project in Visual Studio, call it 'MathsEvaluatorDemo', and add a TextBox, a Label, and a Button to the form, we will use these controls to test the evaluator.

Add a new Class to the project, name it MathsEvaluator, from now on we will work on this class, this will contain the evaluation logic. Since we won't need to instantiate this class, make it static, and also every method of the class will be static.

Create a new method in the class, call it Parse, it should take a string parameter and return a decimal. So far we have the following code:

public static class MathsEvaluator
{
    public static decimal Parse(string expression)
    {
    }
}

For this point only go back to the Form, and add an event handler to the button click event to call the Parse method and display the result on the label:

private void button1_Click(object sender, EventArgs e)
{
    label1.Text = MathsEvaluator.Parse(textBox1.Text).ToString();
}

We have set up our project, and created an empty method which will handle our evaluation process. So it's time to think about what should the Parse method do.

The idea is that this method will be called recursively, so there are 2 kinds of "expressions" it can get as parameter: either a number or a more complex expression (standing from operations).

Parse a Decimal

Handling the number is easy, we just have to return it as decimal. So let's do this, we will use the decimal.TryParse(string, out decimal) method to decide if we are lucky and the expression was just a decimal value. Note that it has a decimal out parameter containing the parsed value, if it was able to parse, so we will just return it in this case:

decimal d;
if (decimal.TryParse(expression, out d))
{
    //The expression is a decimal number, so we are just returning it
   return d;
}
else
{
}

We can even run our first test now. Just add a return 0; in the else part (or comment out the else statement), because without it, it won't compile, and then run the program, enter some decimal value to the textbox and press the button, the value should appear in the label. This isn't a big magic, but at least we can see that what we did so far is working. Remember to remove the statement added to the else block for further development.

Parse an Expression

To parse an expression, it will be a little more complicated than dealing with a number. We will have to do the following:

  1. Tokenize the input string (split it into pieces)
  2. Group the elements into operations
  3. Evaluate an operation

First of all, create a new method called CalculateValue getting a string as parameter and returning decimal. This will take care of the evaluation of an expression, and we can keep the Parse method clean by calling this method, instead of writing the code there. So add the following line in the Parse method in the else block:

return CalculateValue(expression);

And create the new method:

private static decimal CalculateValue(string expression)
{
}

Tokenizing the String

We will split the expression string into elements (let me explain what I mean by an element in this article: an element can be an operand (another expression or a number), or an operator) by iterating through the string character by character and splitting it before and after an operator is found. Note that we are not dealing with the parentheses at this point to simplify the problem.

We will need a List for the elements, and a string to store the element that we are currently reading. And also, we are defining a char array which will contain a list of the supported operators.

char[] operators = {'+', '-', '*', '/'};
List<string> elements = new List<string>();
string currentElement = string.Empty;

Now let's iterate through the string, and do the following:

  • If the character is ' ' (space), we simply skip it
  • If the character is not a space and not an operator, we add it to the end of the currentElement string
  • If the character is an operator we reached the end of an element, so we add the currentElement to the list, then we add the operator itself to the list, and then we set the currentElement to an empty string, so we can collect characters for the next element.
for (int i = 0; i < expression.Length; i++)
{
    if (operators.Contains(expression[i]))
    {
        //The current character is an operator
        elements.Add(currentElement);
        elements.Add(expression[i].ToString());
        currentElement = string.Empty;
    }
    else if (expression[i] != ' ')
    {
        //The current character is neither an operator nor a space
        currentElement += expression[i];
    }
}

After the for loop, the currentElement might contain some characters (actually it should, to be a proper expression) which we didn't add to the list, because an operator didn't follow it, so let's add it now:

if (currentElement.Length > 0)
{
    elements.Add(currentElement);
}

Group Operations

We have a tokenized string, it's time to compose our first operation from it. We will use the content of the whole list as one operation, by selecting an operator, an operand, and the rest of the list will be the other operand.

To evaluate the operations from left to right, we take the last element to be the right hand operand, the element before it will be the operator and the rest of the list will be the left hand operand. This means that the right hand operand will be simply a decimal value, while the left can be either a decimal value (in case the input was something simple like: 2+5) or an expression. As shown in Figure-2, we call Evaluate (in our implementation it's Parse) for both of the operands, which will result in that the left operand will be tokenized and grouped again, and so on, until it will be simply a number and then we can calculate the result.

An example: If the input was 1+2+3-4, we calculate it in the following way (1+2+3)-(4) => ((1+2)+(3))-4 => (((1)+(2))+3)-4 => (3+3)-4 => 6-4 => 2.
The brackets here mean that the Parse method will be called with that expression as parameter.

To do this, define three strings: oper will be the operator, operand1, and operand2 will be the two operands.

string oper, operand1 = string.Empty, operand2;

Take the last element from the list to operand2, the one before as the operator, and the rest of the list as operand1.

operand2 = elements.Last();
elements.RemoveAt(elements.Count - 1);

oper = elements.Last();
elements.RemoveAt(elements.Count - 1);

while (elements.Count > 0)
{
    operand1 = elements.Last() + operand1;
    elements.RemoveAt(elements.Count - 1);
}

Create a new method to evaluate an operation, call it EvaluateOperation which takes three string parameters and returns a decimal. As the last line in the CalculateValue method, call EvaluateOperation and return the value which it returns.

return EvaluateOperation(oper, operand1, operand2);

Evaluate an Operation

We already called the EvaluateOperation method, so let's write it.

We start with a check if the operator, that the method received, is only 1 character long (we only support operators which stand from a single character), and throw an ArgumentException if not:

private static decimal EvaluateOperation
    (string oper, string operand1, string operand2)
{
    if (oper.Length == 1)
    {

    }
    else
    {
        throw new ArgumentException("Unsupported operator");
    }
}

The rest of the code goes in the body of the if statement.
Here is the place for the recursive call, which will convert the two string operands to decimal values:

decimal op1 = Parse(operand1);
decimal op2 = Parse(operand2);

With the decimal values of the operands, we can easily evaluate the value of the operation with a switch case construction, and then return the obtained value. In the default path, let's throw an exception indicating that other operators are not supported.

decimal value = 0;
switch (oper[0])
{
    case '+':
        value = op1 + op2;
        break;
    case '-':
        value = op1 - op2;
        break;
    case '*':
        value = op1 * op2;
        break;
    case '/':
        value = op1 / op2;
        break;
    default:
        throw new ArgumentException("Unsupported operator");
}
return value;

We got to the recursive call, and also did some real mathematical computation, sounds like we got to the end. Well, unfortunately not yet. Anyway it's a great time to test the solution, and take a break to drink a coffee/tea/beer/... whichever you prefer.

During the test, we should notice a few things:

  • All operations are treated like if they would have the same precedence
  • Parentheses are still not supported

Precedences

To support different precedence levels, let's change the way in which we store the operators in the CalculateValue method. Change the char array to a Dictionary with a char key (this will be the operator) and an int value (this will be the precedence of the operator):

Dictionary<char, int> operators = new Dictionary<char, int>
{
    {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}
};

A Dictionary is a collection of Key-Value pairs. Here we use the operators as keys, and their value is their precedence level.

I added the addition and subtraction with precedence 1, and multiplication and division with precedence 2.

To use this Dictionary, the evaluation process has to be changed. The new process can be summarized as the following:

  1. Start with the highest level of precedence
  2. Evaluate each operation which belongs to this level from left to right
  3. Replace the two operands and the operator in the element list with the calculated value
  4. Go to the next level
  5. If there are no more levels, return the value obtained by the last evaluation call

To change the evaluation process, delete everything in the CalculateValue method from this line until the end of the function:

string oper, operand1 = string.Empty, operand2;

The New Evaluation Process

The following code should be placed in the place of the previously removed lines in the CalculateValue method.

Define a decimal variable to store the calculated value, we will also return this at the end of the function, and a for loop from the highest precedence present in the operators dictionary until the lowest.

//define a value which will be used as the return value of the function
decimal value = 0;

//loop from the highest precedence to the lowest
for (int i = operators.Values.Max(); i >= operators.Values.Min(); i--)
{

}

return value;

Since the precedence levels for the operators are stored as values in the Dictionary, we used the Max() and Min() LINQ queries on the ValueCollection of the operators dictionary. The LINQ queries are available in the System.Linq namespace. Add the following to the using directives (if not included yet):

using System.Linq;

Since LINQ is probably not a beginner topic, let's see what is it:
"Language-Integrated Query (LINQ) is a set of features introduced in Visual Studio 2008 that extends powerful query capabilities to the language syntax of C# and Visual Basic. LINQ introduces standard, easily-learned patterns for querying and updating data, and the technology can be extended to support potentially any kind of data store." Source: MSDN.
The important part for us now is the following: It adds some extra functionality to data sources which implement the IEnumerable or IEnumerable<T> interface, as our collection does.

The rest of the code should be placed inside the for loop.

We need another loop which runs while there are any operations left from the current precedence level in the elements list. Let's use a while loop and first check if there are at least 3 elements in the list (an operator and the two operands), then the condition described before:

//loop while there are any operators left
// in the list from the current precedence level
while (elements.Count >= 3
                    && elements.Any(element => element.Length == 1
                        && operators.Where(op => op.Value == i)
                             .Select(op => op.Key).Contains(element[0])))
{
}

I used lambda expressions as parameters for the LINQ queries to perform this check, and I will explain what is happening here in detail in a moment.

But first about lambda expressions in a few words:

"A lambda expression is an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types. All lambda expressions use the lambda operator =>, which is read as "goes to". The left side of the lambda operator specifies the input parameters (if any) and the right side holds the expression or statement block." Source: MSDN

For example, in the condition of the while loop above, there is a Where query on the operators Dictionary, the Where method waits a function as parameter to test each element for a condition, with the following type: Func<KeyValuePair<char,int>, bool>, which means that the parameter of the function is a KeyValuePair with char Key and int Value (an entry in the operators dictionary), and it should return a bool value. We provided a lambda expression as this function, so the left side of the expression should be one variable name (its type will be KeyValuePair<char,int>), here it is op. The right side in our example should be a boolean expression, which will indicate whether an element satisfies the Where condition and should be included in the collection returned by the Where() method, so we check whether the Value of the parameter(op) equals to the current precedence level(i), and the value of this comparison will be the returned value of this lambda expression.

Another example:

//lambda expression
(x) => x > 5

//if it would be a function
bool foo(int x)
{
    return x > 5;
}

This was just an introduction to lambda expressions, many articles can be found on the internet covering this topic in much more detail.

I think the beginning (elements.Count >= 3) is clear, it's just a condition to check if there are at least 3 elements in the list.
Next we check if any of the elements satisfy the condition given in the brackets: elements.Any(<condition>), this returns a bool value indicating whether there are any elements which satisfy the condition.
The condition is fulfilled if the element is an operator from the current precedence level. This will be a lambda expression where the parameter is the currently checked element from the elements list (element), first we check if its length is 1 (all of our operators are just 1 character, so it has to satisfy this condition), then we check if it is contained in a list made from the operator characters from the current precedence level:
To get the list of the operators where the Value(precedence) of the operator equals the current level(i), the following is performed: operators.Where(op => op.Value == i). This is a Where condition used on the dictionary, which returns a part of the dictionary with elements which satisfy the condition in the brackets. The condition used here is a lambda expression returning a bool value, true if the Value of the dictionary entry equals i (as explained in the note). This will return a collection of KeyValuePairs, but we just need the list of the operators, so we perform a Select operation, to get a collection of chars, and we check if the collection contains an element which equals with the first character of the currently checked element from the elements list.

Now the body of the while loop.

We get the position of the operator which satisfied the while condition. This done again with lambda expressions, but we use almost the same query, the only difference will be that instead of the Any operation we use here the FindIndex operation, which returns the first index of the element which fulfills the given condition:

//get the position of this element
int pos = elements
	.FindIndex(element => element.Length == 1 &&
	operators.Where(op => op.Value == i)
	.Select(op => op.Key).Contains(element[0]));

So now we have the index of the operator, the two operands will be the elements right before and right after this index, with these we can call the EvaluateOperation method:

//evaluate it's value
value = EvaluateOperation(elements[pos],
	elements[pos - 1], elements[pos + 1]);

The next thing is to change these three elements in the elements list to the one value obtained. To achieve this, change the first to the new value, and delete the following two elements:

//change the first operand of the operation
// to the calculated value of the operation
elements[pos - 1] = value.ToString();
//remove the operator and the second operand from the list
elements.RemoveRange(pos, 2);

The evaluator is now ready to calculate the value of an expression evaluating the operations in the proper order.

Note: The whole code of the CalculateValue method (after some maintenance) will be included in the beginning of the next section.

The Parentheses

To support parentheses in the expressions, we will modify the tokenizer code. First to keep things clean, move the current tokenizer code to a new method named TokenizeExpression returning a List<string> and taking a string (the expression) and a Dictionary<char, int> (the operators) as parameter. The new method should look like this:

private static List<string> TokenizeExpression
            (string expression, Dictionary<char, int> operators)
{
    List<string> elements = new List<string>();
    string currentElement = string.Empty;
    for (int i = 0; i < expression.Length; i++)
    {
        if (operators.Keys.Contains(expression[i]))
        {
            //The current character is an operator
            elements.Add(currentElement);
            elements.Add(expression[i].ToString());
            currentElement = string.Empty;
        }
        else if (expression[i] != ' ')
        {
            //The current character is neither an operator nor a space
            currentElement += expression[i];
        }
    }

    //Add the last element (which follows the last operation) to the list
    if (currentElement.Length > 0)
    {
        elements.Add(currentElement);
    }

    return elements;
}

And you should have a CalculateValue method similar to this:

public static decimal CalculateValue(string expression)
{
    //Dictionary to store the supported operations
    //Key: Operation;
    //Value: Precedence (higher number indicates higher precedence)
    Dictionary<char, int> operators = new Dictionary<char, int>
    {
        {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'^', 3}
    };

    //Tokenize the expression
    List<string> elements = TokenizeExpression(expression, operators);

    //define a value which will be used as the return value of the function
    decimal value = 0;

    //loop from the highest precedence to the lowest
    for (int i = operators.Values.Max(); i >= operators.Values.Min(); i--)
    {
        //loop while there are any operators left
        // in the list from the current precedence level
        while (elements.Count >= 3
            && elements.Any(element => element.Length == 1 &&
                operators.Where(op => op.Value == i)
                .Select(op => op.Key).Contains(element[0])))
        {
            //get the position of this element
            int pos = elements
                .FindIndex(element => element.Length == 1 &&
                operators.Where(op => op.Value == i)
                .Select(op => op.Key).Contains(element[0]));

            //evaluate it's value
            value = EvaluateOperation(elements[pos],
                elements[pos - 1], elements[pos + 1]);
            //change the first operand of the operation
            // to the calculated value of the operation
            elements[pos - 1] = value.ToString();
            //remove the operator and the second operand from the list
            elements.RemoveRange(pos, 2);
        }
    }

   return value;
}

Now, back to the brackets (I use the terms parentheses and brackets mixed in the article, in both cases I mean the same thing: "()"), and the new TokenizeExpression method.

Tokenizing using a State Machine

We will change the tokenizer code to a simple state machine. It will have three states:

  • 0 - normal mode
  • 1 - after an opening bracket '('
  • 2 - after a closing bracket ')'

See in Table-1 below the idea of the state machine:

State Next character
operator
(+,-,*,/)
not space ) (
0 0 / Add the collected string and the operator to the list. 0 / Copy character to the string. 1 / Set bracket count to 0.
If the collected string is not empty, add it to the list, and add '*' also.
1 1 / Copy character to the string. If (bracket count is 0): 2 / -

Else: 1 / decrease bracket count, copy character to the string.
1 / Increase bracket count. Copy character to the string.
2 0 / Add the collected string and the operator to the list. 0 / Add the collected string, and '*' to the list. Set the string to the new character. 1 / Set bracket count to 0.
If the collected string is not empty, add it to the list, and add '*' also.

Table-1 The description of the state machine

A figure concentrating only on the state transitions:

State transitions

Figure-3 The state transitions used by the state machine
(the empty arrow means 'Any other case')

A little explanation: The basic idea is still, that we are copying the characters to a string and when an operator comes, add it to the list followed by the operator, and start to collect again. You can see this in the first two cells of the first row. Then if an opening bracket comes, we will change state to 1.

Inside the parentheses, we will count the number of brackets we went throw, with +1 for opening bracket and -1 for closing, so the closing bracket, for the opening bracket we received in state 0, will be the one which we get when the bracket count is 0. In order to make this work, we set the bracket count to 0. And the next thing is just a little extra, it makes possible to understand this for example 5(1+2) as 5*(1+2). So in case we received an opening parenthesis and the collected string wasn't empty (so it didn't follow an operator, where we empty it), then we add the element that it contains to the list, and also the '*' operator.

In state 1, a.k.a. inside the outer most parentheses, we just simply copy every character to the string, and keep track of how many brackets have we seen, in order to recognize the proper closing bracket for the opening one, which took us to state 1 (Imagine: ((((5*2)+1)-2(12/4))/2)-2 ). If we got to the closing bracket we were looking for, then change the state to 2.
Note: We could change the state simply to 0 and we would be ready, but the 3rd state is added to handle the following situation: (1+2)5 or (1+2)(2+3). Since we added support to omit the sign of multiplication before a parentheses expression, we should also offer the support to omit it after.

In state 2, if we get an operator or an opening bracket, we do the same thing as in state 0 (We already solved the missing multiplication sign in case of an opening bracket). Any other case changes the state to 0 and add the expression in the string to the list, and add the multiplication operator also, and because this character is the first character of the next expression, set it as the new value of the string which we are using to collect the next expression.

The Implementation of the State Machine

First, let's add a variable to store the state in front of the for loop:

int state = 0;

Now let's add a switch case to the for loop to implement different behavior for each case. And add the code we already have in the first case path (case 0: ... break;)

switch (state)
{
    case 0:
        if (operators.Keys.Contains(expression[i]))
        {
            //The current character is an operator
            elements.Add(currentElement);
            elements.Add(expression[i].ToString());
            currentElement = string.Empty;
        }
        else if (expression[i] != ' ')
        {
            //The current character is neither an operator
            // nor a space
            currentElement += expression[i];
        }
        break;
    case 1:
        break;
    case 2:
        break;
}

Change it to recognize the opening bracket:

case 0:
    if (expression[i] == '(')
    {
        //Change the state after an opening bracket is received
        state = 1;
        bracketCount = 0;
        if (currentElement != string.Empty)
        {
            //if the currentElement is not empty,
            // then assuming multiplication
            elements.Add(currentElement);
            elements.Add("*");
            currentElement = string.Empty;
        }
    }
    else if (operators.Keys.Contains(expression[i]))
    {
        //The current character is an operator
        elements.Add(currentElement);
        elements.Add(expression[i].ToString());
        currentElement = string.Empty;
    }
    else if (expression[i] != ' ')
    {
        //The current character is neither an operator
        // nor a space
        currentElement += expression[i];
    }
    break;

Now let's implement the case 1:

case 1:
    if (expression[i] == '(')
    {
        bracketCount++;
        currentElement += expression[i];
    }
    else if (expression[i] == ')')
    {
        if (bracketCount == 0)
        {
            state = 2;
        }
        else
        {
            bracketCount--;
            currentElement += expression[i];
        }
    }
    else if (expression[i] != ' ')
    {
        //Add the character to the current element,
        // omitting spaces
        currentElement += expression[i];
    }
    break;

And finally the case 2:

case 2:
    if (operators.Keys.Contains(expression[i]))
    {
        //The current character is an operator
        state = 0;
        elements.Add(currentElement);
        currentElement = string.Empty;
        elements.Add(expression[i].ToString());
    }
    else if (expression[i] != ' ')
    {
        elements.Add(currentElement);
        elements.Add("*");
        currentElement = string.Empty;

        if (expression[i] == '(')
        {
            state = 1;
            bracketCount = 0;
        }
        else
        {
            currentElement += expression[i];
            state = 0;
        }
    }
    break;

Manage Incorrect Input

So far, we have got a working expression parser and evaluator, if we provide correct input. Now we will provide an option for the user to validate the input, and to try to parse it, without getting exceptions.

Check Input Characters

A basic input validation would be to check whether the string contains illegal characters. The characters allowed for an expression would be the following: 0-9 (numbers); '+'; '-'; '*'; '/'; '('; ')'; '.'; ','; ' '(space).

We create a function which checks if the string contains only these characters with trying to match the following regular expression: ^[0-9+*-/()., ]+$

public static bool IsExpression(string s)
{
    //Determines whether the string contains illegal characters
    Regex RgxUrl = new Regex("^[0-9+*-/()., ]+$");
    return RgxUrl.IsMatch(s);
}

For this, the following using directive is required:

using System.Text.RegularExpressions;

TryParse() Method

As seen in built-in classes, we will provide a TryParse() method, which returns a boolean value indicating whether the expression can be parsed, and also the evaluated value as an out parameter.

public static bool TryParse(string expression, out decimal value)

To check if it can be evaluated, check first if it contains any illegal characters, then call the Parse method surrounded with a try catch block, to catch any exceptions.

public static bool TryParse(string expression, out decimal value)
{
    if (IsExpression(expression))
    {
        try
        {
            value = Parse(expression);
            return true;
        }
        catch
        {
            value = 0;
            return false;
        }
    }
    else
    {
        value = 0;
        return false;
    }
}

Basically, we just call Parse, but catch any exceptions instead of the user, and return false if any occurred. If everything ran fine, we return true and the evaluated value in the out parameter.

Let's change the code in our demo application to use this method, in the button event handler:

private void button1_Click(object sender, EventArgs e)
{
    decimal d;
    if (MathsEvaluator.TryParse(textBox1.Text, out d))
    {
        label1.Text = d.ToString();
    }
    else
    {
        label1.Text = "Unable to evaluate expression,"
            + " possible incorrect syntax.";
    }
}

Extensibility

I would like to show how the solution can be extended to provide more functionality than that was required in the beginning.

The ^ Operator

This section provides an example of adding support for a new operation which has two operands and the operator is a single character as the previously implemented ones.

The new operator will be ^(aka power, e.g. 2^3 = 8 or 2^(5+5) = 1024). It has higher precedence than any other implemented (we will mark it as 3).

Let's see a list of required changes:

  1. Include it in the regular expression used in IsExpression
  2. Add it to the operators Dictionary in the CalculateValue method
  3. Finally, implement it in the EvaluateOperation function

Implementation

  1. Change the regular expression line, to:
    Regex RgxUrl = new Regex("^[0-9+*-/^()., ]+$");
  2. Change the operators Dictionary definition to:
    Dictionary<char, int> operators = new Dictionary<char, int>
    {
        {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'^', 3}
    };
  3. Implement the ^ operation. To do this, we will use the double Math.Pow(double, double) method, but we have to convert the parameters to double and the returned value to decimal, because the method works on doubles and we are using decimals in the class. Add the following to the switch block in the EvaluateOperation method:
    case '^':
        value = Convert.ToDecimal(
            Math.Pow(Convert.ToDouble(op1),
                Convert.ToDouble(op2)));
        break;

That's it! Now the evaluator supports the power operation as well.

History

  • First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Akos Orban
Student
Poland Poland
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberRyanEK18-Dec-11 15:33 
GeneralMy vote of 5 PinmemberTushar_Patil12-Dec-11 18:22 
GeneralMy vote of 5 PinmemberThorsten Bruning12-Dec-11 5:02 
A very detailed article - thank you.
QuestionThoughts PinmemberPIEBALDconsult12-Dec-11 4:02 
AnswerRe: Thoughts PinmemberAkos Orban13-Dec-11 2:19 
GeneralMy vote of 5 PinmemberFilip D'haene12-Dec-11 3:27 
GeneralRe: My vote of 5 PinmemberAkos Orban13-Dec-11 2:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 12 Dec 2011
Article Copyright 2011 by Akos Orban
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid