The BasicMathEval library

Table of Contents

This manual describes usage and functionality of the BasicMathEval library, version 1.0.

Copyright 2015, 2016 Ivano Primi <ivprimi(at)libero(dot)it>

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 3 or any later version published by the Free Software Foundation. You should have received a copy of the GNU General Public License along with this document. If not, see http://www.gnu.org/licenses/.

1 OVERVIEW

The BasicMathEval library is a simple programming library which supplies classes and functions for parsing and evaluating mathematical expressions within C++ programs.

The source code of BasicMathEval is in C++ and is conforming to the directives of the ISO/IEC 14882:2003 (C++03); thus, if you want to build and install the library on your system you need a C++ compiler and a C++ library which are C++03-compliant.

An example of application requiring functions to parse and evaluate mathematical expressions is a spreadsheet; another example of this type is given by programs for the scientific computing, like Octave(C) or bc(C).

The routines supplied by the BasicMathEval library are simpler than those ones of programs like Octave: they do not permit to manage and evaluate expressions containing arrays (i.e. vectors) or matrices and they recognize only a limited number of mathematical functions.

However, BasicMathEval already supports computations with complex numbers, and its routines understand mathematical expressions containing one or more variables.

2 DOWNLOAD AND INSTALLATION

The tar-gzipped archive with the source code of the BasicMathEval library can be downloaded from http://savannah.nongnu.org/download/bmeval. The latest stable release of BasicMathEval is version 1.0.

Detailed instructions on how to build and install BasicMathEval can be found in the file INSTALL included in the tar-gzipped archive with the source code.

3 COMPILING AND BUILDING A PROGRAM BASED ON BasicMathEval

Together with the BasicMathEval library a little and simple tool is installed. You can use it to quickly compile and build your BasicMathEval-based programs.

The tool is a console program called bmEvalConf. It is installed in the directory PREFIX/bin, where PREFIX is the root directory of your BasicMathEval installation (usually /usr/local, /usr or your $HOME).

In the following I will assume that

  • you are familiar with the command line interface (for example, the one of an X-Terminal),
  • you have installed on your system a C++ compiler supporting the standard ISO/IEC 14882:2003 (C++03), and
  • the directory PREFIX/bin is included in your PATH environment variable.

To know if PREFIX/bin is in your PATH, open a terminal and launch the command echo $PATH. If the definition of PATH does not include PREFIX/bin, then you should launch bmEvalConf by using its complete path (PREFIX/bin/bmEvalConf) in place of the plain name.

In addition, I assume that c++ is the command name under which the C++ compiler can be launched.

bmEvalConf recognizes four options:

-v      to return the current version of BasicMathEval

-c      to return the flags for compiling BasicMathEval-based programs

-l      to return the flags for linking against BasicMathEval

-n      to insert a newline at the end of the output

The option -v cannot be used together with the options -c and -l, but may be used together with -n:

bmEvalConf -v      writes to the standard output (terminal screen)
                   the version of BasicMathEval installed on your system,

bmEvalConf -v -n

or

bmEvalConf -n -v   (order does not matter) behaves exactly the same,
                   but it also prints a newline at the end of -v output.

The options -c and -l cannot be used together with -v, but they may be used both at the same time and they may be accompanied by the option -n. As above, order does not matter.

bmEvalConf -c      writes to the standard output the flags
                   needed for compiling BasicMathEval-based programs

bmEvalConf -l      writes to the standard output the flags
                   needed to link against BasicMathEval, and

bmEvalConf -c -l   

or

bmEvalConf -l -c   writes both the flags for compiling BasicMathEval-based
                   programs and the flags for linking against BasicMathEval.

If the -n option is added, then the information written is followed by a newline.

Let us assume that you have written a small program which makes use of objects and functions from the BasicMathEval library, and that its source code only consists of the file test.cpp. To compile the source file test.cpp you do not have to remember where the BasicMathEval library was installed since you may simply launch the command

c++ -c $(bmEvalConf -c) test.cpp

or

c++ -c `bmEvalConf -c` test.cpp

The first form of command is appropriate for a shell sh compatible, like ash, bash or ksh, the second form can be used with tcsh. If you are using another shell, probably the right form to obtain the expansion of the command bmEvalConf -c will be another one (look at the manual of your preferred shell for this).

Once you have obtained the object file test.o, you can do the linkage by using the command (for a shell sh-compatible):

c++ test.o $(bmEvalConf -l) -o test

or (for tcsh)

c++ test.o `bmEvalConf -l` -o test

If you want, you may also compile and build at the same time by means of

c++ test.cpp $(bmEvalConf -c -l) -o test

or

c++ test.cpp `bmEvalConf -c -l` -o test

Mind that, if BasicMathEval has been compiled with the support for double precision arithmetic, the output of bmEvalConf -c will also include the macro definition -D_DOUBLE_PRECISION_ (otherwise, a -D_SINGLE_PRECISION_ will be outputted).

4 BASIC USAGE

The main class provided by BasicMathEval is called evaluator. This class, as all other classes and functions supplied by the library, is defined inside the bmEval namespace.

After an object of type evaluator has been created, you can call its method evaluate to parse a mathematical expression and compute its result. This method has a very simple signature:

cValue evaluator::evaluate (const char* newExpression = (const char*)0)

It takes as argument a pointer to a buffer of characters containing the expression to be evaluated and returns its result in the form of a cValue. cValue is either an alias for std::complex<double> or an alias for std::complex<float>, depending on the way the BasicMathEval library has been built. Thus, cValue represents a complex value either in double or in single precision. Notice that cValue is actually defined as std::complex<rValue>, where rValue is either an alias of float or an alias of double.

Every evaluator object stores internally, in a form which allows for quick re-evaluation, the last evaluated expression. If the argument passed to the evaluate function is the null pointer, or if it is a pointer to an expression which compares exactly equal to the last evaluated expression, then this one is quickly re-evaluated by using its internal representation stored in the evaluator object.

For curious people: this internal representation is a queue of mathematical tokens, namely the tokens forming the last evaluated expression arranged according to reverse Polish notation (RPN). Thus, if the last evaluated expression is "(12 + 34) * 2", its internal representation will be the queue

12 34 + 2 *

where 12 is the first element of the queue and * the last one. Using the internal representation to re-evaluate an expression is faster than evaluating the same expression again. This for two reasons:

  1. The expression does not need to be parsed again before the actual evaluation takes place.
  2. Reverse Polish notation has been found to lead to faster calculations than usual algebraic notation.

Whenever the method evaluator::evaluate is executed, an exception of type evalError may be thrown. Therefore, every call to this method should be performed within a try…catch block. The program below is a first, very basic application of the BasicMathEval library:

#include <iostream>
#include <cstdlib>
#include <BasicMathEval/evaluator.h>

using namespace std;
using namespace bmEval;

int main (void)
{
  evaluator myEvaluator;
  cValue result;

  cout << "Hello world! This is my first computation :-)" << endl;
  cout << "1024 * ( 1 + 1/2 + 1/4 ) = ";
  try
  {
    result = myEvaluator.evaluate ("1024 * ( 1 + 1/2 + 1/4 )");
  }
  catch (evalError e)
  {
    cerr << "!!! Parsing error occurred: " << e << endl;
    return EXIT_FAILURE;
  } 
  cout << result.real() << endl;
  return EXIT_SUCCESS;
}

This is the output of the program when run on my laptop with GNU/Linux:

ivano@xxxxx[~]$ ./hello_world 
Hello world! This is my first computation :-)
1024 * ( 1 + 1/2 + 1/4 ) = 1792
ivano@xxxxx[~]$

Of course, in this particular case you do not expect any error occurrence while evaluating the given expression (1024 * ( 1 + 1/2 + 1/4 )), therefore it is superfluous to put the call to myEvaluator.evaluate inside a try…catch block. However, for a general program this would not be at all the case. Also, mind that the result of an expression should always be stored in a variable of complex (cValue) type, even if in the case of the expression above we know in advance that its result will have zero imaginary part (and we display indeed only the real part of the result). The real and imaginary part of a complex number can be obtained by using the methods real() and imag() of the std::complex class.

The following program shows how straightforward it is to write down a simple calculator relying on the functions of the BasicMathEval library.

#include <iostream>
#include <cstdlib>
#include <string>
#include <BasicMathEval/evaluator.h>

using namespace std;
using namespace bmEval;

void display (cValue result)
{
  if ( result.real() != 0)
  {
    cout << result.real();
    if (result.imag() != 0)
    {
      cout << showpos << result.imag() << 'i' << noshowpos;
    }
  }
  else // result.real() == 0
  {
    if (result.imag() != 0)
    {		      
      cout << result.imag() << 'i';
    }
    else
    {
      cout << '0';
    }
  } // end of result.real() == 0
  cout << endl;
}

int main (void)
{
  evaluator myEvaluator;
  cValue result;
  string inputLine;
  
  cout << "This is a simple calculator. Type an expression followed by <Enter>" << endl;
  cout << "to evaluate it and display its result, press <Ctrl> and 'C' together to quit" << endl;
  do
    {
      cout << ">>> ";
      getline (cin, inputLine);
      try
      {
        result = myEvaluator.evaluate (inputLine.c_str());
      }
      catch (evalError e)
      {
        cerr << "!!! Parsing error occurred: " << e << endl;
	return EXIT_FAILURE;
      } 
      display (result);
      if (myEvaluator.listOfComputationalErrors().size() != 0)
	{
	  vector<evalError> listOfComputationalErrors = myEvaluator.listOfComputationalErrors(); 
	  
	  cout << "+++ Caught computational error(s):" << endl;
	  for (size_t i = 0; i < listOfComputationalErrors.size(); i++)
	    cout << listOfComputationalErrors[i] << endl;
	}      
    } while ( cin.eof() == false && 
	      cin.fail() == false && 
	      cin.bad() == false);
  return EXIT_SUCCESS;
}

The program above asks the user for a mathematical expression until he presses simultaneously the keys <Ctrl> and C. The given expression is first stored in the string variable inputLine. Later a pointer to the buffer of characters containing the expression is passed to the method evaluate of the object myEvaluator for being processed and evaluated. In case of successful parsing of the given expression, the result returned by the evaluate method is displayed onto the screen, otherwise the error is reported which prevented the expression from being successfully parsed and the program is then terminated.

5 IMPORTANT KNOWLEDGE

Below you can see a work session on my laptop with the calculator from the precedent section. Mind that for a given number x, sqrt(x) is its square root, while atan(x) is its arc tangent, that is the value whose tangent is x. In particular, the value of atan(1) is equal to the fourth part of the mathematical constant pi.

ivano@xxxxx[~]$ ./simple_calculator 
This is a simple calculator. Type an expression followed by <Enter>
to evaluate it and display its result, press <Ctrl> and 'C' together to quit
>>> 3*3 + 4*4
25
>>> 10 + 1/10 * 30
13
>>> (10 + 1/10) * 30
303
>>> sqrt (100)
10
>>> sqrt (16)
4
>>> pi = atan(1) * 4
3.14159
>>> 2 * pi * 10
62.8319
>>> 2 * (x = 10)
20
>>> x
10
>>> sqrt(-1)
1i
>>> abs(3+4i)
5
>>> 33 / 0
nan+nani
+++ Caught computational error(s):

  Division by zero at position 3
  The divisor should always be a non-null number
>>> 12 / (3-3) + 0^0 - log(log(1))
nan+nani
+++ Caught computational error(s):

  Division by zero at position 3
  The divisor should always be a non-null number

  Bad exponent for operator at position 14
  Zero cannot be raised to a power with non-positive real part

  Out of domain at position 19
  The argument of the logarithmic function should always be a non-null number
>>> Area = pi * radius^2
!!! Parsing error occurred: 
  Undefined variable at position 12
  radius is neither a variable nor a function
ivano@xxxxx[~]$

Looking carefully at the results of this work session, you can surely notice several remarkable facts.

Mathematical operators are evaluated according to a precise priority order. This has been chosen to be compatible with the evaluation rules learned at school. For example, multiplication and division are computed before addition and subtraction. The order according to which operators are evaluated can be changed by using parentheses, namely ( and ): what is contained within a pair of parentheses is always evaluated before everything which is outside. This explains why the result of 10 + 1/10 * 30 is 13 while the result of (10 + 1/10) * 30 is 303. Parentheses can be nested, as you probably know from school. The parser of the BasicMathEval library can also cope with nested parentheses: as you should expect, it starts evaluating the operators and functions contained in the most inner sub-expression(s) and proceeds towards the exterior levels.

If two (or more) operators are in the same parenthesis level and have the same priority, their evaluation takes place according to grouping rules: depending on the type of the operators, they can be evaluated from left to right or from right to left. You can find in section 6 detailed information about the different classes of operators supported by BasicMathEval, including priority levels and grouping rules.

As already mentioned, in the work session above you can find the function sqrt (square root), which for every given number evaluates to the square root of the number (more precisely, to the first complex branch of the square root of the number). Besides sqrt, BasicMathEval supports all common mathematical functions, for example exponential and logarithmic functions, trigonometric and inverse trigonometric functions, and also less common ones. Look at section 7 for the list of all mathematical functions known to the evaluator of BasicMathEval and for their meaning/usage. Mind that, if two or more functions are found in the same parenthesis level, they are evaluated from left to right. Also, functions have an higher priority than operators: within the same parenthesis level, first functions are evaluated, then any eventual operator.

The expression to evaluate may also contain variables and assignment operators. In particular, the simple assignment operator (=) is supported by BasicMathEval, as shown by the evaluation of pi = atan(1) * 4, which defines pi as a variable with value equal to the one of the mathematical constant with the same name.

As you likely know, variables are very useful to safe time and avoid copy errors while entering new expressions, since they allow to replace long and difficult to remember numeric values with short, easy to remember names. Constants are similar to variables, but are defined using the operator := instead of = and once defined cannot be modified: the value of a constant is a constant value. Both variables and constants can be removed (destructed) by means of the delete operator (~).

The identifier of a variable or constant may consist of any combination of alphabetical letters, underscores and digits, but it must start with an alphabetical letter or an underscore. Also, the identifier of a variable/constant should never coincide with the name of any of the mathematical functions known to BasicMathEval, otherwise a parsing error will arise during the execution of evaluate.

As in C/C++, assignment operators are considered by the routines of BasicMathEval as normal operators having a result. The result of an assignment operator is the value which the variable on the left side of the assignment is set to. This explains why the result of pi = atan(1) * 4 is (approximately) the value of the mathematical constant pi. Thus, evaluator::evaluate can also cope with expressions in which an assignment is part of a complex computation, like 2 * (pi = atan(1) * 4) * 10, which first defines the variable pi and then uses its value to compute the circumference of a circle with radius 10. You can see this capability in action by looking at the work session above: when 2 * (x = 10) is evaluated, first the variable x is defined and its value is set to 10 (look at the result of the subsequent expression), then the double of the value of x is computed and displayed. Also the delete operator (~) returns a result: its result is the value of the deleted variable or constant. In section 6 you can find more information on the assignment operators supported by BasicMathEval, as well as on the delete operator.

Mind that variables and constants defined in the expression passed to evaluator::evaluate are stored in a lookup table internal to the evaluator object on which the function evaluate is called. Thus, when the program below is executed

#include <iostream>
#include <cstdlib>
#include <BasicMathEval/evaluator.h>

using namespace std;
using namespace bmEval;

int main (void)
{
  evaluator myEvaluator;
  cValue result;

  cout << "pi = ";
  try
  {
    result = myEvaluator.evaluate ("pi := 4 *atan(1)");
  }
  catch (evalError e)
  {
    cerr << "!!! Parsing error occurred: " << e << endl;
    return EXIT_FAILURE;
  } 
  cout << result.real() << endl;
  return EXIT_SUCCESS;
}

the constant pi is stored in a lookup table internal to the object myEvaluator. Whenever a new expression is passed to myEvaluator::evaluate and the identifier of a variable or constant is found inside it, the object myEvaluator searches in its lookup table for a variable or constant matching the found identifier. If such a variable/constant is found, then its value is used during the evaluation of the expression. If not, then an Undefined variable error is arisen. The only exception to this rule is, when the operator = or := is used to set a variable or respectively a constant. In such a case the nonexistence of a variable/constant matching the identifier is not considered as an error.

What has been just said implies that different instances (objects) of the evaluator class do have different lookup tables. If at a certain time two or more evaluator objects exist, none of them is aware of the variables and constants stored in the lookup tables of the other objects. Thus, when the function evaluate is called on a certain evaluator object, there is no risk that variables and constants stored in other objects existing at the time of the call are used to evaluate the given expression.

The current contents of the lookup table of an evaluator object can be accessed and possibly modified by means of the reference returned by the function evaluator::internalVartable. This function takes no argument and returns a reference to an instance of the class variablesTable, namely a reference to the lookup table of the object on which the function is called. In this way a programmer can define, examine, delete and possibly modify the variables and constants stored in the lookup table of an evaluator object without having to evaluate expressions only for these purposes.

An instance of the class variablesTable is roughly speaking a map associating strings, the identifiers of the stored constants and variables, with complex values. These ones are not just instances of the class cValue but are provided with a Boolean attribute specifying whether they are read-only, as it is the case for constants, or modifiable, as in the case of variables.

The class variablesTable provides public methods to query and modify the contents of an instance. It is also possible to erase or list the contents of a variablesTable object, either partially or completely. Read section 8 to know more about the functionalities offered by the class variablesTable to manage the lookup tables associated to evaluator objects.

As said above, the result of the evaluation of an expression via evaluator::evaluate is a complex number. The work session above shows indeed that the expression to evaluate may contain complex values. This is actually not always true, it depends on the properties set when constructing an instance of evaluator; however, the default constructor of the class evaluator creates an object which can handle complex numbers. Complex numbers are written using the common notation a+bi, where a and b are real (floating point) values. If b is zero, then a can be used as a shorter form for a+0i. Mind however that the pure imaginary unit should be written as 1i and not as i, since the latter one would be treated as the identifier of a variable.

Coming to evaluation errors, they can be distinguished in two types: computational errors (division by zero, out of domain, and invalid exponent) and parsing errors. A parsing error occurs if, while analyzing the parts forming the given expression, evaluator::evaluate detects that this one violates the rules according to which an expression should be written to be considered valid and then evaluated. For example, the evaluation of the expression 12 + * 18 will result in the raising of the parsing error Missing argument for operator, since a numeric value (which could be also provided by a variable, a constant, or a sub-expression) is missing before the product: without such a value, the mathematical evaluation of the expression is not possible. In other words, the expression is mathematically illegal, which can be seen before starting any computation.

It can also happen that the given expression is mathematically legal, but its result cannot be properly computed since:

  • one of the values provided to an operator makes the evaluation of the operator impossible, or
  • the argument supplied to a function is out of the numeric set where the function is defined, i.e. the value of the function for the given argument cannot be computed.

The former is the case of the expression 33 / 0 above, which causes the raising of the error Division by zero. The latter is for example the case of the expression log(0), which causes the raising of the error Out of domain, since the logarithmic function is not defined for the value zero of the argument. Both Division by zero and Out of domain are computational errors. The only other error of this type is Bad exponent, which occurs if during the evaluation of an expression BasicMathEval tries to compute 0^z where z is a complex number with non-positive real part.

Parsing errors and computational errors are handled differently by objects of the evaluator class.

The occurrence of a parsing error makes an exception arise. The exception is always an instance of the class evalError. As for any C++ exception, if it is not caught, it causes normally the immediate termination of the running program (only clean-up operations are performed before quitting the program). In the source code of the simple calculator from section 4 parsing errors are caught to be printed onto the standard error device (screen). After printing a parsing error, the program terminates by returning a failure code.

Computational errors are also represented as instances of the class evalError, but instead of being thrown they are stored in a list internal to the evaluator object that is used to evaluate the mathematical expression(s) in which they arise.

The list of computational errors of an evaluator object is cleared before computing (or recomputing) any mathematical expression. After the result of a mathematical expression has been computed, the list of computational errors of the evaluator object which performed the computation contains all computational errors arisen till to the determination of the result. If no computational error occurred, the list will be empty.

The current contents of the list can be obtained by means of the method evaluator::listOfComputationalErrors(), which returns the list in the form of a vector of /evalError/s, as shown in the source code listing of the simple calculator from section 4. Since the contents of the list of computational errors should only be modified by its owning evaluator object, the return value of evaluator::listOfComputationalErrors() is actually a constant reference to a vector of /evalError/s.

The programmer should decide if and when it is appropriate that the user of its program is made aware of the computational errors happened during the evaluation of an expression. In any case, the user will be likely notice that something wrong happened, since the occurrence of a computational error makes the result of an expression be equal to nan+nani, where nan is the Not a Number constant, precisely std::numeric_limits<rValue>::quiet_NaN().

6 SUPPORTED OPERATORS

BasicMathEval supports the following classes of operators:

  • unary operators (+, - and !),
  • arithmetic operators (+, -, *, /, %, //, %%, ^, <>, ><),
  • comparison operators (the same as in C/C++),
  • logical operators (&&, ||, ^^),
  • assignment operators (both simple and compound), and
  • the deletion operator (~).

The table below provides an overview of the priority levels and grouping rules of the single operators.

Priorities                                                    Grouping

HIGHEST
  |
  |
9 |      ~  (unary operator)
8 |      ^                                                    Right to left
7 |      !  +  -  (unary operators)                           Right to left
6 |      <>  ><                                               Left to right
5 |      *  /  %  //  %%                                      Left to right
4 |      +  -                                                 Left to right
3 |      <  >  <=  >=  ==  !=                                 Left to right
2 |      &&  ||  ^^                                           Left to right
1 |      :=  =  +=  -=  *=  /=  %=  //=  %%=  ^=  <>=  ><=    Right to left
  |
  V
LOWEST

Most of the operators supported by BasicMathEval should be well known to any C/C++ programmer and do not require any explanation. However, it should be noticed that:

  • Comparison operators return zero for false, and one for true.
  • The logical NOT operator (!) returns zero whenever its operand is different from zero, and one if its operand is zero.
  • The logical operators AND (&&), OR (||) and XOR (^^) do always return either zero or one, according to the following table
  left-hand operand right-end operand result
AND non-zero non-zero 1
  non-zero zero 0
  zero non-zero 0
  zero zero 0
OR non-zero non-zero 1
  non-zero zero 1
  zero non-zero 1
  zero zero 0
XOR non-zero non-zero 0
  non-zero zero 1
  zero non-zero 1
  zero zero 0
  • ^ is not the bitwise XOR but the power operator, thus z^n is z raised to the power of n.
  • The operator // is the integer division. For complex values w and z, w//z is defined as the complex number whose real and imaginary part are obtained by truncating respectively the real and the imaginary part of the result of the complex division of w by z. Mind that the truncation of a floating point value is done by approximating it to the integer number which is obtained by zeroing the fractional part of the value. In this way, if w and z are integer values, then w//z is the usual integer division of w by z. If w and z are real, non-integer values, w//z is the truncation of the quotient w/z.
  • The arithmetic operators +, -, *, /, % are the same as in C/C++; however, in BasicMathEval their operands can be any complex values. In particular, the definition of the modulus operator (%) is extended in such a way that w%z makes sense also if w and z are complex values: w%z is defined as w-(w//z)*z, where w//z is the result of the integer division of w by z. If w and z are both integer values, w%z is just the remainder of the integer division of w by z.
  • %% is the percentage operator, which is defined as
w%%z = (w / 100) * z .
  • <> and >< are, respectively, the maximum and minimum operator. w<>z is equal to w if the real part of w is greater than or equal to the real part of z, otherwise it is equal to z. Analogously, w><z is equal to w if the real part of w is less than or equal to the real part of z, otherwise it is equal to z. In case of real operands (i.e. operands with null imaginary part), <> and >< coincide with the usual maximum and minimum.
  • As in C/C++, both simple and compound assignment operators are supported by BasicMathEval. However, two types of simple assignment are known to BasicMathEval, namely = and :=. The operator = is used to set a variable (i.e. either to define a new variable or to change the value of an already existing one), while := is used to define a constant.
  • In addition to the compound assignment operators which are well known to any C/C++ programmer, BasicMathEval also knows about //=, %%=, ^=, <>=, and ><=, which are obtained by combining with an assignment the operations //, %%, ^, <>, and ><, respectively. Thus
w//=z  is the same as  w = w//z, and similarly for the other operators.
  • The deletion operator (~) can be used to un-define a variable or a constant. Whenever applied to a variable or constant within an expression, the deletion operator first returns the value of the variable/constant, then removes this one from the internal list of the evaluator object which is evaluating the expression. Thus, if the value of x is 21.5, then after the assignment y = 1 + ~x the variable y will have the value 22.5 but x will not be any longer defined. Before being used in further computations, x will have to be defined again through a simple assignment.

7 SUPPORTED MATHEMATICAL FUNCTIONS

BasicMathEval supports the following functions:

re real part of a complex number
im imaginary part of a complex number
arg argument of a complex number
abs absolute value
conj conjugate of a complex number
exp exponential function
sqrt square root
cbrt cubic root
log logarithm
log2 base-2 logarithm
log10 base-10 logarithm
sin sine
cos cosine
tan tangent
asin arc sine
acos arc cosine
atan arc tangent
sinh hyperbolic sine
cosh hyperbolic cosine
tanh hyperbolic tangent
asinh hyperbolic arc sine
acosh hyperbolic arc cosine
atanh hyperbolic arc tangent
erf error function (Faddeeva's function)
erfc complementary error function
gamma gamma function
floor floor function
ceil ceiling function
round rounding function (halfway cases are rounded away from zero)
fix truncation
frac fractional part
step Heaviside step function (returns one if argument is zero)
ostep Heaviside step function (returns zero if argument is zero)
sign sign function (the sign of the real part of the argument)
X01cc characteristic function of the segment {x+0i : x in [0,1]}
X01oo characteristic function of the segment {x+0i : x in (0,1)}
X01co characteristic function of the segment {x+0i : x in [0,1)}
X01oc characteristic function of the segment {x+0i : x in (0,1]}
disp display function

Most of the supported mathematical functions behave as expected: the value they return is the one established by the mathematical definition. Whenever an invalid argument is passed to a function, the method evaluator::evaluate recognizes the mistake and raises the computational error Out of domain. Mind that an invalid argument is any argument falling outside the domain of a function (see https://en.wikipedia.org/wiki/Domain_of_a_function). If an evaluator object has been instantiated to only handle computations with real numbers, then it considers as domain of a function the real domain, not the complex one.

For some of the supported functions, there are no standard mathematical definitions specifying the values they should return. This is the way BasicMathEval handles them:

  • The disp function returns the value of its argument and at the same time prints a message to standard output. The message tells at which position within the evaluated expression the function is called and reports the value of the argument. Mind that positions are counted from zero, where zero is the position of the first character in the expression, and that the argument of disp is printed in the form (x,y) where x and y are the real and the integer part of the argument, respectively. For example, the evaluation of the expression 2+disp(3+35/432) * 10 causes the writing of the message disp() at position 2: (3.08101851852,0). Mind that disp is not available if BasicMathEval has been compiled with the macro _BUILD_WITH_LESS_FUNCTIONALITY_ enabled. This is usually done to get a version of BasicMathEval which is completely thread-safe.
  • The functions of the family X****() may only return one or zero. The function X01cc(), for instance, returns one (1) whenever the imaginary part of the argument is equal to zero and the real part is greater than or equal to zero and less than or equal to one. In all the other cases X01cc() returns zero (0). The other functions from the family X****() have a similar behavior, only the subset of the complex plane where they assume the value one is different (see the table above).
  • The sign function returns one (1) if the real part of its argument is positive, -1 if it is negative, and zero if it is null.
  • The step function returns 1 if the imaginary part of its argument is zero and the real part is non-negative. The ostep function returns 1 if the imaginary part of its argument is zero and the real part is (strictly) greater than zero.
  • The frac function returns the complex value whose real and imaginary part are given by the fractional part of the real and imaginary part of the argument, respectively. Mind that the fractional part of a real value is given by the digits following the radix character (decimal point). The sign of the fractional part is the same of the original value. Thus frac(5.06) is 0.06, frac(-72.98) is -0.98, frac(9.0) is zero, and frac(12.45-36.21i) is 0.45-0.21i.
  • If its argument is a real value, the function fix returns the integer number which is obtained from the argument by deleting all digits after the radix character (decimal point). In case of a complex argument x+yi the value returned by fix is equal to fix(x)+fix(y)i, i.e. it is equal to the complex number obtained by computing fix for the real and imaginary part of the argument.
  • The functions round, ceil, and floor behave similarly to fix, but for a real argument x the values round(x), ceil(x) and floor(x) are defined respectively as
    • the value obtained by rounding x to the nearest integer, where halfway cases are always rounded away from zero,
    • the smallest integral value not less than x, and
    • the largest integral value not greater than x.
  • gamma is the gamma function, which for a real argument x is defined by gamma(x): integral from 0 to infinity of t^(x-1) e^-t dt. In case of a complex argument x+yi, the value returned is equal to gamma(x), i.e. the number which would be returned for the real part alone. Mind that this definition of gamma does not comply with the usual one in case of complex argument.
  • erf is defined as Faddeeva's extension of the error function to the complex plane. erfc is the complementary error function, which is defined by
erfc(z)=1-erf(z).

8 MANAGING LOOKUP TABLES

In this section the interface of the class variablesTable is illustrated. A variablesTable is a map which associates identifiers of constants and variables (i.e. strings) with instances of the class storedValue. An instance of the class storedValue is a complex value provided with a Boolean attribute specifying whether the value is read-only or modifiable. If the storedValue associated with an identifier is read-only, that identifier defines a constant, otherwise a variable. Both the class variablesTable and the class storedValue are declared in the header file BasicMathEval/variablesTable.h, which is automatically included when including BasicMathEval/evaluator.h. The header file BasicMathEval/variablesTable.h declares also the class variableDefinition. Instances of variableDefinition are requested to call some of the methods of the class variablesTable. For convenience of the reader, the public interfaces of the classes declared in BasicMathEval/variablesTable.h are reported here together with adequate comments and explanations.

namespace bmEval
{
  class storedValue
  {
    // An object of the class STOREDVALUE is a complex value
    // provided with a Boolean attribute specifying whether the value
    // is read-only or modifiable. If the STOREDVALUE associated to an
    // identifier is read-only, that identifier defines a constant,
    // otherwise a variable.
    
  public:
    // Constructor
    storedValue (cValue value = cValue(0, 0), bool isReadOnly = false) : m_value(value), m_isReadOnly (isReadOnly) {}

    // Copy-Constructor
    storedValue (const storedValue& obj) : m_value(obj.m_value), m_isReadOnly (obj.m_isReadOnly) {}

    // Safe assignment: the assignment operation is only
    // performed if the field M_ISREADONLY is
    // currently set to false.
    storedValue& operator= (const storedValue& obj) {
      if (&obj != this && !m_isReadOnly)
	{
	  m_value = obj.m_value;
	  m_isReadOnly = obj.m_isReadOnly;
	}
      return *this;
    }

    // Destructor
    ~storedValue() {
      m_value = cValue (0, 0);
      m_isReadOnly = false;
    }

    // Setting method for the value.
    // The field M_VALUE is set to the supplied NEWVALUE.
    void setValueTo (cValue newValue)
    {
      m_value = newValue;
    }
    
    // Setting method for the read-only flag.
    // The field M_ISREADONLY is set to the supplied NEWVALUE.
    void setReadOnlyFlagTo (bool newValue)
    {
      m_isReadOnly = newValue;
    }
 
    // Setting method for both M_VALUE and M_ISREADONLY.
    void setTo (cValue newValue, bool willBeReadOnly) {
      m_value = newValue;
      m_isReadOnly = willBeReadOnly;
    }
    
    // Getting methods for value and read-only flag.
    cValue getValue () const { return m_value; }
    bool isReadOnly () const { return m_isReadOnly; }
    
  private:
    cValue m_value;
    bool m_isReadOnly;
  };

  class variableDefinition {
    // An instance of the class VARIABLEDEFINITION consists of an identifier
    // (a string), of the complex value associated to the identifier, and 
    // of a Boolean attribute specifying whether this value is read-only or modifiable.
    // Value and boolean attribute form together the contents of
    // a VARIABLEDEFINITION. They are saved in the field
    // M_CONTENTS, which is an instance of the class STOREDVALUE.
    // Mind that, if the value associated to its identifier is read-only,
    // a VARIABLEDEFINITION defines a constant, otherwise a (proper) variable.    

    // The constructor of the class VARIABLEDEFINITION always checks if
    // the argument ID (providing the identifier of the new VARIABLEDEFINITION)
    // is conform to the rule which determines whether a string is a valid
    // identifier or not.
    // A valid identifier may consist of any combination of alphabetical letters,
    // underscores and digits, but it must always start with an alphabetical letter
    // or an underscore.
    // If the argument ID does not supply a valid identifier, the
    // constructor of the class VARIABLEDEFINITION will throw an
    // exception of type std::runtime_error.    
  public: 
    variableDefinition (const std::string& id, cValue value = cValue(0, 0), bool isReadOnly = false):
      m_id(id), m_contents (value, isReadOnly) {
      checkIdValidity();
    }
      
    variableDefinition (const std::string& id, const storedValue& contents):
      m_id(id), m_contents(contents) {
      checkIdValidity();
    }
    // Default copy constructor and assignment operator are fine
    // Default destructor is fine

    // Getting methods for identifier and contents.
    std::string getId() const {
      return m_id;
    }

    storedValue getContents() const {
      return m_contents;
    }
    
  private:
    // ...

    std::string m_id;
    storedValue m_contents;
  };

  class match {
    // Objects of the class MATCH are used within methods
    // of the class VARIABLESTABLE to select those variables
    // whose identifiers match a given prefix/suffix.
  public:
    // Two types of match are possible when comparing a
    // variable identifier against a given key: prefix match
    // or suffix match.
    enum type {
      PREFIX = 0,
      SUFFIX = 1
    };

    match (const std::string& tag, type mtype) : m_tag(tag), m_type(mtype) {}
    // Default copy constructor and destructor are fine for this class,
    // also default assignment operator is fine.
    bool operator() (const std::pair<std::string, storedValue>& vDef) const
    {
      if (m_type == PREFIX)
	return (vDef.first.size() >= m_tag.size() &&
		vDef.first.compare (0, m_tag.size(), m_tag) == 0);
      else
	return (vDef.first.size() >= m_tag.size() &&
		vDef.first.compare (vDef.first.size() - m_tag.size(), m_tag.size(),
				    m_tag) == 0);
    }

  private:
    std::string m_tag;
    type m_type;
  };
  
  class variablesTable
  {
    // An object of the class VARIABLESTABLE is roughly speaking a map
    // associating strings, the identifiers of the constants and
    // variables stored in the object, with their (complex) values.
    // These values are actually instances of the class STOREDVALUE, i.e.
    // they are provided with a Boolean attribute specifying
    // whether they are read-only, as it is the case for constants, or
    // modifiable, as in the case of proper variables.

    friend class basicCalculator;
  public:
    // This struct is only used in the method
    // getVariables() to return a vector with
    // the values of (some of) the variables
    // stored in the TABLE.
    struct entry {
      std::string m_id;
      cValue m_value;
      bool m_isReadOnly;
    };
    
    // Default constructors and assignment operator are fine

    // Destructor
    ~variablesTable ()
      {
	m_table.clear();
      }

    // Setting method.
    // If the ID of the given variable definition (VARDEF) is not present
    // in the TABLE, then a new variable according to the
    // provided definition is added to the TABLE and TRUE is returned. 
    // Otherwise, the variable with the ID of VARDEF, if it is 
    // not read-only, is set to the VALUE and READONLY flag
    // of VARDEF and TRUE is returned.
    // If the variable with the ID of VARDEF is read-only,
    // then it is not set, and the method returns FALSE.    
    bool setVariable (const variableDefinition& varDef)
    {
      return setVariable(varDef.getId(), varDef.getContents().getValue(), varDef.getContents().isReadOnly());
    }
    
    // Setting method. For every variable definition
    // from the vector DEFINITIONS (a variable definition
    // consists of a variable ID and a STOREDVALUE, i.e. a couple
    // VALUE + READONLY flag) the following operation is
    // performed: if the ID from the definition is not present
    // in the TABLE, then a new variable with this ID and with
    // the VALUE and READONLY flag requested by the definition is added to it. 
    // Otherwise, the variable with the speficied ID, if it is 
    // not read-only, is set to the requested VALUE and READONLY flag.
    void setVariables (const std::vector<variableDefinition>& definitions)
    {
      std::vector<variableDefinition>::const_iterator constIt;

      for (constIt = definitions.begin(); constIt != definitions.end(); constIt++)
	{
	  m_table[constIt->getId()] = constIt->getContents();
	}
    }

    // If PREFIX does not start with an alphabetic character or underscore,
    // this function does nothing. Otherwise, for every variable definition
    // from the vector DEFINITIONS (a variable definition
    // consists here of a STOREDVALUE, i.e. a couple
    // VALUE + READONLY flag) the following operation is
    // performed: first an ID is constructed by concatenating PREFIX with the
    // index of the current definition (mind that the index of the first definition
    // in the vector is zero), then if the constructed ID is not present
    // in the TABLE, a new variable with this ID and with
    // the VALUE and READONLY flag requested by the definition is added to it. 
    // In case a variable with the constructed ID is already present in the TABLE,
    // this variable, if it is not read-only, is set to the requested
    // VALUE and READONLY flag from the current definition.
    void setVariables (const std::string& prefix, const std::vector<storedValue>& definitions)
    {
      size_t len;

      if ( (Utils::doesStartWithId (prefix, len)) )
	{
	  std::vector<storedValue>::size_type idx;
	  std::string id;
	  
	  for (idx = 0; idx < definitions.size(); idx++)
	    {
	      id = prefix + Utils::n2str (idx);
	      m_table[id] = definitions[idx];
	    }
	}
    }
    
    // Getting method. This function returns false if TABLE
    // contains no variable with the provided ID, true otherwise.
    // In the latter case, VALUE is set to the value, and
    // ISREADONLY to the read-only attribute of the
    // variable with the given ID. 
    bool isVariableDefined (const std::string& id, cValue& value,
			    bool& isReadOnly) const
    {
      bool rv;

      std::map<std::string, storedValue >::const_iterator constIt = 
	m_table.find(id);
      if ( (rv = (constIt != m_table.end())) )
	{
	  value = constIt->second.getValue();
	  isReadOnly = constIt->second.isReadOnly();
	}
      return rv;
    }

    // Erasing method. This function removes from TABLE the 
    // variable with the given ID. It returns 1 if such a variable
    // has been actually found and removed, 0 otherwise.
    size_t eraseVariable (const std::string& id)
    {
      return m_table.erase (id);
    }

    // This function removes all variables stored in TABLE.
    void clear ()
    {
      m_table.clear();
    }

    // This function returns the number of variables
    // stored in TABLE.
    size_t size () const
    {
      return m_table.size();
    }

    // If KEY is the empty string, this function will
    // return a vector with the list of all variables contained
    // in the TABLE.
    // If KEY is not the empty string and MTYPE is match::PREFIX (match::SUFFIX),
    // then this function will return in the form of a vector the list of
    // all variables from TABLE whose identifier starts (ends) with the
    // prefix (suffix) KEY.
    std::vector<entry>
    getVariables (const std::string& key, 
		  match::type mType = match::PREFIX) const;
    
    // If KEY is the empty string, this function will
    // print to OS the list of all variables contained in the TABLE.
    // If KEY is not the empty string and MTYPE is match::PREFIX (match::SUFFIX),
    // then this function will print to OS the list of
    // all variables from TABLE whose identifier starts (ends) with the
    // prefix (suffix) KEY.
    void listVariables (std::ostream& os, 
			const std::string& key, 
			match::type mType = match::PREFIX) const;

    // If KEY is the empty string, this function will
    // remove all variables stored in the TABLE.
    // If KEY is not the empty string and MTYPE is match::PREFIX (match::SUFFIX),
    // then this function will remove from TABLE all variables
    // whose identifier starts (ends) with the prefix (suffix) KEY.
    // The value returned is the number of variables actually removed.
    size_t eraseVariables (const std::string& key, 
			   match::type mType = match::PREFIX);

  private:
    // ...

    std::map<std::string, storedValue > m_table;    // the actual TABLE
  };
} // end of namespace bmEval

9 SIMPLE SCIENTIFIC CALCULATOR

Depending on the way the BasicMathEval library has been built and installed on your system, it is possible that also the simple scientific calculator (ssc) is available to you. This is just a plain calculator based on the functionalities provided by BasicMathEval, which you should be able to run from the command line of your terminal just by launching the command

ssc

To get a quick help on how to use it, start ssc with the option -h:

ssc -h

Author: Ivano Primi <ivprimi(at)libero(dot)it>

Created: 2016-04-17 Sun 21:09

Emacs 24.4.1 (Org mode 8.2.10)

Validate