65.9K
CodeProject 正在变化。 阅读更多。
Home

评估复杂和实数计算器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (15投票s)

2011年5月21日

Ms-PL

5分钟阅读

viewsIcon

34798

downloadIcon

823

从字符串评估复数和实数。

ComplexEvaluator.png

引言

在 .NET 4.0 中,原生库在 System.Numerics 命名空间中提供了一个复数类。理想的情况是像 Matlab 或 Matematica 那样编写公式。

我尝试的解决方案是使用正则表达式,将公式写成一个简单的 string,然后将 string 转换为一系列复数,进行计算并输出计算结果。

该评估器比仅仅对复数进行计算更为通用。它也可以作为普通计算器,仅处理实数。

用于复数和实数的正则表达式

识别用户输入的模式始终是 a+bi 的形式,并定义为我们需要识别的复数模式。写入复数基本上有三种主要方式:

  1. a+bi 包含实部和虚部
  2. a 仅包含实部
  3. bi 仅包含虚部

a 和 b 都是实数(在原生 .NET 中,它们可以是 Double、Decimal 或 Integer 类型)。可以在网上找到用于双精度数的正则表达式,但很少能找到一个通用的正则表达式来识别连续的、由数学运算符分隔的通用计算机数字。一个简单的输入示例可以说明识别计算机数字格式主要模式所需的内容:

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

此输入的正确解释应为:

  1. 0.00000000003567
  2. 489000i
  3. -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]))"

可以使用以下假设分解两个正则表达式:

  1. 数学运算符(+*/^)将优先出现,运算符(-+*/^)跟在实际数字后面。
  2. 数字可以以运算符“–”开头(我们不需要“+”运算符,因为数字默认是正的)。
  3. 如果数字前面是字母 E,或者后面紧跟着 E,则它不是一个独立的数字。

区分实数和虚数仅在结尾处有一点不同。如果数字前面没有字母“i”,则它是实数;如果前面有“i”,则它是虚数。所有其他代码都用于强制正则表达式包含完整的数字。

另一方面,复数通常成对出现(实部和虚部),所以我们需要编写一个能够理解这一点的正则表达式,并且仅当找不到成对出现时,才将数字解析为独立的实数或虚数。匹配将按以下顺序出现,并按此顺序返回匹配项:

  1. a+bi,称为“Both”
  2. bi+a,称为“Both”
  3. a,称为“Real”
  4. 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*\-")

在正常的数字计算中,运算符 *、/、+、-、( )和 ^ 必须具有不同的优先级才能正常工作。

  1. ( ) 这意味着先计算 () 中的所有内容,并且当它可以被定义为 "("、复数和 ")" 时,再继续。我们暂时将此排除在主评估器之外。
  2. 替换输入中的所有常量。
  3. ^ 如果匹配到 "(" 复数 ")" ^ "(" 复数 ")",则执行此任务。
  4. * 和 / 如果找到 "(" 复数 ")" (* 或 /) "(" 复数 ")",则执行 * 或 /。
  5. + 和 - 如果找到 "(" 复数 ")" (+ 或 -) "(" 复数 ")",则执行 + 或 -。

首先,我们将所有常量替换为实际的数值(根据代码的写法,这只支持 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

© . All rights reserved.