The following is an example implementation using the GT Graphical User Interface system.
Download source
Download GtCalculatorApp_20170114.zip
Version 1.1.0
Date: 12/01/2016
Author: Anthony Daniels
email: AnthonyDaniels99@gmail.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 in-fix 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 TI-83). 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
{ //IMPORTANT NOTE!!! these operators are in order of operation prescedence
//THE HIGHER THE NUMBER THE HIGHER THE PRESCEDENCE
//DO NOT CHANGE THIS ORDERING, HARDCODING IS USED BASED ON ENUMERATED TYPES
//
NOOP = -1,
OPENPAREN = 0,
OPENBRACKET,
OPENBLOCK,
CLOSEPAREN,
CLOSEBRACKET,
CLOSEBLOCK,
COMMA,
LT,
GT,
LTE,
GTE,
NEQ,
EQ,
AND,
OR,
NOT,
SUB,
ADD,
DIV,
MULT,
POW,
NEG,
ASSIGN,
NUMBER,
VAR,
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 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 |
The following is a screen shot from the GT based Calculator application. For more information on GT please see this article.
https://www.codeproject.com/tips/897483/gt-a-cplusplus-graphical-user-interface-library-and-system
The Test Menu runs the Prashtow test series from ExprTK. It has approximately 6600 tests and the answers were generated in the Excel file in the Calculator\TestSuite folder. The results are shown in the "TestCalcStar_Results.txt" file that is generated. Currently out of the 6600 tests CalcStar is only failing 20 tests. Most of these failures revolve around precision issues of large numbers coming out of the CSV generated answers file. The others revolve around the log function. This is being investigated.
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.