0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Excel VBAでDKA法(Durand-Kerner-Aberth法)

Last updated at Posted at 2024-09-16

$ n $次多項式

f(z) = c_0 z^n + c_1 z^{n-1} + \cdots + c_{n-1} z + c_n

の係数$ c_0, c_1, \dots, c_n$を引数として与えた場合に、その$ n $個の根――すなわち

f(z) = c_0 \prod_{k=1}^n (z-\alpha_k) = 0

を満たす$ \alpha_0, \alpha_1, \dots, \alpha_n$を得るVBAプログラムを次のとおり自作した。

Classモジュール

複素数を表すClassモジュール

complex.cls
Option Explicit

Private Const strClassName As String = "Complex"

'============================
'プロパティ
'============================
Public re As Double '実部
Public im As Double '虚部

'=============================
'コンストラクタ(初期化処理)
'=============================
Private Sub Class_Initialize()
  re = 0
  im = 0
End Sub

'=============================
'複素数の定義(実部と虚部) a+bi
'=============================
Public Function SetC(a As Double, b As Double)
  re = a
  im = b
End Function

'=============================
'複素数の定義(絶対値と偏角) re^{iΘ}
'=============================
Public Function SetC_RT(r As Double, theta As Double)
  re = r * Cos(theta)
  im = r * Sin(theta)
End Function

'=============================
'複素数の定義(実部と虚部の文字列)
'=============================
Public Function SetC_str(strComplex As String)
  re = WorksheetFunction.ImReal(strComplex)
  im = WorksheetFunction.Imaginary(strComplex)
End Function

'=============================
'絶対値 |a| = a.AbsC()
'=============================
Public Function AbsC() As Double
  AbsC = Sqr(re ^ 2 + im ^ 2)
End Function

'=============================
'絶対値 |a| = a.AbsCSq()
'=============================
Public Function AbsCSq() As Double
  AbsCSq = re ^ 2 + im ^ 2
End Function
 
'=============================
'偏角 ∠a = a.Angle()
'=============================
Public Function Angle() As Double
  If re ^ 2 + im ^ 2 = 0 Then
    Angle = 0
  Else
    Angle = WorksheetFunction.Atan2(re, im)
  End If
End Function

'=============================
'足し算 a+b = a.Plus(b)
'=============================
Public Function Plus(ByVal b As Variant) As Variant
  If (2 <= VarType(b) And VarType(b) <= 6) Then
    Set Plus = New Complex
    Plus.re = re + b
    Plus.im = im
  ElseIf (VarType(b) = 9 And TypeName(b) = strClassName) Then
    Set Plus = New Complex
    Plus.re = re + b.re
    Plus.im = im + b.im
  Else
    Plus = CVErr(xlErrNum)
  End If
End Function

'=============================
'引き算 a-b = a.Minus(b)
'=============================
Public Function Minus(ByVal b As Variant) As Variant
  If (2 <= VarType(b) And VarType(b) <= 6) Then
    Set Minus = New Complex
    Minus.re = re - b
    Minus.im = im
  ElseIf (VarType(b) = 9 And TypeName(b) = strClassName) Then
    Set Minus = New Complex
    Minus.re = re - b.re
    Minus.im = im - b.im
  Else
    Minus = CVErr(xlErrNum)
  End If
End Function

'=============================
'掛け算 a*b = a.Times(b)
'=============================
Public Function Times(ByVal b As Variant) As Variant
  If (2 <= VarType(b) And VarType(b) <= 6) Then
    Set Times = New Complex
    Times.re = re * b
    Times.im = im * b
  ElseIf (VarType(b) = 9 And TypeName(b) = strClassName) Then
    Set Times = New Complex
    Times.re = re * b.re - im * b.im
    Times.im = re * b.im + im * b.re
  Else
    Times = CVErr(xlErrNum)
  End If
End Function

'============================
'割り算 a/b = a.Div(b)
'============================
Public Function Div(ByVal b As Variant) As Variant
  If (2 <= VarType(b) And VarType(b) <= 6) Then
    If b = 0 Then
      Div = CVErr(xlErrNum)
    Else
      Set Div = New Complex
      Div.re = re / b
      Div.im = im / b
    End If
  ElseIf (VarType(b) = 9 And TypeName(b) = strClassName) Then
    If b.AbsC() = 0 Then
      Div = CVErr(xlErrNum)
    Else
      Set Div = New Complex
      Div.re = (re * b.re + im * b.im) / (b.AbsC() ^ 2)
      Div.im = (-re * b.im + im * b.re) / (b.AbsC() ^ 2)
    End If
  Else
    Div = CVErr(xlErrNum)
  End If
End Function

'============================
'共役複素数
'============================
Public Function CompCnj() As Complex
  Set CompCnj = New Complex
  CompCnj.re = re
  CompCnj.im = (-1) * im
End Function

'============================
'文字列(直交座標)
'============================
Public Function CompStr() As String
  If re = 0 Then
    If im = 0 Then
      CompStr = "0"
    ElseIf im > 0 Then
      If im = 1 Then
        CompStr = "i"
      Else
        CompStr = Str(im) & "i"
      End If
    Else
      If im = -1 Then
        CompStr = "-i"
      Else
        CompStr = "-" & Str(Abs(im)) & "i"
      End If
    End If
  ElseIf re > 0 Then
    If im = 0 Then
      CompStr = Str(re)
    ElseIf im > 0 Then
      If im = 1 Then
        CompStr = Str(re) & "+i"
      Else
        CompStr = Str(re) & "+" & Str(im) & "i"
      End If
    Else
      If im = -1 Then
        CompStr = Str(re) & "-i"
      Else
        CompStr = Str(re) & Str(im) & "i"
      End If
    End If
  Else
    If im = 0 Then
      CompStr = "-" & Str(Abs(re))
    ElseIf im > 0 Then
      If im = 1 Then
        CompStr = "-" & Str(Abs(re)) & "+i"
      Else
        CompStr = "-" & Str(Abs(re)) & "+" & Str(im) & "i"
      End If
    Else
      If im = -1 Then
        CompStr = "-" & Str(Abs(re)) & "-i"
      Else
        CompStr = "-" & Str(Abs(re)) & "-" & Str(Abs(im)) & "i"
      End If
    End If
  End If
  CompStr = Replace(CompStr, " ", "")
End Function

'============================
'文字列(直交座標,極座標cossin,極座標rExpΘ)
'============================
Public Function CompStrPolar() As String
  Dim dblAbs As Double
  Dim dblAngle As Double
  dblAbs = Sqr(re ^ 2 + im ^ 2)
  If re ^ 2 + im ^ 2 = 0 Then
    dblAngle = 0
  Else
    dblAngle = WorksheetFunction.Atan2(re, im)
  End If
  
  CompStrPolar = Str(dblAbs) & " Exp(" & Str(dblAngle) & "i)"
End Function

標準モジュール

各種計算用の函数を定義する標準モジュール

ComplexFunc.bas
Option Explicit

Private Const strTypeComplex As String = "Complex"
Private Const cnstDblEpsilon As Double = 10 ^ (-12) 'ニュートン法の収束基準
Private Const PI As Double = 3.14159265358979 '円周率

'=============================
'Sum函数
'=============================
Public Function SumComp(ParamArray varArray()) As Variant
  Dim cnt As Long
  Dim cmpSum As Complex: Set cmpSum = New Complex
  
  For cnt = LBound(varArray) To UBound(varArray)
    If (2 <= VarType(varArray(cnt)) And VarType(varArray(cnt)) <= 6) Then
      cmpSum.re = cmpSum.re + varArray(cnt)
      cmpSum.im = cmpSum.im + 0
    ElseIf (VarType(varArray(cnt)) = 9 And TypeName(varArray(cnt)) = strTypeComplex) Then
      cmpSum.re = cmpSum.re + varArray(cnt).re
      cmpSum.im = cmpSum.im + varArray(cnt).im
    Else
      SumComp = CVErr(xlErrNum)
      Exit Function
    End If
  Next cnt

  Set SumComp = cmpSum
End Function

'=============================
'Sub函数(第1引数を被減数として、第2引数以降で順次減じていく)
'=============================
Public Function SubComp(ParamArray varArray()) As Variant
  Dim cnt As Long
  Dim dblTmp As Double
  Dim cmpSub As Complex: Set cmpSub = New Complex
  Dim dblSq As Double
  For cnt = LBound(varArray) To UBound(varArray)
    If (2 <= VarType(varArray(cnt)) And VarType(varArray(cnt)) <= 6) Then
      If cnt = LBound(varArray) Then
        cmpSub.re = varArray(cnt)
        cmpSub.im = 0
      Else
        cmpSub.re = cmpSub.re - varArray(cnt)
      End If
    ElseIf (VarType(varArray(cnt)) = 9 And TypeName(varArray(cnt)) = strTypeComplex) Then
      If cnt = LBound(varArray) Then
        cmpSub.re = varArray(cnt).re
        cmpSub.im = varArray(cnt).im
      Else
        cmpSub.re = cmpSub.re - varArray(cnt).re
        cmpSub.im = cmpSub.im - varArray(cnt).im
      End If
    Else
      SubComp = CVErr(xlErrNum)
      Exit Function
    End If
  Next cnt
  Set SubComp = cmpSub
End Function

'=============================
'Product函数
'=============================
Public Function ProductComp(ParamArray varArray()) As Variant
  Dim cnt As Long
  Dim dblTmp As Double
  Dim cmpProduct As Complex: Set cmpProduct = New Complex
  
  For cnt = LBound(varArray) To UBound(varArray)
    If (2 <= VarType(varArray(cnt)) And VarType(varArray(cnt)) <= 6) Then
      If cnt = LBound(varArray) Then
        cmpProduct.re = varArray(cnt)
        cmpProduct.im = 0
      Else
        dblTmp = cmpProduct.re * varArray(cnt) - cmpProduct.im * 0
        cmpProduct.im = cmpProduct.re * 0 + cmpProduct.im * varArray(cnt)
        cmpProduct.re = dblTmp
      End If
    ElseIf (VarType(varArray(cnt)) = 9 And TypeName(varArray(cnt)) = strTypeComplex) Then
      If cnt = LBound(varArray) Then
        cmpProduct.re = varArray(cnt).re
        cmpProduct.im = varArray(cnt).im
      Else
        dblTmp = cmpProduct.re * varArray(cnt).re - cmpProduct.im * varArray(cnt).im
        cmpProduct.im = cmpProduct.re * varArray(cnt).im + cmpProduct.im * varArray(cnt).re
        cmpProduct.re = dblTmp
      End If
    Else
      ProductComp = CVErr(xlErrNum)
      Exit Function
    End If
  Next cnt

  Set ProductComp = cmpProduct
End Function

'=============================
'Div函数(第1引数を被除数として、第2引数以降で順次除していく)
'=============================
Public Function DivComp(ParamArray varArray()) As Variant
  Dim cnt As Long
  Dim dblTmp As Double
  Dim cmpDiv As Complex: Set cmpDiv = New Complex
  Dim dblSq As Double
  
  For cnt = LBound(varArray) To UBound(varArray)
    If (2 <= VarType(varArray(cnt)) And VarType(varArray(cnt)) <= 6) Then
      If cnt = LBound(varArray) Then
        cmpDiv.re = varArray(cnt)
        cmpDiv.im = 0
      Else
        If varArray(cnt) = 0 Then
          DivComp = CVErr(xlErrNum)
          Exit Function
        Else
          dblTmp = cmpDiv.re / varArray(cnt)
          cmpDiv.im = cmpDiv.im / varArray(cnt)
          cmpDiv.re = dblTmp
        End If
      End If
    ElseIf (VarType(varArray(cnt)) = 9 And TypeName(varArray(cnt)) = strTypeComplex) Then
      If cnt = LBound(varArray) Then
        cmpDiv.re = varArray(cnt).re
        cmpDiv.im = varArray(cnt).im
      Else
        dblSq = varArray(cnt).re ^ 2 + varArray(cnt).im ^ 2
        If dblSq = 0 Then
          DivComp = CVErr(xlErrNum)
          Exit Function
        Else
          dblTmp = cmpDiv.re * varArray(cnt).re + cmpDiv.im * varArray(cnt).im
          cmpDiv.im = cmpDiv.im * varArray(cnt).re - cmpDiv.re * varArray(cnt).im
          cmpDiv.re = dblTmp / dblSq
          cmpDiv.im = cmpDiv.im / dblSq
        End If
      End If
    Else
      DivComp = CVErr(xlErrNum)
      Exit Function
    End If
  Next cnt

  Set DivComp = cmpDiv
End Function

'=============================
'Power函数。ただし指数は整数に限る
'=============================
Public Function PowerComp(ByVal varBase As Variant, ByVal varExp As Variant) As Variant
  Dim cnt As Long
  Dim dblTmp As Double
  Dim cmpPower As Complex: Set cmpPower = New Complex

  If (2 <= VarType(varExp) And VarType(varExp) <= 6) And (varExp = Int(varExp)) Then
    If varExp = 0 Then
      Call cmpPower.SetC(1, 0)
    Else
      Call cmpPower.SetC(1, 0)
      For cnt = 1 To Abs(varExp)
        Set cmpPower = ProductComp(cmpPower, varBase)
      Next cnt
      If varExp < 0 Then
        Set cmpPower = DivComp(1, cmpPower)
      End If
    End If
    Set PowerComp = cmpPower
  Else
    Debug.Print "error"
    Exit Function
  End If
End Function

'=============================
'絶対値
'=============================
Public Function AbsComp(varParam As Variant) As Variant
  If (2 <= VarType(varParam) And VarType(varParam) <= 6) Then
    AbsComp = Abs(varParam)
  ElseIf (VarType(varParam) = 9 And TypeName(varParam) = strTypeComplex) Then
    AbsComp = varParam.AbsC
  Else
  End If
End Function

'=============================
'多項式 f(z)=c_0 z^n + c_1 z^(n-1) + … + c_{n-2} z^2 + c_{n-1} z + c_n
'の係数c_0, c_1, ..., c_n を与えると
'その根α_1, α_2, ..., α_n を返す函数
'=============================
Public Function GetRoots(ByRef varRoots As Variant, ParamArray varCoeff() As Variant) As Boolean
  Dim cnt As Long
  Dim varTmp As Variant
  Dim compCoeff As Variant

  If UBound(varCoeff) = -1 Then
    GetRoots = False
    Exit Function
  ElseIf UBound(varCoeff) = 0 Then
    If UBound(varCoeff(0)) <= 0 Then
      GetRoots = False
      Exit Function
    Else
      If GetRoots2(varRoots, varCoeff(0)) Then
        GetRoots = True
      End If
    End If
  Else
    ReDim compCoeff(UBound(varCoeff))
    For cnt = LBound(varCoeff) To UBound(varCoeff)
      Set compCoeff(cnt) = New Complex
      Set compCoeff(cnt) = SumComp(varCoeff(cnt), 0)
    Next cnt
    GetRoots = True
    If GetRoots2(varRoots, compCoeff) Then
      GetRoots = True
    End If
  End If
End Function

'=============================
'多項式 f(z)=c_0 z^n + c_1 z^(n-1) + … + c_{n-2} z^2 + c_{n-1} z + c_n
'の係数c_0, c_1, ..., c_n を与えると
'その根α_1, α_2, ..., α_n をByRef引数に代入する
'根を得られればTrueを返し、得られなければFalseを返す
'=============================
Public Function GetRoots2(ByRef varRoots As Variant, ByVal varCoeff As Variant) As Boolean
  Dim cnt As Long, cnt2 As Long, cnt3 As Long, cnt4 As Long
  Dim varTmp As Variant
  Dim compCoeff As Variant '与えられたvarCoeffがComplex型とは限らないので、引数をComplex型として格納する変数
  Dim compCoeff2 As Variant, varRoots2 As Variant
  Dim compRoots01 As Variant 'ニュートン法を適用する根 α(k)_i
  Dim compRoots02 As Variant 'ニュートン法を適用する根 α(k+1)_i
  Dim compRoots11 As Variant 'f(z) = c_0(z-α_1)(z-α_2)・・・(z-α_n)
  Dim compRoots12 As Variant 'f'(z) = c_0 Π(α_i-α_j)  (i≠j)
  Dim compTemp As Complex: Set compTemp = New Complex
  Dim varError As Variant 'ニュートン法を適用する根の差分の絶対値 ε_i = |α(k)_i - α(k+1)_i|
  Dim dblError As Double 'ニュートン法を適用する根の差分の合計 Σε_i
  Dim dblEpsilon As Double: dblEpsilon = cnstDblEpsilon

'  On Error GoTo ErrLabal

  ' varCoeffの型を確認
  ' 配列型でなければ
  If VarType(varCoeff) < vbArray Then
    '引数として与えられた係数の配列が不適切なので
    '処理結果をFlaseとする
    GetRoots2 = False
  Else
    '配列の要素数が0から数えて0なら
    If UBound(varCoeff) < 1 Then
      '1次式ですらないので
      '処理結果をFlaseとする
      GetRoots2 = False
    Else
      ReDim compCoeff(UBound(varCoeff))
      '配列の要素の型に想定外のものがないか確認
      For cnt = LBound(varCoeff) To UBound(varCoeff)
        Select Case VarType(varCoeff(cnt))
          '実数型なら
          Case vbByte, vbLong, vbSingle, vbDouble, vbCurrency
            Set compCoeff(cnt) = New Complex
            Call compCoeff(cnt).SetC(varCoeff(cnt), 0)
          '文字列型なら
          Case vbString
            Set compCoeff(cnt) = New Complex
            Call compCoeff(cnt).SetC_str(varCoeff(cnt))
          'オブジェクト型なら
          Case vbObject
            'Complex型なら
            If TypeName(varCoeff(cnt)) = strTypeComplex Then
              Set compCoeff(cnt) = New Complex
              Set compCoeff(cnt) = varCoeff(cnt)
            End If
          'それ以外なら
          Case Else
            '想定外の配列が存在したので
            '処理結果をFlaseとする
            GetRoots2 = False
            Exit Function
        End Select
      Next cnt
    End If

    '根を格納する変数の配列数を再定義し、各要素をComplex型とする
    ReDim compRoots01(UBound(compCoeff) - 1)
    For cnt = LBound(compCoeff) To UBound(compCoeff) - 1
      Set compRoots01(cnt) = New Complex
    Next cnt

    Select Case UBound(compCoeff)
      '係数配列の要素数が0から数えて1なら
      Case 1
        '1次方程式なのでニュートン法を使うまでもなく
        Set compRoots01(0) = ProductComp(DivComp(compCoeff(1), compCoeff(0)), -1)
        GetRoots2 = compRoots01
      '係数配列の要素数が0から数えて2以上なら
      Case Else
        'まずはn次方程式がn重根かどうか確認
        If IsNFoldedRoots(compCoeff, compTemp) Then
          For cnt = LBound(compCoeff) To UBound(compCoeff) - 1
            Set compRoots01(cnt) = compTemp
          Next cnt
          varRoots = compRoots01
        'n重根でなければ
        Else
          'Aberthの初期値を代入
          compRoots01 = GetAberthInitial(compCoeff)
          'compRoots01以外の配列数を再定義(compRoots01はCase 1(1次方程式)の場合も使用するので、Selectに入る前に再定義済み)
          ReDim compRoots02(UBound(compCoeff) - 1)
          ReDim compRoots11(UBound(compCoeff) - 1)
          ReDim compRoots12(UBound(compCoeff) - 1)
          ReDim varError(UBound(compCoeff) - 1)
          For cnt = LBound(compCoeff) To UBound(compCoeff) - 1
            Set compRoots02(cnt) = New Complex
            Set compRoots11(cnt) = New Complex
            Set compRoots12(cnt) = New Complex
            varError(cnt) = CDbl(0)
          Next cnt
          'ループ処理
          Do
            'ループ回数を数える
            cnt3 = cnt3 + 1
            dblError = 0
            For cnt = LBound(compCoeff) To UBound(compCoeff) - 1
              Call compRoots11(cnt).SetC(0, 0)
              For cnt2 = LBound(compCoeff) To UBound(compCoeff)
                Set compRoots11(cnt) = SumComp(compRoots11(cnt), ProductComp(compCoeff(cnt2), PowerComp(compRoots01(cnt), UBound(compCoeff) - cnt2)))
              Next cnt2
            Next cnt
            For cnt = LBound(compCoeff) To UBound(compCoeff) - 1
              Set compRoots12(cnt) = compCoeff(0)
              For cnt2 = LBound(compCoeff) To UBound(compCoeff) - 1
                If cnt2 <> cnt Then Set compRoots12(cnt) = ProductComp(compRoots12(cnt), SumComp(compRoots01(cnt), ProductComp(-1, compRoots01(cnt2))))
              Next cnt2
              Set compRoots02(cnt) = SumComp(compRoots01(cnt), ProductComp(-1, DivComp(compRoots11(cnt), compRoots12(cnt))))
            Next cnt
            'ループ処理の繰り返しによる根の収束の確認
            For cnt = LBound(compCoeff) To UBound(compCoeff) - 1
              'ループ処理cnt3-1回目の根とループ処理cnt3回目の根との差分を取る
              Set compTemp = SubComp(compRoots01(cnt), compRoots02(cnt))
              '差分の絶対値
              varError(cnt) = compTemp.AbsC
              'n個の根について差分を合計
              dblError = dblError + varError(cnt)
              'ループ処理cnt3-1回目の根を削除して、そこへループ処理cnt3回目の根を代入
              Set compRoots01(cnt) = compRoots02(cnt)
            Next cnt
            '反復処理回数が多い場合の処理
            If cnt3 Mod 100 = 0 Then
              For cnt = LBound(varError) To UBound(varError)
                If varError(cnt) < dblEpsilon Then
                  compRoots01(cnt).re = Round(compRoots01(cnt).re, 12)
                  compRoots01(cnt).im = Round(compRoots01(cnt).im, 12)
                  Call PolyN2Nminus1(compRoots01(cnt), compCoeff, compCoeff2)
                  If GetRoots2(varRoots2, compCoeff2) Then
                    For cnt2 = LBound(varError) To UBound(varError)
                      If cnt2 <> cnt Then
                        Set compRoots01(cnt2) = varRoots2(cnt4)
                        cnt4 = cnt4 + 1
                      End If
                    Next cnt2
                    varRoots = compRoots01
                    GetRoots2 = True
                    Exit Function
                  End If
                End If
              Next cnt
              If cnt3 > 500 Then
                Debug.Print "Error: The number of loops exceeded the specified number."
                Call RootsRounding(compRoots01, compCoeff)
                varRoots = compRoots01
                GetRoots2 = False
                Exit Function
              End If
              dblEpsilon = dblEpsilon * 10
            End If
          Loop While dblError > dblEpsilon
          Call RootsRounding(compRoots01, compCoeff)
          varRoots = compRoots01
        End If
    End Select

    '処理結果をTrueとする
    GetRoots2 = True
  End If

  'エラー処理に進入しないようExit
  Exit Function
'
'ErrLabal:
'  Debug.Print "An error occurred."
'  GetRoots2 = False

End Function
'=============================
'Aberthの初期値の半径
'http://nalab.mind.meiji.ac.jp/~mk/lecture/ouyoukaiseki4/notebook/nonlinear-algebra/node40.html
'=============================
Private Function GetAberthRadius(ByVal varCoeff As Variant) As Variant
  Dim cnt As Long
  Dim varTmp As Variant
  Dim dblTmp As Double
  ReDim varTmp(UBound(varCoeff))
  For cnt = LBound(varCoeff) To UBound(varCoeff)
    Set varTmp(cnt) = DivComp(varCoeff(cnt), varCoeff(LBound(varCoeff)))
    If dblTmp < varTmp(cnt).AbsC Then dblTmp = varTmp(cnt).AbsC
  Next cnt
  GetAberthRadius = varTmp(1).AbsC / (UBound(varCoeff) - 1) + 1 + dblTmp
End Function

'=============================
'Aberthの初期値
'http://nalab.mind.meiji.ac.jp/~mk/lecture/ouyoukaiseki4/notebook/nonlinear-algebra/node40.html
'=============================
Private Function GetAberthInitial(ByVal varCoeff As Variant) As Variant
  Dim cnt As Long
  Dim varTmp As Variant
  Dim compTmp As Complex: Set compTmp = New Complex
  Dim dblRadius As Double
  dblRadius = GetAberthRadius(varCoeff)
  ReDim varTmp(UBound(varCoeff) - 1)
  For cnt = LBound(varCoeff) To UBound(varCoeff) - 1
    Call compTmp.SetC_RT(dblRadius, (2 * cnt + 0.5) * PI / UBound(varCoeff))
    ' ↑Aberthの初期値、↓伊理の初期値
'    Call compTmp.SetC_RT(dblRadius, (2 * PI * cnt + 1.5) / UBound(varCoeff))
    Set compTmp = SumComp(compTmp, DivComp(ProductComp(-1, varCoeff(1)), ProductComp(varCoeff(0), UBound(varCoeff))))
    Set varTmp(cnt) = compTmp
    Set compTmp = New Complex
  Next cnt
  GetAberthInitial = varTmp
End Function

'=============================
' 複素数Classの配列を、実数のものだけ実数型に置き換える
'=============================
Private Sub IfRealThenComp2Real(ByRef varBf, ByRef varAf)
  If Not (LBound(varBf) = LBound(varAf) And UBound(varBf) = UBound(varAf)) Then
    Exit Sub
  End If
  Dim cnt As Long
  For cnt = LBound(varBf) To UBound(varBf)
    If (2 <= VarType(varBf(cnt)) And VarType(varBf(cnt)) <= 6) Then
      varAf(cnt) = varBf(cnt)
    ElseIf (VarType(varBf(cnt)) = 9 And TypeName(varBf(cnt)) = strTypeComplex) Then
      If varBf(cnt).im = 0 Then
        varAf(cnt) = varBf(cnt).re
      Else
        Set varAf(cnt) = varBf(cnt)
      End If
    Else
      Debug.Print "error"
      Exit Sub
    End If
  Next cnt
End Sub

'=============================
'複素数Classの配列を、文字列に置き換える
'=============================
Private Sub RealAndComp2String(ByRef varBf, ByRef varAf)
  If Not (LBound(varBf) = LBound(varAf) And UBound(varBf) = UBound(varAf)) Then
    Exit Sub
  End If
  Dim cnt As Long
  For cnt = LBound(varBf) To UBound(varBf)
    If (2 <= VarType(varBf(cnt)) And VarType(varBf(cnt)) <= 6) Then
      varAf(cnt) = Str(varBf(cnt))
    ElseIf (VarType(varBf(cnt)) = 9 And TypeName(varBf(cnt)) = strTypeComplex) Then
      If varBf(cnt).im = 0 Then
        varAf(cnt) = Str(varBf(cnt).re)
      Else
        varAf(cnt) = varBf(cnt).CompStr
      End If
    Else
      Debug.Print "error"
      Exit Sub
    End If
  Next cnt
End Sub

'=============================
'-1.999999のような根が-2でよいか確認するSub Procedure
'=============================
Private Sub RootsRounding(ByRef varRoots As Variant, ByVal varCoeff As Variant, Optional ByVal byteDigit As Byte = 7)
  Dim varRootsRounded As Variant
  Dim cnt As Long, cnt2 As Long
  Dim compTemp As Complex

  ReDim varRootsRounded(UBound(varRoots))

  For cnt = LBound(varRoots) To UBound(varRoots)
    Set varRootsRounded(cnt) = New Complex
    varRootsRounded(cnt).re = Round(varRoots(cnt).re, byteDigit)
    varRootsRounded(cnt).im = Round(varRoots(cnt).im, byteDigit)
  Next cnt

  Set compTemp = New Complex
  For cnt2 = LBound(varRoots) To UBound(varRoots)
    Call compTemp.SetC(0, 0)
    For cnt = LBound(varCoeff) To UBound(varCoeff)
      Set compTemp = SumComp(compTemp, ProductComp(varCoeff(cnt), PowerComp(varRootsRounded(cnt2), UBound(varCoeff) - cnt)))
    Next cnt
    If Round(compTemp.re, byteDigit) = 0 And Round(compTemp.im, byteDigit) = 0 Then
      Set varRoots(cnt2) = varRootsRounded(cnt2)
    End If
  Next cnt2
End Sub

'=============================
'n重根の確認
'=============================
Private Function IsNFoldedRoots(ByVal varCoeff As Variant, ByRef compRoot As Complex, Optional ByVal byteDigit As Byte = 7) As Boolean
  Dim compNFoldedRoots As Complex: Set compNFoldedRoots = New Complex
  Dim cnt As Long
  Dim compTemp As Complex: Set compTemp = New Complex
  
  '係数配列の要素数が0から数えて2(2次方程式)未満であれば
  If UBound(varCoeff) < 2 Then
    '重根判定にFalseを返す
    IsNFoldedRoots = False
  '係数配列の要素数が0から数えて2以上であれば
  Else
    'パスカルの三角形の左から2つめ(n次多項式ならn-1。配列の要素はゼロから数えるので配列の要素数)に基づき
    'c_0(x-(a+bi))^n=c_0 x^n+c_0 (n-1) (-(a+bi)) x^(n-1)+…
    'c_1=c_0 (n-1) (-(a+bi)) ⇔ (a+bi) = c_1 / c_0 / (-(n-1))
    'としてn重根を推定
    Set compNFoldedRoots = DivComp(varCoeff(1), varCoeff(0), -1 * UBound(varCoeff))
    '推定したn重根を多項式に当てはめて数値計算
    For cnt = LBound(varCoeff) To UBound(varCoeff)
      Set compTemp = SumComp(compTemp, ProductComp(varCoeff(cnt), PowerComp(compNFoldedRoots, UBound(varCoeff) - cnt)))
    Next cnt
    '推定したn重根を多項式に当てはめた結果がゼロとなっているか確認
    If Round(compTemp.re, byteDigit) = 0 And Round(compTemp.im, byteDigit) = 0 Then
      '与えられているByRef引数にn重根を代入
      Set compRoot = New Complex
      Set compRoot = compNFoldedRoots
      '重根判定にTrueを返す
      IsNFoldedRoots = True
    Else
      '重根判定にFalseを返す
      IsNFoldedRoots = False
    End If
  End If
End Function

'=============================
'n次多項式の根の1つが判明している場合に、残りのn-1個の根を持つn-1次多項式を返す
'=============================
Public Function PolyN2Nminus1(ByVal compRoot As Variant, ByVal varCoeffIn, ByRef varCoeffOut, Optional ByVal byteDigit As Byte = 7) As Boolean
  Dim cnt As Long
  Dim compTemp As Complex: Set compTemp = New Complex
  'n次多項式の係数配列の要素数が0から数えて2(2次方程式)未満であれば
  If UBound(varCoeffIn) < 2 Then
    PolyN2Nminus1 = False
  Else
    '与えられた根を多項式に当てはめて数値計算
    For cnt = LBound(varCoeffIn) To UBound(varCoeffIn)
      Set compTemp = SumComp(compTemp, ProductComp(varCoeffIn(cnt), PowerComp(compRoot, UBound(varCoeffIn) - cnt)))
    Next cnt
    '数値計算結果がゼロなら(与えられた根が真に根なら)
    If Round(compTemp.re, byteDigit) = 0 And Round(compTemp.im, byteDigit) = 0 Then
      '残りのn-1個の根を持つn-1次多項式の係数をByRef引数に返す
      Call compTemp.SetC(0, 0)
      ReDim varCoeffOut(UBound(varCoeffIn) - 1)
      For cnt = LBound(varCoeffIn) To UBound(varCoeffIn) - 1
        Set compTemp = ProductComp(compTemp, compRoot)
        Set compTemp = SumComp(compTemp, varCoeffIn(cnt))
        Set varCoeffOut(cnt) = New Complex
        Set varCoeffOut(cnt) = compTemp
      Next cnt
      PolyN2Nminus1 = True
    '数値計算結果がゼロでないなら(与えられた根が真に根でないなら)
    Else
      PolyN2Nminus1 = False
    End If
  End If
End Function


'=============================
'WorkSheet函数用
'=============================
Public Function WSFuncGetRoots(rngCoeff As Range) As Variant
  Dim strCoeff() As String
  Dim compCoeff() As Complex
  Dim cnt As Long
  Dim varRoots As Variant

  If rngCoeff.Rows.Count > 1 And rngCoeff.Columns.Count > 1 Then
    WSFuncGetRoots = CVErr(xlErrNum)
  ElseIf rngCoeff.Rows.Count = 1 Then
    ReDim strCoeff(rngCoeff.Columns.Count - 1)
    For cnt = 0 To rngCoeff.Columns.Count - 1
      strCoeff(cnt) = rngCoeff(1, cnt + 1)
    Next cnt
  ElseIf rngCoeff.Columns.Count = 1 Then
    ReDim strCoeff(rngCoeff.Rows.Count - 1)
    For cnt = 0 To rngCoeff.Rows.Count - 1
      strCoeff(cnt) = rngCoeff(cnt + 1, 1)
    Next cnt
  End If

  ReDim compCoeff(UBound(strCoeff))
  For cnt = LBound(compCoeff) To UBound(compCoeff)
    Set compCoeff(cnt) = New Complex
    Call compCoeff(cnt).SetC_str(strCoeff(cnt))
  Next cnt

  Dim rt As Variant
  Call GetRoots(rt, compCoeff)
  ReDim varRoots(1 To UBound(rt) + 1)

  For cnt = LBound(rt) To UBound(rt)
    varRoots(cnt + 1) = rt(cnt).CompStr
  Next cnt

  WSFuncGetRoots = varRoots

End Function

計算例・使い方

テスト用モジュール

Module1.bas
Option Explicit

Public Sub test001()
  Dim cnt As Long
  Dim a0 As Complex:  Set a0 = New Complex
  Dim a1 As Complex:  Set a1 = New Complex
  Dim a2 As Complex:  Set a2 = New Complex
  Dim a3 As Complex:  Set a3 = New Complex
  Dim a4 As Complex:  Set a4 = New Complex
  Dim a5 As Complex:  Set a5 = New Complex
  Dim a6 As Complex:  Set a6 = New Complex
  Dim a7 As Complex:  Set a7 = New Complex
  Dim a8 As Complex:  Set a8 = New Complex
  Dim rt As Variant

  Set rt = Nothing
  Debug.Print Chr(10) & "x^2+1=0"
  If GetRoots(rt, 1, 0, 1) Then
    Debug.Print "収束しました"
  Else
    Debug.Print "収束しませんでした"
  End If
  For cnt = LBound(rt) To UBound(rt)
    Debug.Print rt(cnt).CompStr
  Next cnt

  Call a1.SetC(3, 2)
  Call a2.SetC(5, 0)
  Call a3.SetC(2, 1)
  Set rt = Nothing
  Debug.Print Chr(10) & "(3+2i)x^2+5x+(2+i)=0"
  If GetRoots(rt, a1, a2, a3) Then
    Debug.Print "収束しました"
  Else
    Debug.Print "収束しませんでした"
  End If
  For cnt = LBound(rt) To UBound(rt)
    Debug.Print rt(cnt).CompStr
  Next cnt

  Call a1.SetC(0, -8)
  Set rt = Nothing
  Debug.Print Chr(10) & "(x^3-8i=0"
  If GetRoots(rt, 1, 0, 0, a1) Then
    Debug.Print "収束しました"
  Else
    Debug.Print "収束しませんでした"
  End If
  For cnt = LBound(rt) To UBound(rt)
    Debug.Print rt(cnt).CompStr
  Next cnt

  Set rt = Nothing
  If GetRoots(rt, 33, 140, 266, 140, 33) Then
    Debug.Print "収束しました"
  Else
    Debug.Print "収束しませんでした"
  End If
  Debug.Print Chr(10) & "33x^4+140x^3+266x^2+140x+33=0"
  For cnt = LBound(rt) To UBound(rt)
    Debug.Print rt(cnt).CompStr
  Next cnt

  Call a0.SetC(1, 0)
  Call a1.SetC(0, -16)
  Call a2.SetC(-112, 0)
  Call a3.SetC(0, 448)
  Call a4.SetC(1120, 0)
  Call a5.SetC(0, -1792)
  Call a6.SetC(-1792, 0)
  Call a7.SetC(0, 1024)
  Call a8.SetC(256, 0)
  Set rt = Nothing
  Debug.Print Chr(10) & "(x-2i)^8=0"
  If GetRoots(rt, a0, a1, a2, a3, a4, a5, a6, a7, a8) Then
    Debug.Print "収束しました"
  Else
    Debug.Print "収束しませんでした"
  End If
  For cnt = LBound(rt) To UBound(rt)
    Debug.Print rt(cnt).CompStr
  Next cnt

  Call a0.SetC(1, 0)
  Call a1.SetC(-16, -24)
  Call a2.SetC(-140, 336)
  Call a3.SetC(2576, -504)
  Call a4.SetC(-8330, -8400)
  Call a5.SetC(-6832, 33432)
  Call a6.SetC(56980, -23184)
  Call a7.SetC(-52432, -35592)
  Call a8.SetC(-239, 28560)
  Set rt = Nothing
  Debug.Print Chr(10) & "{x-(2+3i)}^8=0"
  If GetRoots(rt, a0, a1, a2, a3, a4, a5, a6, a7, a8) Then
    Debug.Print "収束しました"
  Else
    Debug.Print "収束しませんでした"
  End If
  For cnt = LBound(rt) To UBound(rt)
    Debug.Print rt(cnt).CompStr
  Next cnt

  Call a0.SetC(1, 0)
  Call a1.SetC(-15, -21)
  Call a2.SetC(-91, 273)
  Call a3.SetC(1715, -567)
  Call a4.SetC(-5775, -3885)
  Call a5.SetC(1603, 16737)
  Call a6.SetC(16807, -18333)
  Call a7.SetC(-20799, 1347)
  Call a8.SetC(6554, 4449)
  Set rt = Nothing
  Debug.Print Chr(10) & "{x-(2+3i)}^7 (x-1)=0"
  If GetRoots(rt, a0, a1, a2, a3, a4, a5, a6, a7, a8) Then
    Debug.Print "収束しました"
  Else
    Debug.Print "収束しませんでした"
  End If
  For cnt = LBound(rt) To UBound(rt)
    Debug.Print rt(cnt).CompStr
  Next cnt

  Call a0.SetC(1, 0)
  Call a1.SetC(0, -8 * Sqr(3))
  Call a2.SetC(-84, 0)
  Call a3.SetC(0, 168 * Sqr(3))
  Call a4.SetC(630, 0)
  Call a5.SetC(0, -504 * Sqr(3))
  Call a6.SetC(-756, 0)
  Call a7.SetC(0, 216 * Sqr(3))
  Call a8.SetC(81, 0)
  Set rt = Nothing
  Debug.Print Chr(10) & "{x-sqrt(3)i}^8=0"
  If GetRoots(rt, a0, a1, a2, a3, a4, a5, a6, a7, a8) Then
    Debug.Print "収束しました"
  Else
    Debug.Print "収束しませんでした"
  End If
  For cnt = LBound(rt) To UBound(rt)
    Debug.Print rt(cnt).CompStr
  Next cnt
End Sub

テスト用モジュール実行結果

Module1_Result.bas
'x^2+1=0
'収束しました
'i
'-i

'(3+2i)x^2+5x+(2+i)=0
'収束しました
'-.836846641040605+1.09033036388437i
'-.316999512805549-.3210995946536i

'(x^3-8i=0
'収束しました
'1.73205080756888+i
'-1.73205080756888+i
'-2i
'収束しました

'33x^4+140x^3+266x^2+140x+33=0
'-.323693680587964+.274409342564836i
'-1.79751844062416+1.5238352897217i
'-1.79751844062416-1.5238352897217i
'-.323693680587964-.274409342564836i

'(x-2i)^8=0
'収束しました
'2i
'2i
'2i
'2i
'2i
'2i
'2i
'2i

'{x-(2+3i)}^8=0
'収束しました
'2+3i
'2+3i
'2+3i
'2+3i
'2+3i
'2+3i
'2+3i
'2+3i

'{x-(2+3i)}^7 (x-1)=0
'収束しました
'2+3i
'2+3i
'2+3i
'2+3i
'2+3i
'1
'2+3i
'2+3i

'{x-sqrt(3)i}^8=0
'収束しました
'1.73205080756888i
'1.73205080756888i
'1.73205080756888i
'1.73205080756888i
'1.73205080756888i
'1.73205080756888i
'1.73205080756888i
'1.73205080756888i
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?