12,547,922 members (46,334 online)
alternative version

16.2K views
24 bookmarked
Posted

# Evaluate Complex and Real Math Calculator

, 22 May 2011 Ms-PL
 Rate this:
Evaluation of complex and real numbers from string

## Introduction

As of .NET 4.0, the native library does offer a complex class in the namespace System.Numerics. The ideal situation had been to write the formulas in a way that they are written in for instance Matlab or Matematica.

The solution I have tried is to use Regular Expressions, and write the formula as a simple `string`, convert the `string `to a series of complex numbers, perform calculations and give out the calculated result.

The evaluator is more general than to just do calculations on complex numbers however. It can also function as a normal calculator with just real numbers.

## RegEx Pattern for Complex and Real Numbers

The pattern to recognize the use input is always on the form a+bi and is defined as a complex pattern which we need to recognize. There are basically three main ways to write a complex number:

1. a+bi Containing both a real part and an imaginary part
2. a Only a real part
3. bi Only a real imaginary

Both a and b are real numbers (in native .NET, they can have the form of Double, Decimal or Integer). The Regular Expression for a double number can be found on the web, but they rarely show a general RegEx for finding a general computer number in succession, separated by mathematical operators. A simple example of an input gives an idea of what is needed in recognizing the main pattern of numbers of computer format:

`3.567E-10+4.89E+5i-.1`

The correct interpretation of this input should be:

1. 0.00000000003567
2. 489000i
3. -0.1

The general expression for a number that can either be a real or Imaginary can be written as follows:

```'RegEx for Real and Complex numbers
Dim Real As String = "(?<!([E][+-][0-9]+))([-]?\d+\.?\d*([E][+-]" & _
"[0-9]+)?(?!([i0-9.E]))|[-]?\d*\.?\d+([E][+-][0-9]+)?)(?![i0-9.E])"
Dim Img As String = "(?<!([E][+-][0-9]+))([-]?\d+\.?\d*([E][+-]" & _
"[0-9]+)?(?![0-9.E])(?:i)|([-]?\d*\.?\d+)?([E][+-][0-9]+)?\s*(?:i)(?![0-9.E]))"```

The two regular expressions can be broken down with the following assumptions:

1. A mathematical operator (+*/^) will preside and operators (-+*/^) follow the actual number.
2. A number can start with the operator "–" (we don't need the "+" operator as a number is positive by default
3. It is not a standalone number if it is preceded with the letter E or if it is immediately followed by E.

To separate the Real and imaginary numbers is just a small difference at the end. It is Real if the number is not preceded with the letter “i” and imaginary if it is. All the other code is taken in to force the regular expression to include the full number.

Complex numbers on the other hand usually come in pairs of real and imaginary, so we need to write a RegEx that understands this, and only parses the number as a standalone real or imaginary if it can’t find the pairs. The match will occur in the following pair with matches would be returned in this order:

1. a+bi, called Both
2. bi+a, called Both
3. a, called Real
4. bi, called Imag

The regular expression for defining a number type is given below:

```Dim NumType As String = "((?<Both>((" & Real & "\s*([+])*\s*" & Img & _
")|(" & Img & "\s*([+])*\s*" & Real & ")))|(?<Real>(" & _
Real & "))|(?<Imag>(" & Img & ")))"```

## Evaluator

The original evaluator is written by Francesco Balena in the book “Programming Microsoft Visual Basic .NET” (2003) Pages 505 – 509. It was basically a calculator that dealt with real numbers, and this code is altered to take complex numbers. The architecture is basically the same.

We begin with defining the different numbers that we will encounter:

```Dim NumType As String = "((?<Both>((" & Real & "\s*([+])*\s*" & Img & _
")|(" & Img & "\s*([+])*\s*" & Real & ")))|(?<Real>(" & _
Real & "))|(?<Imag>(" & Img & ")))"
Dim NumTypeSingle As String = "((?<Real>(" & Real & "))|(?<Imag>(" & Img & ")))"```

There are two different kinds, as we might encounter numbers that are written in the following way: 5+8i^2. This means that the `NumType `will read it as (5+8i)^2 with is wrong, therefore the need for `NumTypeSingle`.

Next, we define all the functions and operators that we will support with this evaluator:

```Const Func1 As String = "(exp|log|log10|abs|sqr|sqrt|sin|cos|tan|asin|acos|atan)"
' List of 2-operand functions.
Const Func2 As String = "(atan2)"
' List of N-operand functions.
Const FuncN As String = "(min|max)"

' List of predefined constants.
Const Constants As String = "(e|pi)"

Dim rePower As New Regex("\(?(?<No1>" & NumType & ")\)?" & _
"\s*(?<Operator>(\^))\s*\(?(?<No2>" & NumType & ")\)?")
Dim rePower2 As New Regex("\(?(?<No1>" & NumType & ")\)?" & _
"\s*(?<Operator>(\^))\s*(?<No2>" & NumTypeSingle & ")")
Dim rePowerSingle As New Regex("(?<No1>" & NumTypeSingle & ")" & _
"\s*(?<Operator>(\^))\s*(?<No2>" & NumTypeSingle & ")")
Dim rePowerSingle2 As New Regex("(?<No1>" & NumTypeSingle & ")" & _
"\s*(?<Operator>(\^))\s*\(?(?<No2>" & NumType & ")\)?")

Dim reMulDiv As New Regex("\(?\s*(?<No1>" & NumType & ")\)?" & _
"\s*(?<Operator>([*/]))\s*\(?(?<No2>" & NumType & ")\s*\)?\)?")
Dim reMulDiv2 As New Regex("\(?\s*(?<No1>" & NumType & ")\)?" & _
"\s*(?<Operator>([*/]))\s*(?<No2>" & NumTypeSingle & ")")
Dim reMulDivSingle As New Regex("\(?\s*(?<No1>" & NumTypeSingle & ")" & _
"\s*(?<Operator>([*/]))\s*(?<No2>" & NumTypeSingle & ")\s*\)?\)?")
Dim reMulDivSingle2 As New Regex("\(?\s*(?<No1>" & NumTypeSingle & ")" & _
"\s*(?<Operator>([*/]))\s*\(?(?<No2>" & NumType & ")\s*\)?")

Dim reAddSub As New Regex("\(?(?<No1>" & NumType & ")\)?" & -
"\s*(?<Operator>([-+]))\s*\(?(?<No2>" & NumType & ")\)?")

Dim reFunc1 As New Regex("\s*(?<Function>" & Func1 & ")\(?\s*" & _
"(?<No1>" & NumType & ")" & "\s*\)?", RegexOptions.IgnoreCase)
Dim reFunc2 As New Regex("\s*(?<Function>" & Func2 & ")\(\s*" & "(?<No1>" & _
NumType & ")" & "\s*,\s*" & "(?<No2>" & _
NumType & ")" & "\s*\)", RegexOptions.IgnoreCase)
Dim reFuncN As New Regex("\s*(?<Function>" & FuncN & ")\((?<Numbers>(\s*" & _
NumType & "\s*,)+\s*" & NumType & ")\s*\)", RegexOptions.IgnoreCase)
Dim reSign1 As New Regex("([-+/*^])\s*\+")

' This Regex object converts a double minus into a plus.
Dim reSign2 As New Regex("\-\s*\-")```

In a normal calculation with numbers, the Operators *,/,+,- ,( )and ^ have to be given different priorities in order to function properly.

1. ( ) This means calculate everything inside the () first, and when it can be defined as a "(" , complex number and ")", then move on. We will leave this out of the main evaluator for now.
2. Replace all constants in the input.
3. ^ If you have a match for "(" Complex number ")" ^ "(" Complex number ")", perform this task.
4. * and / Do * or / if you find "(" Complex number ")" ( * or /) "(" Complex number ")".
5. + and - Do + or - if you find "(" Complex number ")" ( + or -) "(" Complex number ")".

First, we replace all the constants with the actual numerical value (this only supports e and pi as the code is written):

``` ' The Regex object deals with constants. (Requires case insensitivity.)
Dim reConst As New Regex("\s*(?<Const>" & Constants & ")\s*")
' This resolves predefined constants. (Can be kept out of the loop.)

The actual calculation should be preformed as long as the input string cannot be recognized as a complex or real number.

The actual functions that do the arithmetic operations are written as follows:

```Function DoAddSub(ByVal m As Match) As String
Dim n1, n2 As New Complex()
n1 = GenerateComplexNumberFromString(m.Groups("No1").Value)
n2 = GenerateComplexNumberFromString(m.Groups("No2").Value)

Select Case m.Groups("Operator").Value
Case "+"
Dim f As New Complex
f = n1 + n2
Return String.Format(New ComplexFormatter(), "{0:I0}", f)
Case "-"
Dim f As New Complex
f = n1 - n2
Return String.Format(New ComplexFormatter(), "{0:I0}", f)
Case Else
Return 1
End Select
End Function
Function DoMulDiv(ByVal m As Match) As String
Dim n1, n2 As New Complex()
n1 = GenerateComplexNumberFromString(m.Groups("No1").Value)
n2 = GenerateComplexNumberFromString(m.Groups("No2").Value)
Select Case m.Groups("Operator").Value
Case "/"
Return String.Format(New ComplexFormatter(), "{0:I0}", (n1 / n2))
Case "*"
Return String.Format(New ComplexFormatter(), "{0:I0}", (n1 * n2))
Case Else
Return 1
End Select
End Function

Function DoPower(ByVal m As Match) As String
Dim n1, n2, n3 As New Complex()
n1 = GenerateComplexNumberFromString(m.Groups("No1").Value)
n2 = GenerateComplexNumberFromString(m.Groups("No2").Value)
n3 = Complex.Pow(n1, n2)
Dim s As String = String.Format(New ComplexFormatter(), "{0:I0}", n3)
Return "(" & s & ")"
End Function

Function DoFunc1(ByVal m As Match) As String
' function argument is 2nd group.
Dim n1 As New Complex
n1 = GenerateComplexNumberFromString(m.Groups("No1").Value)
' function name is 1st group.
Select Case m.Groups("Function").Value.ToUpper
Case "EXP"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Exp(n1))
Case "LOG"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Log(n1))
Case "LOG10"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Log10(n1))
Case "ABS"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Abs(n1))
Case "SQR", "SQRT"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Sqrt(n1))
Case "SIN"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Sin(n1))
Case "COS"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Cos(n1))
Case "TAN"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Tan(n1))
Case "ASIN"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Asin(n1))
Case "ACOS"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Acos(n1))
Case "ATAN"
Return String.Format(New ComplexFormatter(), "{0:I0}", Complex.Atan(n1))
Case Else
Return 1
End Select
End Function

Function DoFuncN(ByVal m As Match) As String
' function arguments are from group 2 onward.
Dim args As String() '
Dim args2 As New ArrayList
Dim i As Integer = 2
' Load all the arguments into the array.

For Each h As Capture In m.Groups("Numbers").Captures
args = h.ToString.Split(",")
Next

For Each Str As String In args
Next

'I cant sort complex numbers, you have a go ;)
' function name is 1st group.
Select Case m.Groups("Function").Value.ToUpper
Case "MIN"
args2.Sort()
Return String.Format(New ComplexFormatter(), "{0:I0}", args(0))
Case "MAX"
args2.Sort()
Return String.Format(New ComplexFormatter(), "{0:I0}", _
args(args.Count - 1)) 'args(args.Count - 1).ToString
Case Else
Return 1
End Select
End Function```

There are two things in the code that I did not mention yet. We need to cast the `string `to an actual complex number, and by default the `System.Numerics.Complex.ToString `returns (Real,Imaginary), and we don’t want it in that form. And second, we actually have to cast the matched `string `as a Complex number.

```Private Function GenerateComplexNumberFromString(ByVal input As String) As Complex
input = input.Replace(" ", "")

Dim Number As String = "((?<Real>(" & Real & "))|(?<Imag>(" & Img & ")))"
Dim Re, Im As Double
Re = 0
Im = 0

For Each Match As Match In Regex.Matches(input, Number)

If Not Match.Groups("Real").Value = String.Empty Then
Re = Double.Parse(Match.Groups("Real").Value, CultureInfo.InvariantCulture)
End If

If Not Match.Groups("Imag").Value = String.Empty Then
If Match.Groups("Imag").Value.ToString.Replace(" ", "") = "-i" Then
Im = Double.Parse("-1", CultureInfo.InvariantCulture)
ElseIf Match.Groups("Imag").Value.ToString.Replace(" ", "") = "i" Then
Im = Double.Parse("1", CultureInfo.InvariantCulture)
Else
Im = Double.Parse(Match.Groups("Imag").Value.ToString.Replace("i", ""), _
CultureInfo.InvariantCulture)
End If
End If
Next

Dim result As New Complex(Re, Im)
Return result
End Function```

The default complex `ToString `is overwritten, after an example from the Microsoft documentation:

```    Public Function Format(ByVal fmt As String, ByVal arg As Object,
ByVal provider As IFormatProvider) As String _
Implements ICustomFormatter.Format
If TypeOf arg Is Complex Then
Dim c1 As Complex = DirectCast(arg, Complex)
' Check if the format string has a precision specifier.
Dim precision As Integer
Dim fmtString As String = String.Empty
If fmt.Length > 1 Then
Try
precision = Int32.Parse(fmt.Substring(1))
Catch e As FormatException
precision = 0
End Try
fmtString = "N" + precision.ToString()
End If
If fmt.Substring(0, 1).Equals("I", StringComparison.OrdinalIgnoreCase) Then
Dim s As String = ""
If c1.Imaginary = 0 And c1.Real = 0 Then
s = "0"
ElseIf c1.Imaginary = 0 Then
s = c1.Real.ToString("r")
ElseIf c1.Real = 0 Then
s = c1.Imaginary.ToString("r") & "i"
Else
If c1.Imaginary >= 0 Then
s = [String].Format("{0}+{1}i", _
c1.Real.ToString("r"), _
c1.Imaginary.ToString("r"))
Else
s = [String].Format("{0}-{1}i", _
c1.Real.ToString("r"), _
Math.Abs(c1.Imaginary).ToString("r"))
End If
End If
Return s.Replace(",", ".")
ElseIf fmt.Substring(0, 1).Equals("J", _
StringComparison.OrdinalIgnoreCase) Then
Return c1.Real.ToString(fmtString) + " + " + _
c1.Imaginary.ToString(fmtString) + "j"
Else
Return c1.ToString(fmt, provider)
End If
Else
If TypeOf arg Is IFormattable Then
Return DirectCast(arg, IFormattable).ToString(fmt, provider)
ElseIf arg IsNot Nothing Then
Return arg.ToString()
Else
Return String.Empty
End If
End If
End Function
End Class```

## Bracket Evaluation

Evaluation should be done by calculating the inner most brackets first, replace the brackets with the evaluated result, and then evaluate the next bracket:

```Function EvaluateBrackets(ByVal input As String) As String
input = "(" & input & ")"
Dim pattern As String = "(?>\( (?<LEVEL>)(?<CURRENT>)| (?=\))(?" & _
"<LAST-CURRENT>)(?(?<=\(\k<LAST>)(?<-LEVEL> \)))|\[ (?<LEVEL>)(?" & _
"<CURRENT>)|(?=\])(?<LAST-CURRENT>)(?(?<=\[\k<LAST>)" & _
"(?<-LEVEL> \] ))|[^()\[\]]*)+(?(LEVEL)(?!))"
Dim MAtchBracets As MatchCollection = _
Regex.Matches(input, pattern, RegexOptions.IgnorePatternWhitespace)
Dim captures As CaptureCollection = MAtchBracets(0).Groups("LAST").Captures
Dim ListOfPara As New List(Of String)
For Each c As Capture In captures
Next
Dim result As String = input
Dim CalcList As New List(Of String)
For i As Integer = 0 To ListOfPara.Count - 1
If i = 0 Then
result = CalcList(i)
Else
For j As Integer = i To ListOfPara.Count - 1
ListOfPara(j) = ListOfPara(j).Replace(ListOfPara(i - 1), _
CalcList(i - 1)).Replace(" ", "")
Next
result = Evaluate(ListOfPara(i)).Replace(" ", "")
End If
Next
result = Evaluate(ListOfPara(ListOfPara.Count - 1))
Return result
End Function```

The Regular Expression is originally written by Morten Holk Maate, and it is an example of Balanced grouping, one of the more difficult aspects in RegEx.

## History

This evaluator is basically a modified version of the real number evaluator in: Programming Microsoft Visual Basic .NET (2003) - Francesco Balena (pages 505 - 509.)

With thanks to the publishers for permission to publish the modified source code from the book.

The balanced group regex is from Morten Holk Maate.

## Share

 Engineer Norway
I hope that you like the stuff I have created and if you do wish to say thank you then a donation is always appreciated.
You can donate here[^].

## You may also be interested in...

 Pro Pro

 First Prev Next
 My vote of 5 Vinod Viswanath1-Sep-12 17:10 Vinod Viswanath 1-Sep-12 17:10
 Re: My vote of 5 Kenneth Haugland2-Sep-12 9:49 Kenneth Haugland 2-Sep-12 9:49
 nice CIDev14-Jun-11 3:37 CIDev 14-Jun-11 3:37
 Re: nice Kenneth Haugland2-Sep-12 9:49 Kenneth Haugland 2-Sep-12 9:49
 Re: nice CIDev3-Sep-12 4:28 CIDev 3-Sep-12 4:28
 Last Visit: 31-Dec-99 18:00     Last Update: 21-Oct-16 14:33 Refresh 1