📄 fparser.txt
字号:
of fparser.cc to make the library smaller (Optimize() can still be called,
but it will not do anything).
(If you are interested in seeing how this method optimizes the opcode,
you can call the PrintByteCode() method before and after the call to
Optimize() to see the difference.)
---------------------------------------------------------------------------
bool AddConstant(const std::string& name, double value);
---------------------------------------------------------------------------
This method can be used to add constants to the parser. Syntactically
constants are identical to variables (ie. they follow the same naming
rules and they can be used in the function string in the same way as
variables), but internally constants are directly replaced with their
value at parse time.
Constants used by a function must be added before calling Parse()
for that function. Constants are preserved between Parse() calls in
the current FunctionParser instance, so they don't need to be added
but once. (If you use the same constant in several instances of
FunctionParser, you will need to add it to all the instances separately.)
Constants can be added at any time and the value of old constants can
be changed, but new additions and changes will only have effect the next
time Parse() is called. (That is, changing the value of a constant
after calling Parse() and before calling Eval() will have no effect.)
The return value will be false if the 'name' of the constant was
illegal, else true. If the name was illegal, the method does nothing.
Example: parser.AddConstant("pi", 3.14159265);
Now for example parser.Parse("x*pi", "x"); will be identical to the
call parser.Parse("x*3.14159265", "x");
---------------------------------------------------------------------------
bool AddFunction(const std::string& name,
double (*functionPtr)(const double*),
unsigned paramsAmount);
---------------------------------------------------------------------------
This method can be used to add new functions to the parser. For example,
if you would like to add a function "sqr(A)" which squares the value
of A, you can do it with this method (so that you don't need to touch
the source code of the parser).
The method takes three parameters:
- The name of the function. The name follows the same naming conventions
as variable names.
- A C++ function, which will be called when evaluating the function
string (if the user-given function is called there). The C++ function
must have the form:
double functionName(const double* params);
- The number of parameters the function takes. NOTE: Currently this
value must be at least 1; the parser does not support functions which
take no parameters (this problem may be fixed in the future).
The return value will be false if the given name was invalid (either it
did not follow the variable naming conventions, or the name was already
reserved), else true. If the return value is false, nothing is added.
Example:
Suppose we have a C++ function like this:
double Square(const double* p)
{
return p[0]*p[0];
}
Now we can add this function to the parser like this:
parser.AddFunction("sqr", Square, 1);
parser.Parse("2*sqr(x)", "x");
IMPORTANT NOTE: If you use the Optimize() method, it will assume that
the user-given function has no side-effects, that is, it always
returns the same value for the same parameters. The optimizer will
optimize the function call away in some cases, making this assumption.
---------------------------------------------------------------------------
bool AddFunction(const std::string& name, FunctionParser&);
---------------------------------------------------------------------------
This method is almost identical to the previous AddFunction(), but
instead of taking a C++ function, it takes another FunctionParser
instance.
There are some important restrictions on making a FunctionParser instance
call another:
- The FunctionParser instance given as parameter must be initialized
with a Parse() call before giving it as parameter. That is, if you
want to use the parser A in the parser B, you must call A.Parse()
before you can call B.AddFunction("name", A).
- The amount of parameters in the FunctionParser instance given as
parameter must not change after it has been given to the AddFunction()
of another instance. Changing the number of parameters will result in
malfunction.
- AddFunction() will fail (ie. return false) if a recursive loop is
formed. The method specifically checks that no such loop is built.
- As with the other AddFunction(), the number of parameters taken by
the user-defined function must be at least 1 (this may be fixed in
the future).
Example:
FunctionParser f1, f2;
f1.Parse("x*x", "x");
f2.AddFunction("sqr", f1);
---------------------------------------------------------------------------
Example program:
#include "fparser.hh"
#include <iostream>
int main()
{
FunctionParser fp;
int ret = fp.Parse("x+y-1", "x,y");
if(ret >= 0)
{
std::cerr << "At col " << ret << ": " << fp.ErrorMsg() << std::endl;
return 1;
}
double vals[] = { 4, 8 };
std::cout << fp.Eval(vals) << std::endl;
}
=============================================================================
- The function string
=============================================================================
The function string understood by the class is very similar to the C-syntax.
Arithmetic float expressions can be created from float literals, variables
or functions using the following operators in this order of precedence:
() expressions in parentheses first
-A unary minus
A^B exponentiation (A raised to the power B)
A*B A/B A%B multiplication, division and modulo
A+B A-B addition and subtraction
A=B A<B A>B comparison between A and B (result is either 0 or 1)
A&B result is 1 if int(A) and int(B) differ from 0, else 0.
A|B result is 1 if int(A) or int(B) differ from 0, else 0.
Since the unary minus has higher precedence than any other operator, for
example the following expression is valid: x*-y
Note that the '=' comparison can be inaccurate due to floating point
precision problems (eg. "sqrt(100)=10" probably returns 0, not 1).
The class supports these functions:
abs(A) : Absolute value of A. If A is negative, returns -A otherwise
returns A.
acos(A) : Arc-cosine of A. Returns the angle, measured in radians,
whose cosine is A.
acosh(A) : Same as acos() but for hyperbolic cosine.
asin(A) : Arc-sine of A. Returns the angle, measured in radians, whose
sine is A.
asinh(A) : Same as asin() but for hyperbolic sine.
atan(A) : Arc-tangent of (A). Returns the angle, measured in radians,
whose tangent is (A).
atan2(A,B): Arc-tangent of A/B. The two main differences to atan() is
that it will return the right angle depending on the signs of
A and B (atan() can only return values betwen -pi/2 and pi/2),
and that the return value of pi/2 and -pi/2 are possible.
atanh(A) : Same as atan() but for hyperbolic tangent.
ceil(A) : Ceiling of A. Returns the smallest integer greater than A.
Rounds up to the next higher integer.
cos(A) : Cosine of A. Returns the cosine of the angle A, where A is
measured in radians.
cosh(A) : Same as cos() but for hyperbolic cosine.
cot(A) : Cotangent of A (equivalent to 1/tan(A)).
csc(A) : Cosecant of A (equivalent to 1/sin(A)).
eval(...) : This a recursive call to the function to be evaluated. The
number of parameters must be the same as the number of parameters
taken by the function. Usually called inside if() to avoid
infinite recursion.
exp(A) : Exponential of A. Returns the value of e raised to the power
A where e is the base of the natural logarithm, i.e. the
non-repeating value approximately equal to 2.71828182846.
floor(A) : Floor of A. Returns the largest integer less than A. Rounds
down to the next lower integer.
if(A,B,C) : If int(A) differs from 0, the return value of this function is B,
else C. Only the parameter which needs to be evaluated is
evaluated, the other parameter is skipped; this makes it safe to
use eval() in them.
int(A) : Rounds A to the closest integer. 0.5 is rounded to 1.
log(A) : Natural (base e) logarithm of A.
log10(A) : Base 10 logarithm of A.
max(A,B) : If A>B, the result is A, else B.
min(A,B) : If A<B, the result is A, else B.
sec(A) : Secant of A (equivalent to 1/cos(A)).
sin(A) : Sine of A. Returns the sine of the angle A, where A is
measured in radians.
sinh(A) : Same as sin() but for hyperbolic sine.
sqrt(A) : Square root of A. Returns the value whose square is A.
tan(A) : Tangent of A. Returns the tangent of the angle A, where A
is measured in radians.
tanh(A) : Same as tan() but for hyperbolic tangent.
Examples of function string understood by the class:
"1+2"
"x-1"
"-sin(sqrt(x^2+y^2))"
"sqrt(XCoord*XCoord + YCoord*YCoord)"
An example of a recursive function is the factorial function:
"if(n>1, n*eval(n-1), 1)"
Note that a recursive call has some overhead, which makes it a bit slower
than any other operation. It may be a good idea to avoid recursive functions
in very time-critical applications. Recursion also takes some memory, so
extremely deep recursions should be avoided (eg. millions of nested recursive
calls).
Also note that the if() function is the only place where making a recursive
call is safe. In any other place it will cause an infinite recursion (which
will make the program eventually run out of memory). If this is something
which should be avoided, it may be a good idea to disable the eval()
function completely.
The eval() function can be disabled with the DISABLE_EVAL precompiler
constant (see the beginning of fparser.cc).
=============================================================================
- Contacting the author
=============================================================================
Any comments, bug reports, etc. should be sent to warp@iki.fi
=============================================================================
- The algorithm used in the library
=============================================================================
The whole idea behind the algorithm is to convert the regular infix
format (the regular syntax for mathematical operations in most languages,
like C and the input of the library) to postfix format. The postfix format
is also called stack arithmetic since an expression in postfix format
can be evaluated using a stack and operating with the top of the stack.
For example:
infix postfix
2+3 2 3 +
1+2+3 1 2 + 3 +
5*2+8/2 5 2 * 8 2 / +
(5+9)*3 5 9 + 3 *
The postfix notation should be read in this way:
Let's take for example the expression: 5 2 * 8 2 / +
- Put 5 on the stack
- Put 2 on the stack
- Multiply the two values on the top of the stack and put the result on
the stack (removing the two old values)
- Put 8 on the stack
- Put 2 on the stack
- Divide the two values on the top of the stack
- Add the two values on the top of the stack (which are in this case
the result of 5*2 and 8/2, that is, 10 and 4).
At the end there's only one value in the stack, and that value is the
result of the expression.
Why stack arithmetic?
The last example above can give you a hint.
In infix format operators have precedence and we have to use parentheses to
group operations with lower precedence to be calculated before operations
with higher precedence.
This causes a problem when evaluating an infix expression, specially
when converting it to byte code. For example in this kind of expression:
(x+1)/(y+2)
we have to calculate first the two additions before we can calculate the
division. We have to also keep counting parentheses, since there can be
a countless amount of nested parentheses. This usually means that you
have to do some type of recursion.
The most simple and efficient way of calculating this is to convert it
to postfix notation.
The postfix notation has the advantage that you can make all operations
in a straightforward way. You just evaluate the expression from left to
right, applying each operation directly and that's it. There are no
parentheses to worry about. You don't need recursion anywhere.
You have to keep a stack, of course, but that's extremely easily done.
Also you just operate with the top of the stack, which makes it very easy.
You never have to go deeper than 2 items in the stack.
And even better: Evaluating an expression in postfix format is never
slower than in infix format. All the contrary, in many cases it's a lot
faster (eg. because all parentheses are optimized away).
The above example could be expressed in postfix format:
x 1 + y 2 + /
The good thing about the postfix notation is also the fact that it can
be extremely easily expressed in bytecode form.
You only need a byte value for each operation, for each variable and
to push a constant to the stack.
Then you can interpret this bytecode straightforwardly. You just interpret
it byte by byte, from the beginning to the end. You never have to go back,
make loops or anything.
This is what makes byte-coded stack arithmetic so fast.
=============================================================================
Usage license:
=============================================================================
Copyright
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -