Willkommen auf BeltOfOrion.de
 Impressum |  Datenschutzerklärung |  CSS |  XHTML
muParserX - math expression parser

Introduction


When it comes to evaluating mathematical expressions at runtime you need a math parser. Today there are a variety of solutions available for both commercial and noncommercial use. For instance MTParser, fparser or muParser to name a few. All of them are pretty fast but they do have strict limitations regarding their datatypes and none of them has native support for either strings or arrays. Since these features are probably the most often requested to be added to muParser I decided to extend its engine in order to support strings and arrays. muParserX is the result of this redesign and to my knowledge it's currently the only open source C++ math parsing library having these features. (I don't count Octave since thats a CAS system not a math parser). The following table gives an overview over the fundamental differences between the parsers of the muParser family:

Parser Data types Precision User defined operators User defined functions Locale
support
Licence Performance
(Expr. per second)*
complex scalar  string  vector Binary Postfix Infix Strings as
parameters
Arbitrary number
of parameters
muParser partially(1) ok partially(2) fail double ok ok ok ok ok ok MIT ~ 10.000.000
muParserSSE fail ok fail fail float ok ok ok fail max. 5 ok MIT ~ 20.000.000 -
100.000.000
muParserX ok ok ok ok double ok ok ok ok ok fail LGPL V3 ~ 410.000
Table 1: Feature comparison with other derivatives of muParser. (* Average performance calculated using this set of expressions; (1) muParser comes with an implementation for complex numbers but this is rather limited and more of a hack; (2) muParser can define strings but only as constants.)

So much for the good news, the bad news is that: there ain't no such thing as a free lunch. The obvious consequence of this redesign is a loss in performance. You can't expect mathematical expressions to be evaluated with the same speed when they are evaluated using complex numbers. You can't expect callbacks to be equally performant when they have to do runtime type checking on their arguments. So in order not to let the new features compromise muParsers performance i created an experimental version named muParserX and this article will show you how to use it.

Features


Predefined Constants

By default the parser supports the following mathematical constants:

Binary operators:

Postfix operators:

Infix operators:

Predefined Functions

Sample expressions

The next table shows samples of expressions that can be evaluated using muParserX:

Expression Result Explanation
"hello"=="world" false Comparing strings
"hello "//"world" "hello world" String concatanation operator
sin(a+8i) ... Support for a variety of predefined functions
working with complex numbers.
va[3]+vb[5] ... Support for array variables
va[3]=9 ... Assignment operator in combination with
indexed access to a vector
toupper("hello"//"world") "HELLOWORLD" Transforming a concatenated
string to uppercase.
#010010 18 Interpreting binary values
0x1eff 7935 Interpreting hex values
#10>0x1eff false Comparing binary and hex values
1+2-3*4/5^6 2.99923 The standard operators
(((-9))-e/(((((((pi-(((-7)+(-3)/4/e))))/(((-5))-2)
-((pi+(-0))*(sqrt((e+e))*(-8))*(((-pi)+(-pi)-(-9)*
(6*5))/(-e)-e))/2)/((((sqrt(2/(-e)+6)-(4-2))+((5/(
-2))/(1*(-pi)+3))/8)*pi*((pi/((-2)/(-6)*1*(-1))*(-
6)+(-e)))))/((e+(-2)+(-e)*((((-3)*9+(-e)))+(-9))))
)))-((((e-7+(((5/pi-(3/1+pi)))))/e)/(-5))/(sqrt(((
(((1+(-7))))+((((-e)*(-e)))-8))*(-5)/((-e)))*(-6)-
((((((-2)-(-9)-(-e)-1)/3))))/(sqrt((8+(e-((-6))+(9
*(-9))))*(((3+2-8))*(7+6+(-5))+((0/(-e)*(-pi))+7))
)+(((((-e)/e/e)+((-6)*5)*e+(3+(-5)/pi))))+pi))/
sqrt((((9))+((((pi))-8+2))+pi))/e*4)*((-5)/(((-pi)
)*(sqrt(e)))))-(((((((-e)*(e)-pi))/4+(pi)*(-9)))))
))+(-pi)
-12.23016549 No limit on equation complexity

Using the library


Adding the library to your projects

At the moment I do not provide static or dynamic libraries for muParserX. In order to use the library you should add all files located in the "muparserx/parser" subdiretory into your own project. The library currently comes with project files for VisualStudio 2008 and a Makefile for the GNU C++ compiler but It should compile on every standard compliant C++ compiler.

Parser Tokens

Before starting i should give you an overview over the classes related to muParserX. Parsing works by splitting the mathematical expression into so called tokens. A token can be either a value, a variable, a function call, an operator or a special character such as brackets or commas. The tokens will be stored internally for processing during the evaluation process. When using the parser you will deal with tokens directly only when setting variables, constants functions or operators.

parser token overview

The parser defines an abstract base class named mup::IToken from which all of its tokens are derived. There are three major kinds of token: Value tokens, Callback tokens and generic tokens. Values represent either constants or variables, callbacks represent functions and operators, generic tokens are used for brackets, commas and special characters.
It's important to know that muParserX handles its tokens via reference counted smartpointers using the mup::TokenPtr<...> template class. There is no need to release their pointers explicitely with the delete operator. The following typedefs represent smartpointer names:

namespace mup
{
  // Type of a managed pointer storing parser tokens via their base type.
  typedef TokenPtr<IToken>   ptr_tok_type;

  // Type of a managed pointer storing value tokens via their base type. 
  typedef TokenPtr<IValue>   ptr_val_type;

  // Type of a managed pointer storing binary operator tokens. 
  typedef TokenPtr<IOprtBin> ptr_binop_type;
}

Parser initialization

In order to use muParserX you have to include the file mpParser.h into your projects. The parser resides in the namespace mup.
#include "mpParser.h"

using namespace mup;

The parser functionality is organized in so called packages. A package consits of a set of mathematical functions, operators, value recognition callbacks and constants. Creating a parser instance is simple, the constructor takes an optional variable made up of several flags for activating certain parser packages. For convenience the parser already defines the enumeration values pckALL_COMPLEX and pckALL_NON_COMPLEX which can be used to configure the parser for either complex valued calculation or noncomplex calculations. If this parameter is omitted the parser will be run in its default mode which means it is using complex numbers and all available operators and functions will be installed (pckALL_COMPLEX).

ParserX  p(pckALL_NON_COMPLEX);

The following bits and bit combinations can be used for activating or deactivating certain parser packages at construction time:

Representing values

muParserX defines a common interface for classes representing Values. This interface is implemented by the classes mup::Value and mup::Variable.

parser value overview

mup::Value is a variant type class able to store different data types in a single object. If you want to set up a value for use with muParserX you first have to wrap it into an object of this type. Then you need to bind the value to a mup::Variable object which essentially serves as a proxy for value objects.

Important member functions of parser value classes:

As you may have already guessed by now, the parser supports the following data types:

Type Shortcut Description Additional
qualification
Integer 'i' signed Integer values scalar
float 'f' double precision floating point value
complex 'c' Complex values with double precision for real an imaginary part.
array 'a' An array of values; May contain elements of mixed type. array
boolean 'b' Boolean values. other
string 's' Literal string values.
void 'v' For internal use only; signals a variable is not bound to a value.

Defining variables and constants

Variables can be defined either explicitely in your C++ code or implicitely at parser runtime. Implicit creation of variables is usefull when writing console applications that require dynamic creation of variables (i.e. by using the assignment operator).

Explicit declaration of variables

In order to create a value for use with muParserX you have to create a value object first. The value class provides overloaded constructors for all relevant types:

  using namespace mup;

  // ...

  // Create a complex variable
  Value cVal(cmplx_type(1, 1));
  
  // Create a string variable
  Value sVal(_T("Hello World"));

  // Creating a floating point variable
  Value fVal(1.1);

  // Creating an integer variable
  Value fVal(1);

  // Creating an boolean variable
  Value bVal(true);

  // Create a 3x3 identity matrix
  Value m1(3, 3, 0);
  m1[0][0] = 1;
  m1[1][1] = 1;
  m1[2][2] = 1;

  // Create an array
  Value arr(2, 0);      // Arguments: number of elements, default value
  arr[0] = 2.0;
  arr[1] = _T("hallo"); // note that arrays can consits of mixed type elements

Once you have a parser value you can bind it to a variable object. A variable serves as a proxy class for value objects. It merely holds a pointer to the original value and refers all queries to it thus allowing the parser to change it. After creating the variable object use the DefineVar function in order to add it to the parser. In a similar procedure constants can be defined by using the DefineConst member function but they can be submitted directly.

  ParserX   p;
  Value  val( 1.1);
  Variable var(&val);
  
  p.DefineVar("a", var);

  // Now lets define some constants
  p.DefineConst("b", val);
  // Alternatively you could simply use:
  p.DefineConst("b", 1.1);
warning Once a variable is defined there is no need to call DefineVar again just to change it's value! If you want to change the value change the submitted value object directly. Remember: The parser has a pointer to this value object and it gets the value directly from there! Consequently you have to make sure the value object exists throughout the lifetime of the parser instance that is using it!

Implicit declaration of variables

Implicit creation of variables refers to the creation of parser variables at parser runtime. This feature is turned off by default so in order to use it you have to activate it first by calling the EnableAutoCreateVar member function:

  ParseX p;
  p.EnableAutoCreateVar(true);

Once you have activated the automatic creation of variables the parser will add a variable every time it finds unknown input that could represent a variable name without actually having a definition for such a variable. For instance an expression like a=10 would automatically create a new variable named a provided the variable isn't defined already.

Evaluating an expression

After setting up your variables properly you can use the SetExpr member function of muParser in order to define the expression. The next thing you have to do is call Eval to evaluate the expression. The return value is again stored in an object of type mup::Value. The complete code for creating variables, setting up the expression and evaluating the result is shown here:
  // Create the parser instance
  ParserX  p;

  // Create an array of mixed type
  Value arr(3, 0);
  arr[0] = 2.0;
  arr[1] = "this is a string";

  // Create some basic values
  Value cVal(cmplx_type(1, 1));
  Value sVal("Hello World");
  Value fVal(1.1);

  // Now add the variable to muParser
  p.DefineVar("va", Variable(&arr));
  p.DefineVar("a",  Variable(&cVal));
  p.DefineVar("b",  Variable(&sVal));
  p.DefineVar("c",  Variable(&fVal));

  p.SetExpr("va[0]+a*strlen(b)-c");
  for (int i=0; i<10; ++i)
  {
    // evaluate the expression and change the value of
    // the variable c in each turn
    cVal = 1.1 * i;
    Value result = p.Eval();

    // print the result
    console() << result << "\n";
  }

Querying Variables or constants

Sometimes its necessary to get a list of all variables or constants currently defined be muParserX. You may either want to query all variables or just the variables used in an expression. The latter may be usefull when you are dealing with a large number of variables and it's not possible to define all of them before evaluating an expression. In order to get the list of all variables currently defined by an instance of muParserX simply use the ParserX::GetVar() member function. It returns a map containing the variable names as the keys and pointers to the variable tokens as the values. In order to further process the variable you should cast the token into its proper type (mup::Variable) as shown in the following sample:
  // Get a map of all variables used by muParserX
  var_maptype vmap = parser.GetVar();
  for (var_maptype::iterator item = vmap.begin(); item!=vmap.end(); ++item)
    cout << item->first << "=" << (Variable&)(*(item->second)) << "\n";

Getting the expressions used in an expression does work the same way except that you have to use the ParserX::GetExprVar() member function instead of ParserX::GeVar(). Querying the expression variables does only make sense after having set up an expression using ParserX::SetExpr(...).

  // Set the expression
  parser.SetExpr("a*sin(b)");

  // Query the expression variables 
  var_maptype vmap = parser.GetExprVar();
  for (var_maptype::iterator item = vmap.begin(); item!=vmap.end(); ++item)
    cout << "  " << item->first << " =  " << (Variable&)(*(item->second)) << "\n";
Querying all parser constants works the same way by using ParserX::GetConst() member function:
  // Get a map containing all constants
  val_maptype cmap = parser.GetConst();
  for (val_maptype::iterator item = cmap.begin(); item!=cmap.end(); ++item)
    cout << "  " << item->first << " =  " << (Value&)(*(item->second)) << "\n";

Extending the library


Like the original muParser this library is designed to allow users to extend it with custom functions or and operators. There is however one fundamental difference: muParserX is using objects as callbacks and not static functions. The following sections will show you how to extend the library with custom callbacks.

Defining custom functions

All custom functions must implement the ICallback interface. This interface provides the ICallback::Clone(), ICallback::Eval(...) and ICallback::GetDesc() member functions. An implementation of a user defined class may look like this:

  class MySine : public mup::ICallback
  {
    MySine()
      :ICallback(cmFUNC, "mysin", "f:f")
    {}

    virtual void Eval(ptr_val_type &ret, const ptr_val_type *a_pArg, int a_iArgc)
    {
      // Get the argument from the argument input vector
      float_type a = argv[0]->GetFloat();

      // The return value is passed by writing it to the reference ret
      *ret = sin(a);
    }

    const char_type* GetDesc() const
    {
      return "mysin(x) - A custom sine function";
    }

    IToken* Clone() const
    {
      return new MySine(*this);
    }
  };

The mup::ICallback constructor takes three argument. The first determines the type of the callback. If you want to add a parser function use cmFUNC. The second is the function name and the last parameter is the function prototype definition. When entering expressions the parser does the type checking provided the functions and operators do specify a prototype. If the functions do not specify prototypes the parser leaves the type checking to the function callback implementation. The prototype is a string with the following format:

"return type : argument Types"

Example prototype definitions:
"c:icc" - A complex function taking an integer and two complex values as its parameter.
"f:ff" - A function taking two floating point arguments returning a float
"i:s" - A function taking a single string argument returning an integer value
"i:*" - A Function taking an unlimited number of arguments returning an integer
"i:v" - A Function without parameter returning an integer value

Operator prototypes are similar to function prototypes. Binary operators are treated like functions taking two arguments, unary operators like functions with a single argument.

Defining custom operators

Defining your own operators is very similar to defining new functions. You have to create a callback object and register it at the parser instance. The parser lets you add unary operators (both infix and prefix) as well as binary operators.

License


LGPL V3 For the moment I have decided to put muParserX under the LGPLv3 License. Please note that this license is a bit more restrictive than the MIT-Licence used by the original muParser. The main reason for changing the licence is that i'm simply dissappointed by the almost complete lack of donations received for muParser. I have seen download numbers of muParser reach 18000+ over the years but the total number of donations received is five. So apart from a few notable exceptions thats not exactly what I would call an overwhelming demonstration of appreciation for my work. On the other side i know how much it sucks finding a library to solve a specific problem one has to work on just to find out it can't be used because it's published under the GPL. I don't have a problem with proprietary use of my work but be fair and comply with the LGPL (publish your modifications!) so others can benefit too.

  muParserX - A C++ math parser library with array and string support
  Copyright (C) 2010 Ingo Berg

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
  as published by the Free Software Foundation, either version 3 of 
  the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU LESSER GENERAL PUBLIC LICENSE for more details.

  You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
  along with this program.  If not, see http://www.gnu.org/licenses.

Benchmarks


Lets be clear about this: Due to it's multiple data types and using complex numbers as it's base type muParserX has to make compromises with regard to absolute parsing performance. However i have been very active in order to minimize this effect and consequently the latest Version is now approximately 500 times faster than the initial release. In other words on a modern computer you should be able to compute well above 100.000 expressions per second with muParserX. This should be sufficient for most applications and its not too bad either when compared to other fast parsers. It's not enough to be one of the top performers though:


Image 1: By comparing benchmark results of different muParserX versions a significant improvement of parsing performance from one version to the next can be shown. This is due to introducing parsing from cached tokens (V1.05), parsing from the reverse polish notation of the expression (V1.07) and from introducing references for returning results from callback objects rather than using dynamically created value objects (V1.08).

The next image shows a comparison with other free math parsers. The top performer in this test are muParserSSE and MathPresso which both are based just in time compilation of the expression so it's not a particulary fair comparison but i'm gonna show it anyway...


Image 2: Due to the overhead imposed by its additional features muParserX does not reach the performance of the other math parsers.

History


V1.0 Initial Release (20090327)

V1.02

V1.03

V1.04 (20100516)

V1.05 (20100523)

V1.06 (20100711)

V1.07 (20100819)

Enhancements: Bugfixes:

V1.08 (20100907)

New Feature: Changes:
Creative Commons License
Autor: Ingo Berg; Dieser Text und die dazugehörigen Mediendateien stehen, sofern nicht anderweitig angegeben, unter der Creative Commons Namensnennung 3.0 Deutschland Lizenz.