评估复杂和实数计算器






4.65/5 (15投票s)
从字符串评估复数和实数。

引言
在 .NET 4.0 中,原生库在 System.Numerics 命名空间中提供了一个复数类。理想的情况是像 Matlab 或 Matematica 那样编写公式。
我尝试的解决方案是使用正则表达式,将公式写成一个简单的 string
,然后将 string
转换为一系列复数,进行计算并输出计算结果。
该评估器比仅仅对复数进行计算更为通用。它也可以作为普通计算器,仅处理实数。
用于复数和实数的正则表达式
识别用户输入的模式始终是 a+bi 的形式,并定义为我们需要识别的复数模式。写入复数基本上有三种主要方式:
- a+bi 包含实部和虚部
- a 仅包含实部
- bi 仅包含虚部
a 和 b 都是实数(在原生 .NET 中,它们可以是 Double、Decimal 或 Integer 类型)。可以在网上找到用于双精度数的正则表达式,但很少能找到一个通用的正则表达式来识别连续的、由数学运算符分隔的通用计算机数字。一个简单的输入示例可以说明识别计算机数字格式主要模式所需的内容:
3.567E-10+4.89E+5i-.1
此输入的正确解释应为:
- 0.00000000003567
- 489000i
- -0.1
可以表示实数或虚数的通用数字表达式可写为:
'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]))"
可以使用以下假设分解两个正则表达式:
- 数学运算符(+*/^)将优先出现,运算符(-+*/^)跟在实际数字后面。
- 数字可以以运算符“–”开头(我们不需要“+”运算符,因为数字默认是正的)。
- 如果数字前面是字母 E,或者后面紧跟着 E,则它不是一个独立的数字。
区分实数和虚数仅在结尾处有一点不同。如果数字前面没有字母“i”,则它是实数;如果前面有“i”,则它是虚数。所有其他代码都用于强制正则表达式包含完整的数字。
另一方面,复数通常成对出现(实部和虚部),所以我们需要编写一个能够理解这一点的正则表达式,并且仅当找不到成对出现时,才将数字解析为独立的实数或虚数。匹配将按以下顺序出现,并按此顺序返回匹配项:
- a+bi,称为“Both”
- bi+a,称为“Both”
- a,称为“Real”
- bi,称为“Imag”
定义数字类型的正则表达式如下所示:
Dim NumType As String = "((?<Both>((" & Real & "\s*([+])*\s*" & Img & _
")|(" & Img & "\s*([+])*\s*" & Real & ")))|(?<Real>(" & _
Real & "))|(?<Imag>(" & Img & ")))"
评估器
原始评估器由 Francesco Balena 在《Programming Microsoft Visual Basic .NET》一书(2003 年)第 505-509 页中编写。它基本上是一个处理实数的计算器,此代码已修改为处理复数。其架构基本相同。
我们首先定义将要遇到的不同数字类型:
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 & ")))"
有两种不同类型,因为我们可能会遇到形如 5+8i^2 的数字。这意味着 NumType
会将其读取为 (5+8i)^2,这是错误的,因此需要 NumTypeSingle
。
接下来,我们定义此评估器将支持的所有函数和运算符:
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*\-")
在正常的数字计算中,运算符 *、/、+、-、( )和 ^ 必须具有不同的优先级才能正常工作。
- ( ) 这意味着先计算 () 中的所有内容,并且当它可以被定义为 "("、复数和 ")" 时,再继续。我们暂时将此排除在主评估器之外。
- 替换输入中的所有常量。
- ^ 如果匹配到 "(" 复数 ")" ^ "(" 复数 ")",则执行此任务。
- * 和 / 如果找到 "(" 复数 ")" (* 或 /) "(" 复数 ")",则执行 * 或 /。
- + 和 - 如果找到 "(" 复数 ")" (+ 或 -) "(" 复数 ")",则执行 + 或 -。
首先,我们将所有常量替换为实际的数值(根据代码的写法,这只支持 e 和 pi)。
' 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.)
Input = reConst.Replace(Input, AddressOf DoConstants)
只要输入字符串不能被识别为复数或实数,就应该执行实际的计算。
执行算术运算的实际函数如下所示:
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
args2.Add(GenerateComplexNumberFromString(Str.Replace(","c, " "c)))
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
代码中还有两件事我还没有提到。我们需要将 string
转换为实际的复数,并且默认情况下 System.Numerics.Complex.ToString
返回 (Real,Imaginary),而我们不想要这种格式。其次,我们实际上必须将匹配的 string
强制转换为 Complex 类型。
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
在 Microsoft 文档中的示例之后,默认的复数 ToString
被重写了。
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
括号求值
求值应通过先计算最内层的括号,然后用求值结果替换括号,最后再计算下一个括号来完成。
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
ListOfPara.Add(c.Value)
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
CalcList.Add(Evaluate(ListOfPara(i)))
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(" ", "")
CalcList.Add(result)
End If
Next
result = Evaluate(ListOfPara(ListOfPara.Count - 1))
Return result
End Function
该正则表达式最初由 Morten Holk Maate 编写,是平衡分组的一个例子,这是正则表达式中较难的部分之一。
历史
此评估器基本上是《Programming Microsoft Visual Basic .NET》(2003 年)- Francesco Balena(第 505-509 页)中实数评估器的修改版本。
感谢出版商允许发布书中修改后的源代码。
平衡分组的正则表达式来自 Morten Holk Maate。