投稿

小数を分数に変換(1)

前回までで、四則演算を含んだ文字列を数値(小数)に変換できた。 で、分数(割り算)で入力したものは分数で出力するのが自然じゃないかと考えた。 そこで、小数を分数に変換することを考える。 精度を保ったままということを考えたら連分数を用いるのがよさそうだ。 処理の流れ  0. x(0) = x、i = 0  1. x(i)を超えない整数をb(i)とし、x(i)-b(i)が0ならば終了  2. x(i+1) = 1/(x(i)-b(i))、i = i + 1  3. i≦100(※)ならば、1へ。でなければ終了   ※は仮の終了条件 これによって、 (1) x = b(0) + 1/(b(1) + 1/(b(2) + ...)) と変換でき、i = nまで計算した結果を X(n) = B(n)/A(n)とするとn≧2のとき以下の式が成り立つ。 (2a) A(0) = 1、A(1) = b(1)、A(n) = b(n)A(n-1) + A(n-2) (2b) B(0) = b(0)、B(1) = b(0)b(1) +  1、B(n) = b(n)B(n-1) + B(n-2) B(n)が十分大きくなったときにも計算を打ち切ることにする。

文字列数式評価(6/完)~メイン処理

細かく分けて関数化したので、メインのプログラムは数行で済む。 '   機 能  :   文字列を数値に変換する '   書 式  :   XEval(x) '   返り値  :   10 進型小数 '   引 数  :   x   -   文字列 '   説 明  :   x に数値を指定したときは倍精度の小数を返す '               (例)    XEval("1/7") = 0.1428571428571428571428571429 '                       XEval( 1/7 ) = 0.142857142857143 '   注 意  :   変数や関数は文字列の外に出して指定する '               (例)    XEval("(1+" & x & ")/2") Public Function XEval(ByVal s)     Set XEval = New Triple     Dim s_              As String       '   かっこ内の文字列         '   かっこの中の処理     Do While InStr(s, "(")         XInPtxt s, s_         s = Replace(s, "(" & s_ & ")", XEval(s_))     Loop         '   連続している符号処理     s = XEval_Sign(s)                 '   各項の和を計算     XEval = XEval_PM(s)     End Function 本文中には書かなかったが、「3+2√7」を「"3+2*" & Sqr(7)」 の形で書けるのが個人的に気に入っている。

文字列数式評価(5)~各項の和

残るは各項を足し合わせる処理だが、 乗除算とほぼ同じ処理になる。 '   各項の和を計算 Private Function XEVal_PM(ByVal s)     On Error GoTo Err:     Set XEVal_PM = New Triple         Dim i               As Long         '   繰り返し変数     '   "+" で分解     s = Split(s, "+")     XEVal_PM = CDec(0)     For i = 0 To UBound(s)         If Len(s(i)) > 0 Then             '   項の中(の乗除算)を計算する             s(i) = XEval_MD(s(i))         Else             s(i) = 0         End If         XEVal_PM = XEVal_PM + s(i)     Next         Exit Function     Err:     Err.Raise 9002, , s(i) & "を数値として処理できませんでした" End Function ゼロ割り回避がない分すっきりしている。

文字列数式評価(4)~項内の乗除算

'   項の中(の乗除算)の計算 Private Function XEval_MD(ByVal s)     On Error Goto Err:     Set XEval_ = New Triple     Dim i               As Long         '   繰り返し変数     '   "/" を "*/" に変換し、"*" で分解     s = Split(Replace(s, "/", "*/"), "*")         XEval_MD = CDec(1)     For i = 0 To UBound(s)         '   除算を乗算に変換         If Left(s(i), 1) = "/" Then             '   除数が 0 ならばエラーを返す             If Mid(s(i), 2) = 0 Then                 Err.Raise 9000, , "ゼロ除算が発生しました"                 Exit Function             End If                 XEval_MD = XEVal_MD / Mid(s(i), 2)         Else             XEval_MD = XEval_MD * s(i)         End If     Next     Exit Function Err:     Err.Raise 9002, , s(i) & "を数値として処理できませんでした" End Function s(i)はSplitから生成される文字列だが、 計算過程で倍精度に変換されないように注意をする。 ※ 精度維持のため、除算は除算のまま処理する。

文字列数式評価(3)~符号処理

普通の式であればかっこを外したあとに 3つ以上符号("+","-")が連なることはないだろうが、 念のためそれも考慮した処理を組み立てる。 "+"で区切ることを考えると、 最終的に残すべきなのは"+"と"+-"ということになる。 連続する符号のパターンは "++", "--", "+-", "-+" の4通りで現れる度に前2つを"+"、後ろ2つを"-"に置き換える。 最後に連続した符号がなくなったところで "-"を"+-"に置き換えれば完成となる。 '   (数式の中の)符号処理 Private Function XEval_Sign(ByVal s) As String     '   2つ以上連続している符号を処理する     Do While XInStr(s, "++", "+-", "-+", "--")         s = XReplace(s, "+", "++", "--")         s = XReplace(s, "-", "+-", "-+")     Loop     '   単独の "-" を "+-" に変換     s = Replace(s, "-", "+-")     '   "*"、"/" の直後の "+" を削除     s = Replace(s, "*+", "*")     s = Replace(s, "/+", "/")     XEval_Sign = s En

文字列数式評価(2)~かっこの処理

ではまず、かっこの中を抜き出す処理 '   機 能  :   かっこ内にある文字列 '   書 式  :   XInPTxt(s, [t]) '               XInPTxt s, [t] '   返り値  :   最初のあらわれる開きかっこと '               それに対応する閉じかっこの間の文字列 '   引 数  :   s   -   文字列 '               t   -   かっこ間の文字列を格納する変数 '   説 明  :   かっこの対応が正しくない場合はエラーを返す Public Function XInPtxt(ByVal s, Optional t) As String     Dim i               As Long         '   繰り返し変数     Dim c               As String       '   調べる文字     Dim d               As Long         '   かっこの深さ     Dim lp              As Long         '   開きかっこ位置     Dim rp              As Long         '   閉じかっこ位置     For i = 1 To Len(s)         c = Mid(s, i, 1)         Select Case c             Case "("                 d = d + 1                 '   d = 1 ならば、最初の開きかっこ                 If d = 1 Then lp = i             Case ")"                 d = d - 1                 '   d = 0 ならば、対応する閉じかっこ       

文字列数式評価(1)

距離計算の前に、VBAでなるべく高精度を保ちたいと思い、 十進のvariant型をTripleクラスとして使えるようにしようと考えた。 Tripleクラスの既定プロパティとしてValueを作るのだが、 代入の時に (1)  foo = "5/7" としても値は倍精度の15桁にしかならない。 (2)  foo = CDec(5)/7 のようにする手もあるが、 CDecの被せ忘れが精度落ちの原因になりかねないし、 何しろめんどくさい。 そこで、(1)の形でも十進型になるように 文字列を数式として評価する関数を作ることにした。 作るにあたり数式評価についてネットを検索したのだが、 さっぱり要領を得ない(というより理解できなかった?)ので、 ここはシンプルに自前で作ることにする。 処理の流れ  0. 指数関数や平方根など文字列の外にある関数などの値を計算。  1. かっこがあれば、かっこ内を計算し、1へ。(再帰を使う)   かっこがなければ、2へ。(残りは四則計算)  2. 2つ以上続いている符号を(同値関係を保ちながら)   "+"と"+-"へ置き換え、"+"で項に分解する。(残りは乗除算)  3. "/"を"*/"へ置き換え、"*"で因数に分解し、   "/"を含む因数は逆数をとって乗算に直す。  4. 各項内に(もしあれば)乗算を処理。  5. 各項を足し合わせる。 項への分解さえしっかりできれば、速度は若干落ちるだろうが、 (逆)ポーランド記法などにこだわる必要はないと思うのだが、 どうだろう。