11,429,154 members (51,658 online)

# Math Parser .NET

, 30 Oct 2011 CPOL
 Rate this:
Please Sign up or sign in to vote.
A very easy to use mathematical parser library for .NET.

## Introduction

A while back, I wrote an article on CodeProject called TokenIcer. TokenIcer was a program that would automatically create a lexical parser in either C# or VB.NET, based on RegEx rules created in the program.

Since the time that I wrote the article, I have gotten some very good feedback and I wanted to take it a step further. I decided to use TokenIcer to create a mathematical equation parser. That math parser is presented here in this article and it is called Math Parser .NET. It is a .NET Class Library project that can be used with your own programs.

## Background

There are several math parsers that exist already. The goal of my math parser is to keep it as simple to use and operate as possible. One of my biggest pet peeves is when a good library goes "bad" because its simplicity gets overshadowed by its own feature list. I feel that my math parser does its job well while keeping the basic functionality and usage simple.

## Using the Code

Before I go into how to use the library, I would like to first go through how the internals of the library work. The library itself first consists of a lexical analyzer. The lexical analyzer, I created automatically, by using TokenIcer. The lexical analyzer will take an input string like this:

`1 + 2 * 3 + (9 / 3) `

and convert it into enumerated tokens, like this:

```{Integer}{WhiteSpace}{Add}{WhiteSpace}{Integer}{WhiteSpace}{Multiply}
{WhiteSpace}{Integer}{WhiteSpace}{Add}{WhiteSpace}{LParen}
{Integer}{WhiteSpace}{Divide}{WhiteSpace}{Integer}{RParen}```

Once there are no more tokens to process, the lexical analyzer will return a null token.

The next job is to create a loop to cycle through each token until the null token is received. The input received by the parser will be in what's called Infix notation. Infix notation means that numbers are separated by mathematical operators. Like the example above, 1 and 2 is separated by a plus sign. 2 and 3 is separated by a multiplication sign. In addition, math operators have a specific order of precedence. In the example above, 9 / 3 should be evaluated first since it is in parenthesis. 2 * 3 should be evaluated secondly since multiplication and division have higher precedence than addition. In order to more easily parse math expressions, I convert the equation from Infix notation to Reverse Polish Notation. In Reverse Polish Notation, or RPN, the math symbols come after the numbers. So after the conversion, the example input above would look similar to this:

`1 2 3 * 9 3 / + +`

Once the equation is in RPN notation, it is a simple matter of reading it left to right and doing the actual math.

To use the MathNetLib library in your own program, all you have to do is add a reference to the MathNetLib DLL file. After doing that, you need to instantiate a new instance of the `MathNetLib.Parser` class. There are three main methods for solving equations. The first is called `SimplifyInt()`. `SimplifyInt()` will take a mathematical equation, solve it, and return an integer answer. There are two overloads to this method. Here is the first and easiest example:

```static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
int retval;

try {
retval = parser.SimplifyInt("3.2 + 7.6");
} catch (Exception ex) {
throw ex;
}
}
```

This example will return a value of 11. The thing to keep in mind about `SimplifyInt()` is that it will always return an integer answer and by default, it will round the answer. `SimplifyInt()` has an overloaded method signature that will also allow you to specify what you want to do with the fractional part of the answer, if there is one. Here is an example:

```static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
int retval;

try {
retval = parser.SimplifyInt("3.2 + 7.6",
MathParserNet.Parser.RoundingMethods.Truncate);
} catch (Exception ex) {
throw ex;
}
}```

`RoundingMethods` is an enum with the following values and descriptions:

• `Round` -- This is the default. Rounds the fraction to the nearest whole number. If the number after the decimal point is a 5 or higher, it will round the whole number up. If it is 4 or lower, it will round the whole number down.
• `RoundDown` -- This will always round the whole number down regardless of the number after the decimal point.
• `RoundUp` -- This will always round the whole number up regardless of the number after the decimal point.
• `Truncate` -- This will not do any rounding at all and will simply "cut off" the decimal point and any numbers after it.

The second main method for solving equations is called `SimplifyDouble()`. This method, like `SimplifyInt()`, will solve the equation, but will always return a `double` answer. Here is an example of this method in action:

```static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
double retval;

try {
retval = parser.SimplifyDouble("3.2 + 7.6");
} catch (Exception ex) {
throw ex;
}
}```

This would return an answer of 10.8.

The third and final version for solving math equations is called, simply, `Simplify()`. `Simplify()` will solve an equation and then automatically determine if it is an integer or a floating point answer. `Simplify()` returns a `MathParserNet.SimplificationReturnValue` object. You can check the `ReturnType` property of this object to determine if the answer is a floating point or a whole integer number. Here is an example of its use:

```static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
MathParserNet.SimplificationReturnValue retval;

try {
retval = parser.Simplify("3.2 + 7.2");
} catch (Exception ex)
throw ex;
}
if (retval.ReturnType == MathParserNet.SimplificationReturnValue.ReturnTypes.Float)
{
Console.WriteLine("The answer is a Floating point number!");
Console.WriteLine(retval.DoubleValue);
}
if (retval.ReturnType == MathParserNet.SimplificationReturnValue.ReturnTypes.Integer)
{
Console.WriteLine("The answer is an Integer!");
Console.WriteLine(retval.IntValue);
}```

Anytime you use any of the simplify functions, you should always wrap it in a `try/catch` block. MathParserNet implements five exceptions. Here is a description of each one:

• `MismatchedParenthesisException` -- This exception is thrown if the equation you are trying to solve has more opening parenthesis than closing parenthesis or vice versa.
• `NoSuchFunctionException` -- This exception is thrown if you try to use a function that has not yet been defined. Functions are described below.
• `NoSuchVariableException` -- This exception is thrown if you try to use a variable that has not yet been defined. Variables are described below.
• `VariableAlreadyDefinedException` -- This exception is thrown if you try to define a variable that has already been defined earlier. Variables are described below.
• `CouldNotParseExpressionException` -- This exception is thrown if any other problem arises while trying to parse your expression. Passing an empty equation to any of the simplify functions will cause this exception to be raised.

The parser can parse the following things:

• () -- Parenthesis
• + -- Add symbol (3 + 2)
• - -- Subtract symbol (3 - 2)
• * -- Multiplication symbol (3 * 2)
• / -- Divide symbol (3 / 2)
• % -- Modulus symbol (3 % 2)(divides the two numbers, but returns the remainder)
• ^ -- Exponent symbol (3 ^ 2)(squares 3)
• ABS -- Function returns the absolute of a number (ABS(-3))
• SIN -- Returns the sine of a number (SIN(3.14))
• COS -- Returns the cosine of a number (COS(3.14))
• TAN -- Returns the tangent of a number (TAN(3.14))
• LOG -- Returns the base 10 logarithm of a number
• LOGN -- Returns the natural logarithm of a number
• func<name> -- Calls a user defined function (see Functions below)

## Variables

MathParserNet supports the use of variables. You define a variable in MathParserNet using the `AddVariable()` method. Here is an example of adding three variables to the parser:

```static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();

try {
parser.AddVariable("PI", 3.14159265);
parser.AddVariable("Three", 3);
parser.AddVariable("HalfPI", "PI / 2");

parser.SimplifyDouble("HalfPI * Three + 7");
} catch (Exception ex) {
throw ex;
}
}```

Variables are kind of like cut and paste in a word processor. When the parser sees a variable, it looks up the value and replaces the variable name with the actual value. So `HalfPI` actually becomes "3.14159265 / 2" and the equation passed into the `SimplifyDouble()` method actually becomes "3.14159265 / 3 + 7".

Variables that have been defined can also be removed by calling the `RemoveVariable()` method. You simply pass the variable name as an argument to `RemoveVariable()` and the variable gets removed. Keep in mind that any variable that has been defined with a removed variable becomes invalid. For example, in the code above, if I remove the `PI` variable, then the `HalfPI` variable also becomes invalid. In addition, you can also remove all variables created by calling the `RemoveAllVariables()` method.

Another thing to keep in mind is that variable names are case-sensitive. PI is different from Pi or pI or pi.

## Functions

In addition to variables, MathParserNet also supports functions. You can define a function to do pretty much anything you want. Here is an example:

```static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();

try {
parser.AddFunction("GetSlope",
new MathParserNet.FunctionArgumentList {"x1", "x2",
"y1", "y2"}, "(y1-y2)/(x1-x2)");

parser.SimplifyDouble("3 * funcGetSlope(3.2 * (9/3), 4.5, 6, 9.2)");
} catch (Exception ex) {
throw ex;
}
}```

This example creates a function called `GetSlope`. The function takes four parameters (x1,x2,y1,y2). Functions must take at least one parameter and can be defined to take as many as you want. Keep in mind though that function parameter names can not have the same name as a defined variable. To call a function, you must prefix the function name with "func" (without quotes, of course). Another thing to keep in mind is that, like variables, functions are also case-sensitive (as is the "func" prefix).

Functions, just like variables, can similarly be undefined by calling the `RemoveFunction()` method. As with variables, you simply pass the function name in as a parameter of `RemoveFunction()`. In addition, you can remove all functions created by calling the `RemoveAllFunctions()` method.

## Delegate Functions

Perhaps the coolest, and arguably, the most handy feature of MathParserNet is the ability to create your own custom function in any .NET language and use it as a custom function in the math parser. Here is an example that shows our `GetSlope` function as a delegate function:

```static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
int myDouble;
double area;
double slope;

try {
parser.RegisterCustomFunction("GetSlope", GetSlope);
parser.RegisterCustomFunction("Doubler", Doubler);
parser.RegisterCustomFunction("AreaCircle", AreaCircle);

area = parser.SimplifyInt("AreaCircle(3.7)");
myDouble = parser.SimplifyDouble("Doubler(7)");
slope = parser.SimplifyDouble("GetSlope(-3.2, 12, 8, 13)");
} catch (Exception ex) {
throw ex;
}
}

static int Doubler(int num)
{
return num * 2;
}

static double AreaCircle(double radius)
{
return Math.PI * (radius * radius);
}

static double GetSlope(double x1,double x2,double y1, double y2)
{
return (y1-y2)/(x1-x2);
}```

Delegate functions can have up to 4 parameters. The return type must also be the same as the parameters and the parameters must all be of the same type. The parameters can be `int`, `double`, or `object`. Integer and double types are self-explanatory. If you declare `object` type parameters, then the objects will be of type `MathParserNet.SimplificationReturnValue`. This way your function can take both integer and double parameters. In addition, your return type must be `object`, but that object can be either integer or double. The math parser will take care of everything else!

As with variables and functions, delegate functions can be removed by using the `UnregisterCustomFunction()` method. You simply pass the function name as a parameter. In addition, you can remove all delegate functions defined by calling the `UnregisterAllCustomFunctions()` method.

## Miscellaneous Methods

There is also a `Reset()` method. By calling this method, the parser will remove all variables and all functions and all delegate functions created. It is like starting with a brand new, freshly instantiated `Parser` class.

One last minor feature to talk about is the `ToFraction()` function. Whenever a function returns a `SimplificationReturnValue`, that object has a method called `ToFraction()` which will convert the returned value into a fraction. For example, if your `SimplificationReturnValue` object had a `DoubleValue` of 0.25, then calling `ToFraction()` would return "1/4". I will leave it up to you to play around with that.

## Extras

I have included a unit test project for testing out the math parsing library. Also, there is a demo Windows Forms project that showcases a lot of the features (if not all) of MathParserNET. If anyone has any questions or feedback, I would always love to hear!

## History

• 10/30/2011 -- Version 1.1 released. This version now throws exceptions instead of returning values, in the case of an error. In addition, delegate methods have been introduced into this version. Also, the `SimplifyInt` and `SimplifyDouble` methods have been created. In addition, I have created a true unit testing project for the Math library and a demo application. Thanks for all the feedback and keep it coming guys!
• 10/26/2011 -- Initial version released.

## License

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

## About the Author

Web Developer
United States
No Biography provided

## Comments and Discussions

 First PrevNext
 Separate parse and evaluate Member 112767231-Dec-14 1:47 Member 11276723 1-Dec-14 1:47
 My vote of 5 Keex0r29-Nov-14 2:34 Keex0r 29-Nov-14 2:34
 Compile error starlily84062918-Sep-14 23:44 starlily840629 18-Sep-14 23:44
 This rocks Member 105588534-Apr-14 6:19 Member 10558853 4-Apr-14 6:19
 Event extension Mike Warden16-May-13 1:20 Mike Warden 16-May-13 1:20
 Parser.Simplify does not work as it should [modified] Salem Hammami16-Apr-13 6:53 Salem Hammami 16-Apr-13 6:53
 Re: Parser.Simplify does not work as it should icemanind22-Apr-13 16:39 icemanind 22-Apr-13 16:39
 My vote of 4 tumbledDown2earth15-Apr-13 22:48 tumbledDown2earth 15-Apr-13 22:48
 implement integratet function filmee2419-Dec-12 5:37 filmee24 19-Dec-12 5:37
 Re: implement integratet function Georg Scholz18-Jan-13 2:00 Georg Scholz 18-Jan-13 2:00
 Problem alterjo15-Nov-12 2:11 alterjo 15-Nov-12 2:11
 Re: Problem Andres Raieste3-Dec-12 22:48 Andres Raieste 3-Dec-12 22:48
 Re: Problem icemanind5-Dec-12 5:16 icemanind 5-Dec-12 5:16
 Problem alterjo15-Nov-12 1:55 alterjo 15-Nov-12 1:55
 Vorzeichenbehandlung / dealing with plus/minus signs [modified] herpo15-Oct-12 9:05 herpo 15-Oct-12 9:05
 Thanks for this tool! We had just one problem with plus/minus signs. Especially with the power operator. Attached you can find some code, which sets brackets to get the right result: Attention: -10^2 = -100; (-10)^2 = 100 E.g. ------ - 1*-sin(Var1)^-2^sin(10) => 1*(0-1)*sin(Var1)^((0-1)*-2^sin(10)) Hopefully bug free - i couldn't find anymore with my test unit Greets Herbert ```public string HandleSigns(string equation) { if (equation.Trim().StartsWith("-") || equation.Trim().StartsWith("+")) equation = "0" + equation; equation = Regex.Replace(equation, @"\s+", string.Empty);   //Vorzeichen vor Klammer: "-(" und Funktionen: "-sin( string pattern = @"[+\-*/^]" + @"(" + @"([\-+])" + //Vozeichen @"([a-zA-Z0-9]*)" + //Funktionsname @"(" + @"(?'Open'\()" + //öffnende Klammer @"[^\(\)]*" + //Inhalt @")+" + @"(" + @"(?'Close-Open'\))" + //schließende Klammer @")+" + @")" + @"([\^]*)"; // Optional folgende Potenz Match m = null; while ((m = Regex.Match(equation, pattern)) != null && m.Success) { if (!m.Success) break;   string newequat = equation; if (m.Groups[0].ToString().StartsWith("^")) { // - - ------ ------ -- // 1*(0-1)*(10)^-(1+2)^-SIN(PI*Var4/180)*10 => 1*(0-1)*(10)^((0-1)*(1+2)^((0-1)*SIN(PI*Var4/180)))*10 this.SetBracketsAtPow(ref newequat, m.Groups[1].Index); } else// if (false && m.Groups[6].ToString() == "^") { // ----- // 1*-sin(Var1)^-2 => 1*(0-1)*sin(Var1)^2 newequat = equation.Substring(0, m.Groups[1].Index) + "(0" + m.Groups[1].ToString()[0] + "1)*" + m.Groups[1].ToString().Substring(1) + equation.Substring(m.Groups[1].Index + m.Groups[1].Length); } if (newequat.Equals(equation)) break; equation = newequat; }   // Variablen pattern = @"[+\-*/^]" + @"(" + @"([\-+])" + //Vozeichen @"([a-zA-Z0-9]*)" + //Variablenname oder Zahlen @")" + @"([\^]*)"; // Optional folgende Potenz while ((m = Regex.Match(equation, pattern)) != null && m.Success) { if (!m.Success) break; string newequat = equation; if (m.Groups[0].ToString().StartsWith("^")) { // ------ - // 1*-sin(Var1)^-2^sin(10) => 1*(0-1)*sin(Var1)^((0-1)*-2^sin(10)) this.SetBracketsAtPow(ref newequat, m.Groups[1].Index); } else { // - ----- // 1*-10^-2 => 1*(0-1)*10^((0-1)*2) newequat = equation.Substring(0, m.Groups[1].Index) + "(0" + m.Groups[1].ToString()[0] + "1)*" + m.Groups[1].ToString().Substring(1) + equation.Substring(m.Groups[1].Index + m.Groups[1].Length); } if (newequat.Equals(equation)) break; equation = newequat; }   equation = equation.Replace("**", "^"); equation = equation.Replace("(+", "(0+"); equation = equation.Replace("(-", "(0-"); equation = equation.Replace(",-", ",0-"); equation = equation.Replace(",+", ",0+"); return equation; }   private bool SetBracketsAtPow(ref string expression, int startIndex) { if (string.IsNullOrEmpty(expression) || startIndex >= expression.Length || startIndex <= 0 || expression[startIndex - 1] != '^') return false;   string preExp = expression.Remove(startIndex); string exp = expression.Substring(startIndex); if (exp.StartsWith("+")) { // Wenn positives Vorzeichen => nur Vorzeichen entfernen expression = expression.Substring(1); return true; } else if (exp.StartsWith("-")) { // Wenn negatives => Klammern setzen string negativMultiplier = "((0-1)*"; string operators = "/*-+"; // Klammer auf setzen und vorzeichen umwandeln // -(1+2)^sin(10) => ((0-1)*(1+2)^sin(10) exp = negativMultiplier + exp.Substring(1); int idx = negativMultiplier.Count(); int klammernZaehler = exp[idx] == '(' ? 1 : 0; idx++; while (idx < exp.Length && (klammernZaehler > 0 || (!operators.Contains(exp[idx]) || exp[idx - 1] == '^'))) { if (exp[idx] == '(') klammernZaehler++; else if (exp[idx] == ')') klammernZaehler--; idx++; } if (idx >= exp.Length) expression = preExp + exp + ")"; else expression = preExp + exp.Insert(idx - 1, ")");   return true; } else return true; }```modified 19-Oct-12 3:51am.
 Re: Vorzeichenbehandlung / dealing with plus/minus signs icemanind17-Oct-12 9:02 icemanind 17-Oct-12 9:02
 Problem with double slawoonet10-Oct-12 0:07 slawoonet 10-Oct-12 0:07
 Re: Problem with double icemanind10-Oct-12 12:14 icemanind 10-Oct-12 12:14
 An excellent library Graham Wilson5-Sep-12 15:40 Graham Wilson 5-Sep-12 15:40
 Re: An excellent library icemanind26-Sep-12 6:52 icemanind 26-Sep-12 6:52
 My vote of 5 Hurty10-Jul-12 0:31 Hurty 10-Jul-12 0:31
 New version ? Andres Raieste9-Jul-12 2:55 Andres Raieste 9-Jul-12 2:55
 Re: New version ? icemanind26-Sep-12 6:54 icemanind 26-Sep-12 6:54
 Re: New version ? Andres Raieste26-Sep-12 22:00 Andres Raieste 26-Sep-12 22:00
 RegisterCustomFunction in c++/cli? [modified] Member 868581414-Mar-12 8:59 Member 8685814 14-Mar-12 8:59
 Re: RegisterCustomFunction in c++/cli? Member 868581414-Mar-12 15:51 Member 8685814 14-Mar-12 15:51
 Re: RegisterCustomFunction in c++/cli? Member 868581415-Mar-12 9:18 Member 8685814 15-Mar-12 9:18
 Re: RegisterCustomFunction in c++/cli? icemanind19-Mar-12 15:07 icemanind 19-Mar-12 15:07
 Could you add exponential function? Member 86841125-Mar-12 3:27 Member 8684112 5-Mar-12 3:27
 Re: Could you add exponential function? icemanind9-Mar-12 12:23 icemanind 9-Mar-12 12:23
 Suggestion to use compiled regex (30% performance gain) [modified] Andres Raieste16-Dec-11 4:23 Andres Raieste 16-Dec-11 4:23
 My vote of 5 Wes Grant16-Nov-11 5:34 Wes Grant 16-Nov-11 5:34
 Globalization issues (ie doesn't work with "3,14" instead of "3.14" number style) [modified] Andres Raieste15-Nov-11 11:34 Andres Raieste 15-Nov-11 11:34
 Re: Globalization issues (ie doesn't work with "3,14" instead of "3.14" number style) icemanind15-Nov-11 19:58 icemanind 15-Nov-11 19:58
 Some optimizations suggestions... [modified] pavfrang13-Nov-11 2:09 pavfrang 13-Nov-11 2:09
 My vote of 5 Mihai MOGA12-Nov-11 22:03 Mihai MOGA 12-Nov-11 22:03
 Using variables derek999910-Nov-11 0:16 derek9999 10-Nov-11 0:16
 Re: Using variables icemanind10-Nov-11 18:21 icemanind 10-Nov-11 18:21
 Re: Using variables derek999910-Nov-11 23:38 derek9999 10-Nov-11 23:38
 Re: Using variables icemanind13-Nov-11 4:24 icemanind 13-Nov-11 4:24
 My vote of 5 cooniur6-Nov-11 23:15 cooniur 6-Nov-11 23:15
 My vote of 5 sebfia3-Nov-11 23:38 sebfia 3-Nov-11 23:38
 Suggestions thatraja2-Nov-11 0:05 thatraja 2-Nov-11 0:05
 Re: Suggestions icemanind2-Nov-11 12:42 icemanind 2-Nov-11 12:42
 Re: Suggestions thatraja3-Nov-11 2:13 thatraja 3-Nov-11 2:13
 My vote of 5 aeastham1-Nov-11 0:07 aeastham 1-Nov-11 0:07
 nice dan o31-Oct-11 23:22 dan o 31-Oct-11 23:22
 My vote of 5 FlorianRappl27-Oct-11 6:28 FlorianRappl 27-Oct-11 6:28
 Re: My vote of 5 icemanind30-Oct-11 13:15 icemanind 30-Oct-11 13:15
 Re: My vote of 5 Florian Rappl31-Oct-11 9:52 Florian Rappl 31-Oct-11 9:52
 Last Visit: 31-Dec-99 19:00     Last Update: 3-May-15 10:26 Refresh 12 Next »

General    News    Suggestion    Question    Bug    Answer    Joke    Rant    Admin

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150428.2 | Last Updated 30 Oct 2011
Article Copyright 2011 by icemanind
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid