Add your own alternative version
Stats
94.5K views 5.6K downloads 138 bookmarked
Posted
26 Oct 2011

Comments and Discussions



It's a big goal to change the parser for use with string type.
I.e.: var1 = "Wor" / var2 = "ld!" and with expression like "var1+var2" return "World!"
At moment is not possible because the result type allow only integer and double type.
In that case the use of custom function is useful for lookup/transcoding value from a database.
How it's possible to extending to string manipulation?
Thanks!!!





My men, you help me a lot. You safe my life, realy. I speack a little english, so:
Viejo, muchísimas gracias, tenia que hacer un programa que al ingresar cualquier ecuación obtuviera sus raíces mediante el método de bisección, bueno es algo complicado para mi. Y muchas gracias de nuevo.





Thank you very much for this wonderful library. I found it very useful in my project. The only problem I have had with it that it assumes a decimal point in the number format settings of the system for floats in formulae and will fail if your system uses a different decimal separator (I am Hungarian, so this is the case with me  we use a comma).
For this reason, I added my SlimDoubleParser to your project and replaced every double.Parse call with SlimDoubleParser.Parse . The code is:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
namespace TetheredSun
{
public static class SlimDoubleParser
{
private static CultureInfo invariantCulture;
private static CultureInfo localCulture;
private static CultureInfo currentCulture;
private static NumberFormatInfo invariantFormat;
private static NumberFormatInfo localFormat;
private static NumberFormatInfo currentFormat;
private static NumberStyles numberStyle;
private static bool isLocalFormat;
static SlimDoubleParser()
{
invariantCulture = CultureInfo.InvariantCulture;
localCulture = new CultureInfo("huHU");
invariantFormat = invariantCulture.NumberFormat;
localFormat = localCulture.NumberFormat;
currentCulture = Thread.CurrentThread.CurrentCulture;
currentFormat = currentCulture.NumberFormat;
numberStyle = NumberStyles.Float;
isLocalFormat = (currentFormat.NumberDecimalSeparator == ",");
}
public static double Parse(string s)
{
double result;
if (TryParse(s, out result)) return result;
Exception exception = new ArgumentException("Parse failed.");
exception.Data.Add("Input string", s);
throw exception;
}
public static bool TryParse(string s, out double result)
{
if (Double.TryParse(s, numberStyle, currentFormat, out result)) {
return true;
} else if (isLocalFormat) {
if (Double.TryParse(s, numberStyle, invariantFormat, out result)) {
currentCulture = invariantCulture;
currentFormat = currentCulture.NumberFormat;
isLocalFormat = false;
return true;
} else {
result = Double.NaN;
return false;
}
} else {
if (Double.TryParse(s, numberStyle, localFormat, out result)) {
currentCulture = localCulture;
currentFormat = localCulture.NumberFormat;
isLocalFormat = true;
return true;
} else {
result = Double.NaN;
return false;
}
}
}
public static bool TryParse(string s, out float result)
{
if (Single.TryParse(s, numberStyle, currentFormat, out result)) {
return true;
} else if (isLocalFormat) {
if (Single.TryParse(s, numberStyle, invariantFormat, out result)) {
currentCulture = invariantCulture;
currentFormat = currentCulture.NumberFormat;
isLocalFormat = false;
return true;
} else {
result = Single.NaN;
return false;
}
} else {
if (Single.TryParse(s, numberStyle, localFormat, out result)) {
currentCulture = localCulture;
currentFormat = localCulture.NumberFormat;
isLocalFormat = true;
return true;
} else {
result = Single.NaN;
return false;
}
}
}
}
}
modified 21Nov15 8:56am.





Hi, nice work. Would it be simple to separate the parsing of the userinput string and the evaluation step? My app needs to evaluate userinput math in a loop. I would like to undertake as much parsing as possible outside the loop and undertake the simplest possible evaluation inside it.





Very great project! Thank you a lot.





Thank you for your good job!
But I have problem in complie it:
var p = new Parser();
foreach (var cfr in _customFunctions)
p.AddCustomFunction(cfr.Key, cfr.Value);
foreach (var vr in _variables)
p.AddVariable(vr.Key, vr.Value);
foreach (var vf in _functions)
p.AddFunction(vf.Name, vf.Arguments, vf.Expression);
var ex = expressions.Select(p.Simplify).ToArray();
object funcRetval = null;
Complie error:
The type arguments for method 'System.Linq.Enumerable.Select<TSource,TResult>(System.Collections.Generic.IEnumerable<tsource>, System.Func<TSource,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
What's wrong?





Saved my project would have taken months to develop this myself, thank you.
Jeff Forrett





This is an excellent component.
A useful extension I've made for us is to allow an event to be fired when an unknown variable is found, rather than having to add all possible combinations of our variables to the list...
public delegate bool UnknownVariableEventHandler(string variableName, out double value);
...
public event UnknownVariableEventHandler OnUnknownVariable;
...
double value;
if (token.TokenName == TokenParser.Tokens.Variable)
{
if (_variables.ContainsKey(token.TokenValue))
{
var z = _variables[token.TokenValue];
if (z.NumberType == NumberClass.NumberTypes.Float)
{
token.TokenName = TokenParser.Tokens.Float;
token.TokenValue = z.FloatNumber.ToString();
}
else if (z.NumberType == NumberClass.NumberTypes.Integer)
{
token.TokenName = TokenParser.Tokens.Integer;
token.TokenValue = z.IntNumber.ToString();
}
}
else if (OnUnknownVariable != null && OnUnknownVariable(token.TokenValue, out value))
{
token.TokenName = TokenParser.Tokens.Float;
token.TokenValue = value.ToString();
}
else
{
throw new NoSuchVariableException(StringResources.Undefined_Variable + ": " + token.TokenValue);
}
Don't know if you'd like to include this in your component icemanind?





Like this post alot. I may not have read all the responses to this thread but, I find that parser.Simplify does not work. Try the following input to it: 161+116. That should give 0. But it returns 32.
Now, this is equivalent to (4^21) + (14^2) which does work and returns 0. Are we too focused on complexity maybe?
NOTE: I did retry just now (after I posted the original comment) with the same numerical expression and it worked. I used the supplied demo to test. It could be the demo project since I played with functions before going to the simplifying process. But certainly there is a situation where Simplify("161+116") will consistently give 32 via the demo.
modified 16Apr13 12:23pm.





Thank you for bring this to my attention. I will look at the demo project and see if there is a bug in it.






can cou implement a random function?





The parser supports custom functions, so you can add it yourself!





(1,1)*1 returns 1
(1.1)*1 returns 11
result is 1,1. What's the problem????





It doesn't support globalization. See a thread below for possible solution.





Andres is correct. It does not support globalization. Look for a new version to come out soon though that will support it!





((1*60)+0,1)*1
The result is 60,1
and demo returns 1
if I use (,) for decimal separators
((1*60)+0.1)*1
demo returns 61
but the correct result is 60,1, what's the problem????





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*(01)*sin(Var1)^((01)*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);
string pattern =
@"[+\*/^]" +
@"(" +
@"([\+])" +
@"([azAZ09]*)" +
@"(" +
@"(?'Open'\()" +
@"[^\(\)]*" +
@")+" +
@"(" +
@"(?'CloseOpen'\))" +
@")+" +
@")" +
@"([\^]*)";
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("^"))
{
this.SetBracketsAtPow(ref newequat, m.Groups[1].Index);
}
else
{
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;
}
pattern =
@"[+\*/^]" +
@"(" +
@"([\+])" +
@"([azAZ09]*)" +
@")" +
@"([\^]*)";
while ((m = Regex.Match(equation, pattern)) != null && m.Success)
{
if (!m.Success)
break;
string newequat = equation;
if (m.Groups[0].ToString().StartsWith("^"))
{
this.SetBracketsAtPow(ref newequat, m.Groups[1].Index);
}
else
{
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("+"))
{
expression = expression.Substring(1);
return true;
}
else if (exp.StartsWith(""))
{
string negativMultiplier = "((01)*";
string operators = "/*+";
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 19Oct12 3:51am.





Thank you for this snippet of code. I will make sure I integrate it into the next version.





Hi, I have a problem with this library.
When I write for example: 10*0,5 I have 0
1,5+5 gives 10 on demo what is wrong?





Hi. The problem is that my math library does not have international support yet. You are using a comma instead of a period to signify a double number. For now, use a period (.) instead of a comma (,) and it will work fine.





Many thanks for submitting this. It has already saved me much development time in one of my projects.
My only suggestion would be to include a complete help file.
A vote of 5 (add the help file, and I'll make that a 6
Graham





Thank you for the feedback. I will make sure the help file for version 1.2 is more complete and more comprehensive. In the meantime, if you need help with anything, feel free to ask.





Great invention :P
Excellent work.!





Hi, are there any new versions of this project on the way (are you planning to update this) ?





Yes, I got some big fixes coming on the way. I am thinking of creating a source repository for this, so others will be able to modify the source code.





Can't wait Any ETA ?





First off, this is a great library, thank you for putting it out there.
I'm using c++/cli to write a windows forms application using VS 2008. The parser is declared and used in Form1. I'm trying to add inverse trigonometric capability by registering custom functions:
MathParserNet::Parser parser;
parser.RegisterCustomDoubleFunction("atan",Math::Atan);
Here is the error I am getting:
error C3374: can't take address of 'System::Math::Atan' unless creating delegate instance
error C2664: 'void MathParserNet::Parser::RegisterCustomFunction(System::String ^,System::Func<T,TResult> ^)' : cannot convert parameter 2 from 'double (__clrcall *)(double)' to 'System::Func<T,TResult> ^'
Any suggestions for getting this to work? Thanks.
modified 14Mar12 20:10pm.





This also does not work:
parser.AddFunction("ATAN", gcnew FunctionArgumentList{"x"}, "x(x^3)/3+(x^5)/5");
Giving error:
error C2440: 'initializing' : cannot convert from 'const char [2]' to 'MathParserNet::FunctionArgumentList ^'





Well, I'll just keep replying to myself until I hear anything
I was able to get the second method to work:
FunctionArgumentList ^fal = gcnew FunctionArgumentList();
fal>Add("x");
parser.AddFunction("ATAN", fal, "x(x^3)/3+(x^5)/5");
So for the time being I am in business, but I sure would like to figure out how to register functions. Thanks.





I am sorry man, I am not a C++ guy at all, but I will look into it and see if I can find a work around for you!





Hi,
Could you add exp in the library?
Thank,
Cha Tre





It already has it! You can use the caret sign (^) to raise a number by a power of x. For example 3^5 will raise 3 by the power of 5.





Hello,
I suggest using compiled Regex for the next version. It gives a 30%40% performance gain when repeatedly parsing an expression using the same Parser instance.
private readonly OrderedDictionary<Tokens, Regex> _tokens;
static Regex _whitespace = new Regex("[ \\t]+", RegexOptions.Compiled);
static Regex _newline = new Regex("[\\r\\n]+", RegexOptions.Compiled);
static Regex _function = new Regex("func([azAZ_][azAZ09_]*)\\(((?<BR>\\()(?<BR>\\))[^()]*)+\\)", RegexOptions.Compiled);
static Regex _logN = new Regex("[Ll][Oo][Gg][Nn]\\(((?<BR>\\()(?<BR>\\))[^()]*)+\\)", RegexOptions.Compiled);
static Regex _sqrt = new Regex("[Ss][Qq][Rr][Tt]\\(((?<BR>\\()(?<BR>\\))[^()]*)+\\)", RegexOptions.Compiled);
static Regex _sin = new Regex("[Ss][Ii][Nn]\\(((?<BR>\\()(?<BR>\\))[^()]*)+\\)", RegexOptions.Compiled);
static Regex _cos = new Regex("[Cc][Oo][Ss]\\(((?<BR>\\()(?<BR>\\))[^()]*)+\\)", RegexOptions.Compiled);
static Regex _tan = new Regex("[Tt][Aa][Nn]\\(((?<BR>\\()(?<BR>\\))[^()]*)+\\)", RegexOptions.Compiled);
static Regex _abs = new Regex("[Aa][Bb][Ss]\\(((?<BR>\\()(?<BR>\\))[^()]*)+\\)", RegexOptions.Compiled);
static Regex _log = new Regex("[Ll][Oo][Gg]\\(((?<BR>\\()(?<BR>\\))[^()]*)+\\)", RegexOptions.Compiled);
static Regex _variable = new Regex("[azAZ_][azAZ09_]*", RegexOptions.Compiled);
static Regex _float = new Regex("([09]+)?\\.+[09]+", RegexOptions.Compiled);
static Regex _integer = new Regex("[09]+", RegexOptions.Compiled);
static Regex _lparen = new Regex("\\(", RegexOptions.Compiled);
static Regex _rparen = new Regex("\\)", RegexOptions.Compiled);
static Regex _exponent = new Regex("\\^", RegexOptions.Compiled);
static Regex _modulus = new Regex("\\%", RegexOptions.Compiled);
static Regex _multiply = new Regex("\\*", RegexOptions.Compiled);
static Regex _divide = new Regex("\\/", RegexOptions.Compiled);
static Regex _add = new Regex("\\+", RegexOptions.Compiled);
static Regex _subtract = new Regex("\\", RegexOptions.Compiled);
...Constructor...
_tokens = new OrderedDictionary<Tokens, Regex>();
_tokens.Add(Tokens.Whitespace, _whitespace);
_tokens.Add(Tokens.Newline, _newline);
_tokens.Add(Tokens.Function, _function);
_tokens.Add(Tokens.LogN, _logN);
_tokens.Add(Tokens.Sqrt, _sqrt);
_tokens.Add(Tokens.Sin, _sin);
_tokens.Add(Tokens.Cos, _cos);
_tokens.Add(Tokens.Tan, _tan);
_tokens.Add(Tokens.Abs, _abs);
_tokens.Add(Tokens.Log, _log);
_tokens.Add(Tokens.Variable, _variable);
_tokens.Add(Tokens.Float, _float);
_tokens.Add(Tokens.Integer, _integer);
_tokens.Add(Tokens.Lparen, _lparen);
_tokens.Add(Tokens.Rparen, _rparen);
_tokens.Add(Tokens.Exponent, _exponent);
_tokens.Add(Tokens.Modulus, _modulus);
_tokens.Add(Tokens.Multiply, _multiply);
_tokens.Add(Tokens.Divide, _divide);
_tokens.Add(Tokens.Add, _add);
_tokens.Add(Tokens.Subtract, _subtract);
...
private void PrepareRegex()
{
_regExMatchCollection.Clear();
foreach (KeyValuePair<Tokens, Regex> pair in _tokens)
{
_regExMatchCollection.Add(pair.Key, pair.Value.Matches(_inputString));
}
}
...Peek...
foreach (KeyValuePair<Tokens, Regex> pair in _tokens)
{
Match m = pair.Value.Match(_inputString, _index);
...
}
...RegisterCustomFunction...
Regex customFuncEx = new Regex(sb.ToString());
_tokens.Insert(4, (Tokens)_customFunctionIndex, customFuncEx);
...
modified 16Dec11 9:35am.





I like the extensibility with the delegate functions.





Great job (5 stars), however the tests fail and you get exceptions when the current CultureInfo is something with a different number style than in "enUS" (for example, if "3.14" is given as "3,14", which is quite common).
The most simple workaround that works is to change the current culture to "enUS" before calling SimplifyDouble and undo the change when done parsing.
A bit more efficient and universal approach, I guess, would be to:
1. Specify CultureInfo as a SimplifyDouble or constructor parameter
2. Change Float token (_tokens.Add(Tokens.Float, "([09]+)?\\.+[09]+"); ) in Lexer.cs to be dependant on NumberFormatInfo.NumberDecimalSeparator for the given culture, maybe even simply something like this:_tokens.Add(Tokens.Float, "([09]+)?\\" + _cultureInfo.NumberFormat.NumberDecimalSeparator + "+[09]+");
3. Specify number style for float and integer .Parse() methods, like this: nc.FloatNumber = double.Parse(token.TokenValue, _cultureInfo.NumberFormat);
However, I suspect there are other parts too that need to be globalized.
Best wishes!
modified 15Nov11 18:24pm.





Thanks for bring this to my attention. I haven't thought about globalization, however, as you pointed out, it is quite common. I will look into this and incorporate it into my next release.





Your project is great! Let me suggest some optimizations you could do:
At the Simplify function, instead of:
if (_customFunctions.Any(c => c.Key.Equals(fn))) found = true;
You could use:
found = _customFunctions.ContainsKey(fn);
And instead of repeating checks like :
if (funcRetval is double)
...
if (funcRetval is int)
...
if (...)
you could use "else if". If for example the return val is double , why recheck for other cases?
if (funcRetval is double)
...
else if (funcRetval is int)
...
else if (...)
Of course you could also use switch statements which makes your code a bit clearer, instead of if ... else if . Also when you define the if(token.TokenName == TokenParse.Tokens.Sqrt) etc., the cases with the most frequent use should go first. For example, checking for whitespace should be first and not after Sqrt, Sin, Log, LogN etc. which are less likely to meet!
modified 13Nov11 9:07am.





Great article. Please keep it up!





Here's a code snippet of a function plotter I'm writing:
Dim x As Integer = 0
For x = 0 To 200
parser.AddVariable("x", x)
retval = parser.SimplifyInt("100+100*sin(x/20)", MathParserNet.Parser.RoundingMethods.Round)
parser.RemoveVariable("x")
cPoints.Add(New Point(x, retval))
Next
Do I have to add and remove the "x" variable or can I just set its value somehow? The performance doesn't seem to suffer it just looks inefficient to me!
Thanks.





What you are doing will work. However, it might be more efficient to do it like this:
Dim x as Integer = 0
For x = 0 to 200
retval = parser.SimplifyInt("100+100*sin(" + x.ToString() + "/20)", MathParserNet.Parser.RoundingMethods.Round)
cPoints.Add(New Point(x, retval))
Next
Try that and see if that is more effecient.





Hi,
This is the exact same way I did it when I first tried your library. The problem with using it like this is I wanted a user to be able to enter any function and then plot it. The code would then have to search and replace the values for x. Not a huge problem admittedly but I didn't want string operations in a loop.
Either way, your library is still much faster than the one I was using (I won't name it!)
Will you be adding more trigonometry functions (arc sin, inverse sin etc.) to the library?
Cheers.





Eventually. In the meantime, do not forget that you can create your own function delegate methods and create your own arc sine, etc...





really really nice work! I'm trying it in my project. Thanks a lot!





Very nice and useful article!





No need but it would be awesome if you...., yes just some formatting things.
Use var (inline code) to methods & properties
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:
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:
Apply Bold for list of items
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 its a 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.
A Table would be awesome
Item   Meaning of Item 

()    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) 
That's all, check entire article to change things(Because I just mentioned only couple of lines).





Sorry about that. Truth be told, I am a much better writer than a formatter. I will make some revisions though.






Really well explained and simple to use in real life. Well done.





Hi,
nice article
maybe would be useful to add some logical functions
like AND, OR, NOT, XOR with variables
gtz







General News Suggestion Question Bug Answer Joke Praise Rant Admin Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

