The code consists of four classes in order to evaluate a string of a real or complex math expression, reducing the expression to one real or complex number. The math expression may or may not include variables.
Introduction
In many situations, there may be a string
containing a math expression, such as "1+2*5
" or "(3+i)(3-i)
", and there is the need to do the math and calculate the result. Also, in case of a formula like "0.5*x+4
", we may need to calculate the result for several different values of x
. In those situations, the complex parser presented here may help.
The classes here are a small part -but improved- of my all free and downloadable CAS calculator http://xrjunque.nom.es. One of the goals is that these classes do not rely on other 'external' classes as happens in the CAS calculator.
The Five Classes
- Class
Global10
contains global values like number of decimals, imaginary character (i
or j
) or CultureInfo
. - Class
Msg10
just contains a few messages to handle possible errors. - Class
Rational
gives a bit more of accuracy in the operations. - Class
Complex
class does the complex math. - Class
parseComplex
is in charge of dividing the input string into tokens and call accordingly to classes Complex
or Msg10
. Class Complex
makes use of class Rational
for its Real and Imaginary members. The 'tokenizing' job is done by a Regex pattern.
Tokens
The tokens groups are:
mode <mode>
numbers <num>
operators <op>
logical operators <lop>
functions <fn>
constants <cnt>
variables <var>
any other character <any>
besides end of tokens <end> formed by an escape character Chr(27).
The pattern looks like:
(?i)(?<mode>(&dec(?<dec>\d{1,2})|&(rad|deg|grad|[hobdij])))(?-i)
(?<numop>(?<num>((\d{1,3}((\,\d{3})+(?!\d))(\.[0-9]+)?)|
[\d]{1}[\.\dA-Fa-f]*)([eE](\s*)[-+]?[0-9]+)?)|
(?<op>[-+*/\^]))|\(|\)|
(?i)(?<fn>logtwo|logten|acosh|acoth|acsch|asech|asinh|atanh|
floor|round|norm|conj|coth|csch|sech|acos|acot|acsc|asec|asin|atan|cosh|sign|sinh|
sqrt|tanh|abs|cos|cot|csc|exp|log|sec|sin|sqr|tan|ln|re|im)(?![a-zA-Z_]+)|
(?<lop>\<\<|\>\>|nand|mod|and|nor|xor|not|or|%|!)(?![a-zA-Z_]+)|
(?<cnt>e|(?i)pi)(?![a-zA-Z_]+)|
(?<vars>[_a-zA-Z]\w*)+|(?<end>\e)+|
(?<any>[^\s←\,\.])+|(?<any>\,|\.)+
</dec>
Pattern for numbers, depending on the Globalization.CultureInfo
setting, may swap the dot (NumberFormat.NumberDecimalSeparator
) and the comma (NumberFormat.NumberGroupSeparator
).
Mode makes possible to enter numbers in hexadecimal, decimal, octal or binary base; along with setting the number of decimals and the imaginary character.
Using the Code
The are two possible ways of instantiation:
Dim eP As New ParseComplex
eP.CultureInfo = New Globalization.CultureInfo("fr-FR")
...
Dim eP As New ParseComplex(New Globalization.CultureInfo("es-AR"))
...
By default, CultureInfo
is set to "en-US
".
Evaluation is done by calling one of the two Evaluate()
methods.
First method:
Dim cplx As Complex = eP.Evaluate("(3+5*i)*(3-i*5)")
First method with variables, set in a Dictionay(Of String, Complex)
:
eP.vars.Add("x", Complex.one)
eP.vars.Add("y", New Complex(-1, 2))
Dim cplx As Complex = eP.Evaluate("(3+x*i)*(y-i*5)")
Once the string
has been parsed, it is possible to call the overloaded second method:
eP.vars.Item("x") = New Complex(3)
Dim cplx As Complex = eP.Evaluate(eP.vars)
Variables names start with a letter or underscore (_
), can contain letters, numbers or underscore and can be any length.
Of course, you may call the Complex
class directly, if you don't need the parser.
The default numeric base is decimal. To change to another base write &h
(hexadecimal), &o
(octal) or &b
(binary). Appending &d
will restore to decimal base.
In a similar way, °
, &grad
will accept angles in degrees or in gradians. To restore to default radians, enter &rad
.
To change default imaginary i
to j
, write &j
and to turn back to the default character, write &i
.
For instance, entering &culture it-IT
will change the current CultureInfo
to it-IT
. So inputs and ouputs will be in mented culture.
An example of possible modificators is the following:
Dim cplx As Complex = eP.Evaluate("&culture fr-FR &dec2 &h 0f + &j &d 0,2+0,3*j"
Console.WriteLine(cplx.tostring)
The Output
You may call Complex.ToString()
or ToStringComplex( numDecimals As Int32, sImg As String, cultureInfo As Globalization.CultureInfo) As String
:
cplx.ToStringComplex(4, eP.Imaginary, eP.CultureInfo)
Detail Version
If the word detail
is found, code will output operation steps. For example:
Dim cP As New ParseComplex
Dim cplx As Complex = cP.Evaluate("detail (2+i*3)*(1+i)")
Console.WriteLine(cP.GetDetail)
Basic Principles
The parsing method is a recursive-descent parsing: Parsing Expressions by Recursive Descent.
Evaluation method E
calls T
for any addition or substraction, but T
calls first F
for any multiplication or substraction, and F
calls first P for any power possible power operation. P
calls first v
to get next token. If there is a "(
" token, v
calls recursively to T
.
E --> T {( "+" | "-" ) T}
T --> F {( "*" | "/" ) F}
F --> P ["^" F]
P --> v | "(" E ")" | "-" T
Step by Step Walk-throughs
The algorithm presented here englobes T
and F
in a single method. Besides, method v
operates logical operators, '%' and mod
any possible function like cos()
, csc()
and so on.
While writing this article, I found some glitches. If you find any further error, please let me know.
History
- 25th March, 2022: Initial version