Version 1.0.0
Date: 3/25/2014
Author: Anthony Daniels
Company: Pyramid Solutions
http://www.pyramidsolutions.com
Introduction
CalcStar
is an expandable, fast C++ function evaluator that
gives the user the same computational power as a scientific calculator. The library can be used to provide function
evaluation capability in any C++ project. The function evaluator tokenizes any input function and compiles it into
an RPN stack before evaluating it providing the user an answer. Functions are typed in a C++ syntax format
and in infix notation (i.e 2 + 2). CalcStar
supports the use of variables in the
functions. Using CalcStar
starts with
including the library header.
#include <CalcStar.h>
From there, the user declares a function evaluator, sets the
expression to be evaluated and variables, then evaluates the function giving an
answer. Once an equation has been
compiled, the user can evaluate with the existing RPN stack to avoid the extra
computational effort of compiling every execution. However, it should be noted that compiling is
very fast. Below is an example usage of
the function evaluator that can be found in the supplied TestApp
.
Std::string strFunc;
strFunc = "(2 + 2)/3 * pow(3,2)  2";
this>m_objFuncEvaluator.Set_strExpression(strFunc);
m_objFuncEvaluator.Evaluate();
dblAnswer = m_objFuncEvaluator.Get_dblCurrValue();
Setting variables is as simple as:
CSVariable objVarTemp;
objVarTemp.m_strVarName = "X1";
objVarTemp.m_dblCurrVal = varValue;
m_objFuncEvaluator.AddVariable(objVarTemp);
Updating the value of a
variable is simple as well.
m_objFuncEvaluator.UpdateVariable(strVarName,dblNewVal);
CalcStar Design
CalcStar
is a general purpose calculator. Essentially, it is on par with any infix
notation scientific calculator (think TI83). When the CalcStar
calculates the answer to an expression, it goes through
three phases: Tokenization, Compilation, and Execution. In Tokenization, the
string
mathematical expression is parsed into a collection of "tokens" or
mathematical words. The token is the
basic unit of operation in a mathematical expression. Below is the list of token types in CalcStar
:
enum CSTAR_DLLAPI CSTokenType
{
NOOP = 1,
OPENPAREN = 0, OPENBRACKET, OPENBLOCK, CLOSEPAREN, CLOSEBRACKET, CLOSEBLOCK, COMMA, NUMBER, VAR, ASSIGN, NEG, ADD, SUB, MULT, DIV, LT, GT, LTE, GTE, NEQ, EQ, AND, OR, NOT, FUNC };
Function is used for any type
of user defined function, such as pow()
for power. Functions have a "signature" followed by an
"(". It should be noted that if CalcStar
can’t find the function signature in its registry, it will consider that token
a variable. The CSToken
object has the following member variables:
variables are public
CSTokenType m_objTokenType;
std::string m_strToken;
CSAssociativity m_objAssociativity;
double m_dblToken;
Once the expression has been tokenized, that stack is
compiled into a Reverse Polarity Notation (RPN) stack to allow for computation.
It creates the RPN stack by using a modified version of the Shunting Yard
Algorithm, a well known algorithm in computer science. RPN stacks preserve order of operations and
allows the computer to simply scan through the stack once performing the
operations along the way. As operations
are performed, their inputs and the operator are replaced with a single
CSRpnUnit
that is the output number for that portion of the calculation. The process continues until a single output
number is left. This is the answer of the expression.
CalcStar Expressions
The user may write any infix notation C++ style mathematical
expression that evaluates to a single number. An example of this is: ( 2 + 3) * X – 1, where X is a variable equal to
3.1415926. The only caveat is that
negative numbers must be surrounded by parenthesis, e.g. (2). This will be fixed in a future version. The reason for it is that CalcStar
views the
negative in a negative number as an operator, not as a property of a
number. In the case of a variable, the
negative sign persists because the value of the variable is unknown at the time
of compilation. Another note is that
subtraction MUST be followed by a space, otherwise it will be viewed as a
negative sign. The built in operators and functions of CalcStar
are as follows:
Basic Functions 


Name 
Signature 
Name 
Signature 
Add 
+ 
Boolean And 
&& 
Subtract 
 
Boolean Equal 
== 
Multiply 
* 
Boolean Not 
! 
Divide 
/ 
Boolean Not Equal 
!= 
Exponential 
exp 
Boolean Or 
 
Power 
pow 
Greater Than 
>

Natural Log 
ln 
Greater Than or Equal 
>= 
Log2 
log2 
Less Than 
<

Log10 
log10 
Less Than or Equal 
<= 
Absolute Value 
abs 
Ceiling 
ceil 
Square Root 
sqrt 
Floor 
floor 

Truncate 
trunc 
Trigonometric Functions 


Name 
Signature 
Name 
Signature 
Sine 
sin 
Arc Sine 
asin 
Cosine 
cos 
Arc Cosine 
acos 
Tangent 
tan 
Arc Tangent 
atan 
Hyp Sine 
sinh 
Arc Hyp Sine 
asinh 
Hyp Cosine 
cosh 
Arc Hyp Cosine 
acosh 
Hyp Tangent 
tanh 
Arc Hyp Tangent 
atanh 
Expanding CalcStar
One of the most important features of CalcStar
is the ease
with which it can be expanded. The user
can add new functions to the system with minimal effort using the existing set
of operators and functions as an example. Every CSRpnUnit
has a pointer to an CSOpBase
object that is responsible for performing the actual calculation when
Evaluate()
is called. CSOpBase
is the base class for all operations and
functions in CalcStar
. It is essentially a functor design, with a virtual
function:
virtual int OpEval(std::vector<CSRpnUnit> &
arrObjCalcStack, bool & blnCalcSuccessful,int intCurrPos) = 0;
This function gets overridden whenever the user inherits
from CSOpBase
. To illustrate this, let’s
look at the CSOpPower
function in detail.
class CSOpPower: public CSOpBase
{
public:
CSOpPower();
~CSOpPower();
virtual int OpEval(std::vector<CSRpnUnit> & arrObjCalcStack,
bool & blnCalcSuccessful,int intCurrPos);
};
static bool blnCSOpPower_Registered =
CSTAR::GetOpBaseFactoryPtr()>Register<CSOpPower>("pow");
CSOpPower
inherits from CSOpBase
and implements the virtual
OpEval
function to perform the actual power calculation. Outside and after the class definition, the
new function is registered with the OpBase
Factory by the
GetOpBaseFactoryPtr()
… line of code. The template argument is the class being
registered. The string
supplied to the function is the signature of the
function. Registering is all the user
has to do to install new functions to be used. When the library compiles, it registers all of the functions this way. In
the implementation of the OpEval
:
int CSOpPower::OpEval(std::vector<CSRpnUnit> & arrObjCalcStack,
bool & blnCalcSuccessful,int intCurrPos)
{
char chrOutput[256];
for (int i = 0; i < 256; chrOutput[i++] = '\0');
blnCalcSuccessful = true;
bool blnValid;
double dblOutput;
CSRpnUnit
objOutput;
try{
this>ValidOpInputs(arrObjCalcStack,blnValid,intCurrPos,2);
if(blnValid)
{
double dblNum, dblPower;
this>GetOpNumber(&(arrObjCalcStack.at(intCurrPos  1)),dblPower);
this>GetOpNumber(&(arrObjCalcStack.at(intCurrPos  2)),dblNum);
dblOutput = pow(dblNum,dblPower);
objOutput.m_objTokenType = NUMBER;
objOutput.m_dblToken = dblOutput;
sprintf_s(chrOutput,"%f",dblOutput);
objOutput.m_strToken = chrOutput;
this>ReplaceOp(arrObjCalcStack,intCurrPos  2,intCurrPos,objOutput);
blnCalcSuccessful = true;
return 1;
}else{
blnCalcSuccessful = false;
return 0;
}
}catch(...){
blnCalcSuccessful = false;
return 1;
}
};
The operation first performs a ValidOpInputs()
to check and
see if the correct inputs are there for the function in the stack. If valid, then the power calculation is
performed. Next the ReplaceOp()
is
called replacing the operator and inputs with a single output number
CSRpnUnit
. It is recommended that the
user follow this pattern (validate, calculate, replace). It is also recommended that in making a new
function, just copy an existing function and change the name and inner
workings. It is easier that way and the
user will be less likely to forget something or make mistakes.