C++ Mathematical Expression Library (ExprTk) https://www.partow.net/programming/exprtk/index.html

This commit is contained in:
Arash Partow 2017-05-05 12:16:16 +10:00
parent 9cddffaf52
commit 628477bf47
8 changed files with 1630 additions and 1028 deletions

2422
exprtk.hpp

File diff suppressed because it is too large Load Diff

View File

@ -39,14 +39,14 @@ void square_wave()
static const T pi = T(3.141592653589793238462643383279502);
T f = pi / T(10);
T t = T(0);
T a = T(10);
const T f = pi / T(10);
const T a = T(10);
T t = T(0);
symbol_table_t symbol_table;
symbol_table.add_variable("f",f);
symbol_table.add_variable("t",t);
symbol_table.add_variable("a",a);
symbol_table.add_constant("f",f);
symbol_table.add_constant("a",a);
symbol_table.add_constants();
expression_t expression;

View File

@ -30,9 +30,9 @@ void polynomial()
std::string expression_string = "25x^5 - 35x^4 - 15x^3 + 40x^2 - 15x + 1";
T r0 = T(0);
T r1 = T(1);
T x = T(0);
const T r0 = T(0);
const T r1 = T(1);
T x = T(0);
symbol_table_t symbol_table;
symbol_table.add_variable("x",x);

View File

@ -70,7 +70,7 @@ void fibonacci()
for (std::size_t i = 0; i < 40; ++i)
{
x = i;
x = static_cast<T>(i);
T result = expression.value();

View File

@ -130,7 +130,7 @@ void primes()
for (std::size_t i = 0; i < 100; ++i)
{
x = i;
x = static_cast<T>(i);
T result1 = expression1.value();
T result2 = expression2.value();

View File

@ -72,7 +72,7 @@ void newton_sqrt()
for (std::size_t i = 0; i < 100; ++i)
{
x = i;
x = static_cast<T>(i);
T result = expression.value();

View File

@ -4848,7 +4848,7 @@ inline std::size_t load_expressions(const std::string& file_name,
std::size_t line_count = 0;
while (std::getline(stream,buffer))
while (std::getline(stream,(buffer)))
{
if (buffer.empty())
continue;
@ -7685,7 +7685,7 @@ struct my_usr_ext : public exprtk::parser<T>::unknown_symbol_resolver
{
static T var_default_value = 1.0;
if (!(result = symbol_table.create_variable(unknown_symbol, var_default_value++)))
if ((result = symbol_table.create_variable(unknown_symbol, var_default_value++)) == false)
{
error_message = "Failed to create variable(" + unknown_symbol + ") in primary symbol table";
}
@ -7694,7 +7694,7 @@ struct my_usr_ext : public exprtk::parser<T>::unknown_symbol_resolver
{
static T cvar_default_value = 1.0;
if (!(result = symbol_table.add_constant(unknown_symbol, cvar_default_value++)))
if ((result = symbol_table.add_constant(unknown_symbol, cvar_default_value++)) == false)
{
error_message = "Failed to create const variable(" + unknown_symbol + ") in primary symbol table";
}

View File

@ -224,7 +224,7 @@ of C++ compilers:
+----------+---------------------------------------------------------+
| true | True state or any value other than zero (typically 1). |
+----------+---------------------------------------------------------+
| false | False state, value of zero. |
| false | False state, value of exactly zero. |
+----------+---------------------------------------------------------+
| and | Logical AND, True only if x and y are both true. |
| | (eg: x and y) |
@ -275,7 +275,7 @@ of C++ compilers:
| clamp | Clamp x in range between r0 and r1, where r0 < r1. |
| | (eg: clamp(r0,x,r1)) |
+----------+---------------------------------------------------------+
| equal | Equality test between x and y using normalized epsilon |
| equal | Equality test between x and y using normalised epsilon |
+----------+---------------------------------------------------------+
| erf | Error function of x. (eg: erf(x)) |
+----------+---------------------------------------------------------+
@ -321,7 +321,7 @@ of C++ compilers:
+----------+---------------------------------------------------------+
| ncdf | Normal cumulative distribution function. (eg: ncdf(x)) |
+----------+---------------------------------------------------------+
| nequal | Not-equal test between x and y using normalized epsilon |
| nequal | Not-equal test between x and y using normalised epsilon |
+----------+---------------------------------------------------------+
| pow | x to the power of y. (eg: pow(x,y) == x ^ y) |
+----------+---------------------------------------------------------+
@ -499,7 +499,7 @@ of C++ compilers:
| | eg: |
| | 1. if (x > y) z; else w; |
| | 2. if (x > y) z; else if (w != u) v; |
| | 3. if (x < y) {z; w + 1;} else u; |
| | 3. if (x < y) { z; w + 1; } else u; |
| | 4. if ((x != y) and (z > w)) |
| | { |
| | y := sin(x) / u; |
@ -651,7 +651,7 @@ expressions. The types are as follows:
(1) Scalar Type
The scalar type is a singular numeric value. The underlying type is
that used to specialize the ExprTk components (float, double, long
that used to specialise the ExprTk components (float, double, long
double, MPFR et al).
@ -673,7 +673,7 @@ however can not interact with scalar or vector types.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[SECTION 10 - COMPONENTS]
There are three primary components, that are specialized upon a given
There are three primary components, that are specialised upon a given
numeric type, which make up the core of ExprTk. The components are as
follows:
@ -791,8 +791,9 @@ methods:
4. bool add_vector (const std::string& name, vector_type&)
The 'vector' type must consist of a contiguous array of scalars which
can be one of the following:
Note: The 'vector' type must be comprised from a contiguous array of
scalars with a size that is larger than zero. The vector type itself
can be any one of the following:
1. std::vector<scalar_t>
2. scalar_t(&v)[N]
@ -800,6 +801,18 @@ can be one of the following:
4. exprtk::vector_view<scalar_t>
When registering a variable, vector, string or function with an
instance of a symbol_table, the call to 'add_...' may fail and return
a false result due to one or more of the following reasons:
1. Variable name contains invalid characters or is ill-formed
2. Variable name conflicts with a reserved word (eg: 'while')
3. Variable name conflicts with a previously registered variable
4. A vector of size (length) zero is being registered
5. A free function exceeding fifteen parameters is being registered
6. The symbol_table instance is in an invalid state
(2) Expression
A structure that holds an abstract syntax tree or AST for a specified
expression and is used to evaluate said expression. Evaluation of the
@ -920,8 +933,8 @@ including which control block each expression references and their
associated reference counts.
exprtk::expression e0; // constructed expression, eg: x + 1
exprtk::expression e1; // constructed expression, eg: 2z + y
exprtk::expression e0; // constructed expression, eg: x + 1
exprtk::expression e1; // constructed expression, eg: 2z + y
+-----[ e0 cntrl block]----+ +-----[ e1 cntrl block]-----+
| 1. Expression Node 'x+1' | | 1. Expression Node '2z+y' |
@ -934,7 +947,7 @@ associated reference counts.
+--------------------+ +--------------------+
e0 = e1; // e0 and e1 are now 2z+y
e0 = e1; // e0 and e1 are now 2z+y
+-----[ e1 cntrl block]-----+
| 1. Expression Node '2z+y' |
@ -1226,7 +1239,7 @@ In the following example, the return value of the expression will be
within the loop body on its last iteration:
var x := 1;
x + for (var i := i; i < 10; i += 1)
x + for (var i := x; i < 10; i += 1)
{
i / 2;
i + 1;
@ -1303,7 +1316,7 @@ lets review the following expression:
var x := 2; // Statement 1
var y := x + 2; // Statement 2
x + y // Statement 3
x + y; // Statement 3
y := x + 3y; // Statement 4
x - y; // Statement 5
@ -1332,9 +1345,9 @@ ExprTk support two forms of conditional branching or otherwise known
as if-statements. The first form, is a simple function based
conditional statement, that takes exactly three input expressions:
condition, consequent and alternative. The following is an example
expression that utilizes the function based if-statement.
expression that utilises the function based if-statement.
x := if (y < z, y + 1, 2* z)
x := if (y < z, y + 1, 2 * z)
In the example above, if the condition 'y < z' is true, then the
@ -1345,7 +1358,7 @@ essentially the simplest form of an if-then-else statement. A simple
variation of the expression where the value of the if-statement is
used within another statement is as follows:
x := 3 * if (y < z, y + 1, 2* z) / 2
x := 3 * if (y < z, y + 1, 2 * z) / 2
The second form of if-statement resembles the standard syntax found in
@ -1401,37 +1414,29 @@ The second variation of the if-statement is to allow for the use of
Else and If-Else cascading statements. Examples of such statements are
as follows:
Example 1: Example 2:
if (x < y) if (x < y)
z := x + 3; {
else y := z + x;
y := x - z; z := x + 3;
}
else
y := x - z;
Example 1: Example 2: Example 3:
if (x < y) if (x < y) if (x > y + 1)
z := x + 3; { y := abs(x - z);
else y := z + x; else
y := x - z; z := x + 3; {
} y := z + x;
else z := x + 3;
y := x - z; };
Example 3: Example 4:
if (x > y + 1) if (2 * x < max(y,3))
y := abs(x - z); {
else y := z + x;
{ z := x + 3;
y := z + x; }
z := x + 3; else if (2y - z)
}; y := x - z;
Example 5: Example 6:
if (x < y) if (x < y or (x + z) > y)
z := x + 3; {
else if (2y != z) z := x + 3;
{ y := x - z;
z := x + 3; }
y := x - z; else if (abs(2y - z) >= 3)
} y := x - z;
else else
x * x; {
z := abs(x * x);
x * y * z;
};
Example 4: Example 5: Example 6:
if (2 * x < max(y,3)) if (x < y) if (x < y or (x + z) > y)
{ z := x + 3; {
y := z + x; else if (2y != z) z := x + 3;
z := x + 3; { y := x - z;
} z := x + 3; }
else if (2y - z) y := x - z; else if (abs(2y - z) >= 3)
y := x - z; } y := x - z;
else else
x * x; {
z := abs(x * x);
x * y * z;
};
In the case where there is no final else statement and the flow
@ -1451,7 +1456,7 @@ Special functions dramatically decrease the total evaluation time of
expressions which would otherwise have been written using the common
form by reducing the total number of nodes in the evaluation tree of
an expression and by also leveraging the compiler's ability to
correctly optimize such expressions for a given architecture.
correctly optimise such expressions for a given architecture.
3-Parameter 4-Parameter
+-------------+-------------+ +--------------+------------------+
@ -1581,25 +1586,28 @@ examples of string variable definitions:
(a) Initialise to a string
var x := 'abc';
(b) Initialise to a string expression
(b) Initialise to an empty string
var x := '';
(c) Initialise to a string expression
var x := 'abc' + '123';
(c) Initialise to a string range
(d) Initialise to a string range
var x := 'abc123'[2:4];
(d) Initialise to another string variable
(e) Initialise to another string variable
var x := 'abc';
var y := x;
(e) Initialise to another string variable range
(f) Initialise to another string variable range
var x := 'abc123';
var y := x[2:4];
(f) Initialise to a string expression
(g) Initialise to a string expression
var x := 'abc';
var y := x + '123';
(g) Initialise to a string expression range
(h) Initialise to a string expression range
var x := 'abc';
var y := (x + '123')[1:3];
@ -1775,7 +1783,7 @@ needs to be 'updated' to either another vector or sub-range, the
vector_view instance can be efficiently rebased, and the expression
evaluated as normal.
exprtk::vector_view<T> view = exprtk::make_vector_view(v, v.size());
exprtk::vector_view<T> view = exprtk::make_vector_view(v,v.size());
symbol_table_t symbol_table;
symbol_table.add_vector("v",view);
@ -1794,7 +1802,7 @@ evaluated as normal.
[SECTION 15 - USER DEFINED FUNCTIONS]
ExprTk provides a means whereby custom functions can be defined and
utilized within expressions. The concept requires the user to
utilised within expressions. The concept requires the user to
provide a reference to the function coupled with an associated name
that will be invoked within expressions. Functions may take numerous
inputs but will always return a single value of the underlying numeric
@ -1840,7 +1848,7 @@ function called 'foo':
(2) ivararg_function
This interface supports a variable number of scalar arguments as input
into the function. The function operator interface uses a std::vector
specialized upon type T to facilitate parameter passing. The following
specialised upon type T to facilitate parameter passing. The following
example defines a vararg function called 'boo':
template <typename T>
@ -1863,7 +1871,7 @@ example defines a vararg function called 'boo':
(3) igeneric_function
This interface supports a variable number of arguments and types as
input into the function. The function operator interface uses a
std::vector specialized upon the type_store type to facilitate
std::vector specialised upon the type_store type to facilitate
parameter passing.
Scalar <-- function(i_0, i_1, i_2....., i_N)
@ -2335,6 +2343,42 @@ Note: For the igeneric_function type, there also needs to be a 'Z'
parameter sequence defined in order for the zero parameter trait to
properly take effect otherwise a compilation error will occur.
(9) Free Functions
The ExprTk symbol table supports the registration of free functions
and lambdas (anonymous functors) for use in expressions. The basic
requirements are similar to those found in ifunction derived user
defined functions. This includes support for free functions using
anywhere from zero up to fifteen input parameters of scalar type, with
a return type that is also scalar. Furthermore such functions will by
default be assumed to have side-effects and hence will not participate
in constant folding optimisations.
In the following example, a two input parameter free function named
'compute', and a three input parameter lambda will be registered with
the given symbol_table instance:
double compute(double v0, double v1)
{
return 2.0 * v0 + v1 / 3.0;
}
.
.
.
typedef exprtk::symbol_table<double> symbol_table_t;
symbol_table_t symbol_table;
symbol_table.add_function("compute", compute);
symbol_table.add_function("lambda",
[](double v0, double v1, double v2) -> double
{ return v0 / v1 + v2; });
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[SECTION 16 - EXPRESSION DEPENDENTS]
@ -2443,7 +2487,7 @@ associated assignments:
(5) None x + y + z
Note: In expression 4, both variables 'z' and 'w' are denoted as being
Note: In expression 4, both variables 'w' and 'z' are denoted as being
assignments even though only one of them can ever be modified at the
time of evaluation. Furthermore the determination of which of the two
variables the modification will occur upon can only be known with
@ -2489,7 +2533,7 @@ associated with a given expression instance.
However as an expression can have more than one symbol table instance
associated with itself, when building more complex systems that
utilize many expressions where each can in turn utilize one or more
utilise many expressions where each can in turn utilise one or more
variables from a large set of potential variables, functions or
constants, it becomes evident that grouping variables into layers of
symbol_tables will simplify and streamline the overall process.
@ -2559,7 +2603,7 @@ own instances of those variables. Examples of such variables could be:
(2) customer_name
The following is a diagram depicting the possible version of the
The following is a diagram depicting the possible version of the
denoted symbol table hierarchies. In the diagram there are two unique
expressions, each of which have a reference to the Global constant,
functions and variables symbol tables and an exclusive reference to a
@ -2589,8 +2633,8 @@ local symbol table.
Bringing all of the above together, in the following example the
hierarchy of symbol tables are instantiated and initialised. An
expression that makes use of various elements of each symbol table is
hierarchy of symbol tables are instantiated and initialised. An
expression that makes use of various elements of each symbol table is
then compiled and later on evaluated:
typedef exprtk::symbol_table<double> symbol_table_t;
@ -2722,7 +2766,7 @@ expressions:
In this scenario one can use the 'dependent_entity_collector'
component as described in [Section 16] to further determine which of
the registered variables were actually used in the given expression.
As an example once the set of utilized variables are known, any
As an example once the set of utilised variables are known, any
further 'attention' can be restricted to only those variables when
evaluating the expression. This can be quite useful when dealing with
expressions that can draw from a set of hundreds or even thousands of
@ -3427,8 +3471,8 @@ values.
expression.value();
printf("Result0: %15.5f\n",result0 );
printf("Result1: %s\n" ,result1.c_str());
printf("Result0: %15.5f\n", result0 );
printf("Result1: %s\n" , result1.c_str());
In the example above, the expression will compute two results. As such
@ -3436,7 +3480,7 @@ two result variables are defined to hold the values named result0 and
result1 respectively. The first is of scalar type (double), the second
is of string type. Once the expression has been evaluated, the two
variables will have been updated with the new result values, and can
then be further utilized from within the calling program.
then be further utilised from within the calling program.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -3753,12 +3797,12 @@ overloads, the definitions of which are:
(1) No variables
(2) One variable called x
(3) Two variable called x and y
(3) Three variable called x, y and z
(3) Two variables called x and y
(3) Three variables called x, y and z
An example use of each of the three overloads for the compute routine
is as follows:
Example uses of each of the three overloads for the compute routine
are as follows:
T result = T(0);
@ -3803,11 +3847,11 @@ is as follows:
(d) integrate
This free function will attempt to perform a numerical integration of
a single variable compiled expression over a defined range and given
step size. The numerical integration is based on the three point form
of the Simpson's rule. The integrate function has two overloads, where
the variable of integration can either be passed as a reference or as
a name in string form. Example usage of the function is as follows:
a single variable compiled expression over a specified range and step
size. The numerical integration is based on the three point form of
Simpson's rule. The integrate function has two overloads, where the
variable of integration can either be passed as a reference or as a
name in string form. Example usage of the function is as follows:
typedef exprtk::parser<T> parser_t;
typedef exprtk::expression<T> expression_t;
@ -3863,7 +3907,7 @@ function is as follows:
....
// Differentiate expression where value of x = 12.3 using a reference
// Differentiate expression at value of x = 12.3 using a reference
// to the x variable
x = T(12.3);
T derivative1 = exprtk::derivative(expression,x);
@ -4047,7 +4091,7 @@ expressions. As an example, lets take the following expression:
1 / sqrt(2x) * e^(3y)
Let's say we would like to determine which sub-part of the expression
Lets say we would like to determine which sub-part of the expression
takes the most time to evaluate and perhaps attempt to rework the
expression based on the results. In order to do this we will create a
text file called 'test.txt' and then proceed to make some educated
@ -4133,7 +4177,7 @@ into account when using ExprTk:
(09) The life-time of objects registered with or created from a
specific symbol-table must span at least the life-time of the
compiled expressions which utilize objects, such as variables,
compiled expressions which utilise objects, such as variables,
of that symbol-table, otherwise the result will be undefined
behavior.
@ -4289,7 +4333,15 @@ into account when using ExprTk:
performance critical code paths, and should instead occur
entirely either before or after such code paths.
(32) Before jumping in and using ExprTk, do take the time to peruse
(32) Deep copying an expression instance for the purposes of
persisting to disk or otherwise transmitting elsewhere with the
intent to 'resurrect' the expression instance later on is not
possible due to the reasons described in the final note of
Section 10. The recommendation is to instead simply persist the
string form of the expression and compile the expression at
run-time on the target.
(33) Before jumping in and using ExprTk, do take the time to peruse
the documentation and all of the examples, both in the main and
the extras distributions. Having an informed general view of
what can and can't be done, and how something should be done