C# library for parsing and evaluation of mathematical expressions with one or more variables. Written to support .NET 4.5 (and higher), .NET 5.0 and .NET 6.0.
Project was initially created as a demonstration of Interpreter design pattern but was later extended with parser, employing Shunting yard algorithm.
Basic features of the library:
- Supports basic arithmentic operations, using both ASCII characters and Unicode mathematical signs:
- addition (
+
), - subtraction (
-
, U+2212−
), - multiplication (
*
, U+00D7×
, U+22C5⋅
), - division (
/
,:
, U+00F7÷
), - exponentiation (
^
).
- addition (
- Allows extra sign (
-
, U+2212−
or+
) preceding a value, function or variable. - Takes care of operator precedence.
- Supports parentheses to override operator precedence.
- Supports standard mathematical functions with one (e.g. sin, sqrt) or two arguments (e.g. atan2, pow) that can be invoked using corresponding identifiers.
- Supports identifiers for basic mathematical constants (e, π).
- Numeric values in expression can be in floating point (e.g. 3.14159) or in scientific format (e.g. 314159e-5). Note: only decimal point is allowed as a decimal separator because comma is used as function argument separator.
- Additional custom functions and constants can be defined in the runtime.
- Parser reports error for invalid expression, with exact position where error occured.
Below are some introductory examples of library usage.
How to evaluate expression
x + 3
for a value of x=2:
- Create
Parser
object. - Invoke
Parse
method and pass the string with mathematical expression. On success,Parse
method returns finalIExpression
object evaluated as a composition of expressions from operations parsed. - Invoke
Evaluate
method of the object returned byParse
method in step 2. Value of the variable must be passed as argument to the method and method returns the value of mathematical expression for the value of variable x provided.
using JSribar.MathematicalExpressionEvaluator;
...
var parser = new Parser();
var mathExpression = "x + 3";
var expression = parser.Parse(mathExpression);
var x = 2;
var result = expression.Evaluate(x);
Console.WriteLine($"Value of {mathExpression} for x={x} is {result}");
Note 1: Spaces around operators and operands are optional. Mathematical expression in the example above could be also written as:
//...
var mathExpression = " x+3 ";
Note 2: Expression object returned by Parse
method can be reused to evaluate expression for different values of the variable. For example, to evaluate the expression for a range of x:
var parser = new Parser();
var mathExpression = "x + 3";
var expression = parser.Parse(mathExpression);
Console.WriteLine($"Values of {mathExpression}{Environment.NewLine}x\tvalue");
for (int x = 0; x <= 10; ++x)
{
Console.WriteLine($"{x}\t{expression.Evaluate(x)}");
}
Evaluate expression
12 − 8 × 2x ÷ 4
for a value of x=2 (result should be 4):
using JSribar.MathematicalExpressionEvaluator;
// ...
var parser = new Parser();
var mathExpression = "12 - 8 * 2 ^ x / 4";
var expression = parser.Parse(mathExpression);
var x = 2;
var result = expression.Evaluate(x);
Console.WriteLine($"Value of {mathExpression} for x={x} is {result}");
Evaluate expression
12 − (8 × 2)(x ÷ 4)
for x=2 (result should be 8):
using JSribar.MathematicalExpressionEvaluator;
// ...
var parser = new Parser();
var mathExpression = "12 - (8 * 2) ^ (x / 4)";
var expression = parser.Parse(mathExpression);
var x = 2;
var result = expression.Evaluate(x);
Console.WriteLine($"Value of {mathExpression} for x={x} is {result}");
Evaluate expression
x + tan(π/x)
for x=4 (result should be 5):
// ...
var mathExpression = "x + tan(PI / x)";
var result = mathExpression.Evaluate(4);
// ...
Note 1: Left parenthesis must follow function name immediately, otherwise ParserException
is thrown.
Note 2: Mathematical constant π can be written also as a Greek character π in the expression string, i.e. "x + tan(π / x)"
.
Evaluate expression
−x + −tan(π/x)
for x=4 (result should be −5):
// ...
var mathExpression = "-x +-tan(PI / x)";
var result = mathExpression.Evaluate(4);
// ...
Note: Preceding sign must be followed immediately by constant, variable or function name (no whitespaces are allowed), otherwise ParserException
is thrown.
Custom function can be added in runtime by AddFunction
or AddFunction2
methods, for functions with single or two arguments, respectively. Custom constants can be added by AddConstant
method.
// User defined function:
static double Hypotenuse(double a, double b)
{
return Math.Sqrt(a * a + b * b);
}
// ...
var parser = new Parser();
// Add user defined mathematical function 'Hypotenuse':
parser.AddFunction2("hypotenuse", Hypotenuse);
// Add mathematical constant 'two' with value of 2:
parser.AddConstant("two", 2);
var expression = parser.Parse("hypotenuse(x, 2 * two)");
var result = expression.Evaluate(3);
// ...
Note 1: Functions and constants must be added before invoking Parse
method with expression that uses them in order to parse the expression correctly.
Note 2: If name of function or constant is already used for existing function or constant, parser throws IdentifierException
.
Parser assumes that variable is named x by default. If you need to use different identifier for a variable, simply provide the identifier to the Parser
constructor:
// Use 'time' instead of default 'x' identifier for variable:
var parser = new Parser("time");
var expression = parser.Parse("sin(time / (2 * PI))");
// ...
Note: If name of identifier is already used for existing function or constant, parser throws IdentifierException
.
To evaluate expression with multiple variables, variable identifiers must be passed to the Parser
as a collection of strings. Actual values of variables for a given context must be passed to the Evaluate
method as a collection of string
- double
tuples, with variable identifiers and corresponding values.
// Use 'x' and 'y' identifiers:
var parser = new Parser("x", "y");
// Expression with 2 variables:
var expression = parser.Parse("sin(x + y)");
// Provide values: x=2, y=3:
var result = expression.Evaluate(("x", 2), ("y", 3));
// ...
Following functions are built-in and directly available:
Identifier | Function invoked | Remark |
---|---|---|
abs |
Math.Abs |
|
acos |
Math.Acos |
|
acosh |
Math.Acosh |
.NET 5.0 or higher |
asin |
Math.Asin |
|
asinh |
Math.Asinh |
.NET 5.0 or higher |
atan |
Math.Atan |
|
atan2 |
Math.Atan2 |
two arguments |
atanh |
Math.Atanh |
.NET 5.0 or higher |
cbrt |
Math.Cbrt |
.NET 5.0 or higher |
cos |
Math.Cos |
|
cosh |
Math.Cosh |
|
exp |
Math.Exp |
|
ln |
Math.Log |
|
log , log10 |
Math.Log10 |
|
log2 |
Math.Log2 |
.NET 5.0 or higher |
pow |
Math.Atan2 |
two arguments |
sin |
Math.Sin |
|
sinh |
Math.Sinh |
|
sqrt |
Math.Sqrt |
|
tan , tg |
Math.Tan |
|
tanh |
Math.Tanh |
Following mathematical constants are built-in and directly available:
Identifier | Field invoked |
---|---|
E |
Math.E |
PI , π |
Math.PI |
Errors are reported through ParserException
and IdentifierException
.
This exception will be thrown by Parse
method when error is encountered in expression string being parsed. ParserException
contains two properties/fields:
Message
- string with error message,Position
- integer with the position in input expression string where error occured.
This exception will be thrown:
- When custom identifier passed to the
Parser
has invalid format (it must not be empty, must start with letter followed by a sequence of letters and digits only). - When user defined function or constant identifier passed to
AddConstant
,AddFunction
orAddFunction2
method has invalid format or is already used for some function or constant. - When not all identifier values are supplied to the
Evaluate
method.
Exception contains two properties/fields:
Message
- string with error message,Identifier
- string with identifier that caused the error.
To add a new function, open Parser.Functions.cs
file:
- Append new
Operator
enumeration value for the function. - Append entry to
functionTokenMap
dictionary with new function identifer as a key and enumeration value from the step 1 as a value. - Append entry to
functionMap
dictionary with enumeration value from step 1 as a key and the function as a value. Note: if function accepts two arguments, it should be appended tofunctionMap2
dictionary.
To add a new constant, open Parser.Constants.cs
file and append new entry to mathematicalConstantsMap
with new constant identifier as dictionary key and constant value as entry value.