オーバーフローを直すには?

解決


フーリエ嫌い  2005-01-06 22:33:43  No: 118634

以下のプログラム中のfai1という式がどうしてもオーバーフローしてしまいます。どなたか直し方を教えて下さい。ちなみにVBは完全な素人です。よろしくお願いします。

Dim n, ii, ie, ih As Integer
    Dim it As Variant
    Dim xr(20070), xi(20070) As Integer
    Dim aa(20070) As Variant
     
    n = 64
    ii = ie - ih
    it = (Abs(ii)) / n
    For i = 0 To n - 1
    xr(i) = aa(Int(ih + it * i)) * (Int(ih + it * i) + 1) - (ih + it * i) + aa(Int(ih + it * i) + 1) * ((ih + it * i) - Int(ih + it * i))
    xi(i) = 0
    Next i
    
    Dim n1 As Integer
    For i = 0 To n1
    xi(i) = 0
    Next i
    
    Dim l, pi As Integer
    l = Int(Log(n) / Log(2) + 0.5)
    pi = 3.141592
    
    Dim k, la, theta, c, s, ct, st, nk, r, t, m0, j, m, mk, a1, a2, b1, b2, cx, sx, x, y, z1, fai1, fai, z, v, pre As Integer
    For k = 1 To la
    theta = 2 * pi / 2 ^ k
    c = 1: s = 0
    ct = Cos(theta)
    st = Sin(theta)
    nk = n / 2 ^ k
    For r = 0 To n / 2 - nk Step nk
    t = r / nk
    m0 = 0
    For j = 1 To k
    m0 = m0 + (t - Int(t / 2) * 2) * n / 2 ^ j
    t = Int(t / 2)
    Next j
    For m = m0 To m0 + nk - 1: mk = m + nk
    a1 = xr(m): a2 = xi(m)
    b1 = c * xr(mk) + s * xi(mk)
    b2 = -s * xr(mk) + c * xi(mk)
    xr(m) = a1 + b1
    xi(m) = a2 + b2
    xr(mk) = a1 - b1
    xi(mk) = a2 - b2
    Next m
    cx = ct * c - st * s
    sx = ct * s + st * c
    c = cx: s = sx
    Next r
    Next k
    For r = 0 To n1
    xr(r) = xr(r) / n: xi(r) = xi(r) / n
    Next r
    nk = 1: k = l
    For r = 0 To n1
    t = r / nk
    m0 = 0
    For j = 1 To k
    m0 = m0 + (t - Int(t / 2) * 2) * n / 2 ^ j
    t = Int(t / 2)
    Next j
    If r < m0 Then
    x = xr(m0): y = xi(m0)
    xr(m) = xr(r): xi(m0) = xi(r)
    xr(r) = x: xi(r) = y
    End If
    Next r
    For r = 1 To 10
    Next r
    z1 = Sqr(xr(1) * xr(1) + xi(1) * xi(1))
    fai1 = (-xr(1) / Abs(xr(1)) * (Atn(xi(1) / z1 / Sqr(1 - xi(1) * xi(1) / z1 / z1)) - pi / 2))
    For r = 1 To 5
    x = xr(r): y = xi(r)
    z = Sqr(x * x + y * y)
    fai = -x / Abs(x) * (Atn(y / z / Sqr(1 - y * y / z / z)) - pi / 2)
    If fai > pi Then
    fai = fai - 2 * pi
    End If
    If fai < -pi Then
    fai = fai + 2 * pi
    End If
    If Abs(fai) > pi Then
    If fai > pi Then
    fai = fai - 2 * pi
    End If
    End If
    pre = z * v * 0.766
    Next r


030  2005-01-06 22:41:28  No: 118635

何故オーバーフローしてしまうかであれば自分で調べる事ができます。
オーバーフローしたときのそれぞれの値をチェックして何故そうなったか
調べてください。

で桁数が足りないとか別の問題が発生したら質問して下さい。

#これは質問というよりデバッグ依頼掲示板(そういうのがあれば)に相応しい内容だと思いますが。


LESIA  2005-01-06 22:42:21  No: 118636

掛け算より割り算の方を先にやるとか

fai1 = (-xr(1) / Abs(xr(1)) * (Atn(xi(1) / z1 / Sqr(1 - (xi(1) / z1) * (xi(1) / z1))) - pi / 2))


フーリエ嫌い  2005-01-06 22:43:54  No: 118637

了解しました。もう少し努力してみたいと思います。
030さんアドバイスありがとうございました。


030  2005-01-06 22:46:55  No: 118638

ちょっと補足
VBに慣れていないということなので、どこにでもあるTipsを

イミディエイトウィンドウで
?変数名
とするとそのときの値を見る事ができます。

ブレークポイントを設定すると、そこでプログラムが
一時中断します。

デバッグのテクニックを身に着けるのも言語を学ぶのと
同じように重要です。


ひろ  2005-01-07 04:51:19  No: 118639

非常に0に近い値による除算とかAtnの引数あたりが気になりますね。

Atnは、その性質上常に引数の0除算及びオーバーフローの可能性がありますから。
Atnの引数の分子と分母を直接渡す Atn2(x,y)という関数を作って、Atn(x/y) の代わりに使用してはどうでしょうか?

Public Function PI() As Double
    PI = Atn(1#) * 4
End Function

Public Function Atn2(ByVal x As Double, ByVal y As Double) As Double
  Dim atnArg As Double
  If y <> 0# Then
    On Error GoTo ErrorHandler
    atnArg = x / y
    Atn2 = Atn(atnArg)
    Exit Function
  End If
'y が0か除算でオーバーフローするほど小さい値である場合
yisVerySmall:

  Dim retVal As Double
  '返り値の絶対値はπ/2にする
  retVal = PI / 2#
  'x と y の符号によって返り値の符号を決める
  If x > 0# Then
    If y < 0# Then
       retVal = -retVal
    End If
  ElseIf x < 0# Then
    If y >= 0# Then
       retVal = -retVal
    End If
  Else
    '計算不能なのでエラーにする
    Err.Raise 5
  End If
  Atn2 = retVal
  Exit Function

ErrorHandler:
    '除算でオーバーフロー→ y は非常に小さい値なので別の方法で計算する
    If Err.Number = 6 Then
      Resume yisVerySmall
    End If
    
    Err.Raise Err.Number

End Function


マグ  2005-01-07 20:03:35  No: 118640

上のソースでこれって円周率ですよね。
>pi = 3.141592

もし、円周率ならば、
pi = Math.PI
にすれば、もっと正確な値を出すことが出来ます。


魔界の仮面弁士  2005-01-07 20:34:18  No: 118641

# コードを見る限り、明らかに .NETではないので、
# Mathクラスは使えないと思いますよ。>マグさん

VB6 や VBA で円周率を得るとすれば、
  PI = 4 * Atn(1)
が簡単ですね。

もっとも、円周率は変化するような値では無いので、
Constステートメントで定数化しても良いでしょうけど。(^^;

さて、本題。

> Dim k, la, theta, c, s, ct, st, nk, r, t, m0, j, m, mk, a1, a2, b1, b2, cx, sx, x, y, z1, fai1, fai, z, v, pre As Integer

この宣言だと、最後の pre だけが Integer で、それ以外は全て
「既定のデータ型(初期指定はVariant)」になってしまいますよ。
もし、すべて Integer にしたいなら、
  Dim k As Integer, la As Integer
という感じで、それぞれに As 句を付けてください。

>    Dim l, pi As Integer
>    l = Int(Log(n) / Log(2) + 0.5)
>    pi = 3.141592
piは、Integer型で宣言されていますので、整数しか入りません。
上記のコードでは、piの値は 3.141592 ではなく、3 に丸められてしまいますよ。

> fai1という式がどうしてもオーバーフローしてしまいます。
オーバーフローに関しては、どの行でエラーになったのか、そして、
それぞれの変数にどのような値が入っているのかを調べてください。

とりあえず、提示されたコードを実行してみましたが、先頭の
>    ii = ie - ih
の実行時に、ie, ih の両方が、値を未設定(0 のまま)だったので、
結果的に、問題のおきたという下記の fai1 式においても、
>  fai1 = (-xr(1) / Abs(xr(1)) * (Atn(xi(1) / z1 / Sqr(1 - xi(1) * xi(1) / z1 / z1)) - pi / 2))
最終的に、xr(1), xi(1), z1 のすべてが 0 のままとなってしまいました。
(z1が0なので、「〜 / z1」の部分がエラーになり、検証できませんでした)

実際のコードでは、各変数に、きちんと値を代入されているのでしょうが、
第三者にはそれはわかりませんので、エラー発生時の各変数の値を見て、
ご自身で判断してください。

さて、オーバーフローの原因は幾つかありますが、たとえば、
    Dim V As Variant
    Dim D As Double

    D = 1 / 0   'これは、「0で除算しました」エラーになるが、

    V = 0
    D = V / 0   'これだと「オーバーフローしました」エラー
のように、Variant型の値を 0 で除算したような場合や、

    Dim I As Integer
    Dim J As Integer
    Dim K As Integer
    I = 30000
    J = 30000
    K = I + J   'ここでオーバーフロー

など、計算結果がデータ型の範囲(Integer型の最大は32767)を
超えた場合に発生します。

また、演算途中の精度にも気をつけてください。たとえば上記を
    Dim I As Integer
    Dim J As Integer
    Dim K As Long
    I = 30000
    J = 30000
    K = I + J
のように Integer → Long に変更したとしても、やはり
オーバーフローになります。これは、「+」演算において、
「Integer型同士を足した場合、Integerの精度で演算する」ためです。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加