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");
MathsEvaluator.IsExpression("5*2+b");
MathsEvaluator.IsExpression("12,4+*");
bool b = MathsEvaluator.TryParse("1+2+3+4+5*0", out d);
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.
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.
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))
{
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:
- Tokenize the input
string
(split it into pieces) - Group the elements into operations
- 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]))
{
elements.Add(currentElement);
elements.Add(expression[i].ToString());
currentElement = string.Empty;
}
else if (expression[i] != ' ')
{
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:
- Start with the highest level of precedence
- Evaluate each operation which belongs to this level from left to right
- Replace the two operands and the operator in the element list with the calculated value
- Go to the next level
- 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.
decimal value = 0;
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:
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:
(x) => x > 5
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 char
s, 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:
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:
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:
elements[pos - 1] = value.ToString();
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]))
{
elements.Add(currentElement);
elements.Add(expression[i].ToString());
currentElement = string.Empty;
}
else if (expression[i] != ' ')
{
currentElement += expression[i];
}
}
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<char, int> operators = new Dictionary<char, int>
{
{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'^', 3}
};
List<string> elements = TokenizeExpression(expression, operators);
decimal value = 0;
for (int i = operators.Values.Max(); i >= operators.Values.Min(); i--)
{
while (elements.Count >= 3
&& elements.Any(element => element.Length == 1 &&
operators.Where(op => op.Value == i)
.Select(op => op.Key).Contains(element[0])))
{
int pos = elements
.FindIndex(element => element.Length == 1 &&
operators.Where(op => op.Value == i)
.Select(op => op.Key).Contains(element[0]));
value = EvaluateOperation(elements[pos],
elements[pos - 1], elements[pos + 1]);
elements[pos - 1] = value.ToString();
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:
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]))
{
elements.Add(currentElement);
elements.Add(expression[i].ToString());
currentElement = string.Empty;
}
else if (expression[i] != ' ')
{
currentElement += expression[i];
}
break;
case 1:
break;
case 2:
break;
}
Change it to recognize the opening bracket:
case 0:
if (expression[i] == '(')
{
state = 1;
bracketCount = 0;
if (currentElement != string.Empty)
{
elements.Add(currentElement);
elements.Add("*");
currentElement = string.Empty;
}
}
else if (operators.Keys.Contains(expression[i]))
{
elements.Add(currentElement);
elements.Add(expression[i].ToString());
currentElement = string.Empty;
}
else if (expression[i] != ' ')
{
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] != ' ')
{
currentElement += expression[i];
}
break;
And finally the case 2
:
case 2:
if (operators.Keys.Contains(expression[i]))
{
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;
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.
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)
{
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 exception
s 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:
- Include it in the regular expression used in
IsExpression
- Add it to the
operators
Dictionary
in the CalculateValue
method - Finally, implement it in the
EvaluateOperation
function
Implementation
- Change the regular expression line, to:
Regex RgxUrl = new Regex("^[0-9+*-/^()., ]+$");
- Change the
operators Dictionary
definition to:
Dictionary<char, int> operators = new Dictionary<char, int>
{
{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'^', 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 double
s and we are using decimal
s 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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.