none
算術演算の結果オーバフローを回避したい RRS feed

  • 質問

  • いつも質問ばかりで恐縮です。

    次のコーディングで算術演算オーバフローが発生します。
      「算術演算の結果オーバフローが発生しました。」
      ('System.OverflowException' の初回例外が vbprog.exe で発生しました。)
    これを回避するにはどんな考慮が必要なのでしょう?
    演算時の型変換が何か関係するように思うのですが詳しいことは分かりません。
    よろしくお願いします。

            Dim maxval As Integer          ' 最大値
            Dim minval As Integer           ' 最小値
            Dim zz As Double         ' 演算結果
           :
            maxval = 2147483647            ' 最大値
            minval = -2147483648            ' 最小値
           :
            zz = Math.Sqrt((maxval - minval) ^ 2 * 2)

    2009年9月24日 6:36

回答

  • 外池と申します。面倒かとは思いますが、計算のひとつひとつの操作がどの型で行われているかを追わないといけません。

    zz = Math.Sqrt((maxval - minval) ^ 2 * 2)
    の行に、やはり問題があります。

    「maxval - minval」の引き算は、Integer型からInteger型を引く計算なので、結果はInteger型のままであるという制限がついてしまいます。したがって、「2147483647」から「-2147483648」を引くと、Integer型の表現できる範囲を超えた結果になるので、OverfloxExceptionになるわけです。同じように 「^2」の演算結果は何型の制限になるか、「*2」の演算結果は何型の制限になるか、すべて気にする必要があります。

    「maxval - minval」の引き算自体を成功させるためには、maxvalやminvalをもっと大きな数字を表現できる型に予め変換しておかなければなりません。例えば、Long型とか、Single型とか、Double型とか。Sqrtの計算をすることを考えれば、Double型で良いのではないでしょうか?


    (ホームページを再開しました)
    • 回答としてマーク ヘグリン 2009年9月24日 7:15
    2009年9月24日 6:59

すべての返信

  • 外池と申します。面倒かとは思いますが、計算のひとつひとつの操作がどの型で行われているかを追わないといけません。

    zz = Math.Sqrt((maxval - minval) ^ 2 * 2)
    の行に、やはり問題があります。

    「maxval - minval」の引き算は、Integer型からInteger型を引く計算なので、結果はInteger型のままであるという制限がついてしまいます。したがって、「2147483647」から「-2147483648」を引くと、Integer型の表現できる範囲を超えた結果になるので、OverfloxExceptionになるわけです。同じように 「^2」の演算結果は何型の制限になるか、「*2」の演算結果は何型の制限になるか、すべて気にする必要があります。

    「maxval - minval」の引き算自体を成功させるためには、maxvalやminvalをもっと大きな数字を表現できる型に予め変換しておかなければなりません。例えば、Long型とか、Single型とか、Double型とか。Sqrtの計算をすることを考えれば、Double型で良いのではないでしょうか?


    (ホームページを再開しました)
    • 回答としてマーク ヘグリン 2009年9月24日 7:15
    2009年9月24日 6:59
  • 外池さん、回答ありがとうございます。

    次の2ケースともOKになりました。
    1.maxval、minvalのデータ型の宣言をdouble
    2.maxval、minvalのデータ型の宣言はintegerのまま、演算式でctype(double)を指定
      zz = Math.Sqrt((ctype(maxval,double) - ctype(minval,double)) ^ 2 * 2)

    ところで、実行速度の観点からはどちらが早いのでしょう。
    やっぱり、1.の方が演算時の型変換がない分優位ですかね。

    ありがとうございました。また、よろしくお願いします。

    2009年9月24日 7:24
  • 解決していますが2点ほど、

    Int32.MaxValue 、Int32.MinValueが使えるかと。

    zzは数学がわかっていれば式を簡略できます。
    Math.Sqrt((maxval - minval) ^ 2 * 2) → (maxval - minval) * Math.Sqrt(2)

    外池さんのアドバイスと合わせて

    zz = ((Double)Int32.MaxValue - Int32.MinValue) * Math.Sqrt(2)

    かなぁ
    # VB構文として間違っていたらごめんなさい
    2009年9月24日 7:33
  • 佐祐理さん、いつもお世話になっています。
    アドバイスありがとうございます。

    計算の対象は、integerで表現できるXY座標空間内の2点間の距離です。
    座標点はランダムに与えられます。従って最大の距離はこの座標空間の
    対角線(第1象限・第3象限間と第2象限・第4象限間)になります。
    上式はこの対角線の長さです。この座標空間ではこれ以上の距離には
    ならないのでこの演算ができればどんな2点間の距離もオーバフローし
    ないと思っています。すなわち、上式は検証用の式です。
    実際の演算式は、2点の座標(xi,yi)、(xj,yj)として
     zz=Math.Sqrt((xi - xj) ^ 2 + ((yi- yj) ^ 2)
    です。

    今後ともよろしくお願いします。ありがとうございました。

    2009年9月24日 8:00
  • 外池です。

    C#で言うところの「キャスト」は、Visual Basicには直接対応する機能がなくて、関数のCDbl( )を使うことになりますね。ドキュメントによればインラインで展開されるので比較的高速に処理してくれるようです。私の経験から、整数型から浮動小数点型への変換は特に問題ないと思います。(逆は、Visual Basicの場合かなりクセがある。)

    ヘグリンさんは実行速度を気にしておられますので、その点について少々。

    xi-xj と yi-yj の演算で算術オーバーフローを避けることは必須なので、xi、xj、yi、yjは早々にDouble型に変換しておかないといけないと思います。
    で、2乗する計算ですが、べき乗の機能をそのまま使うとガクっと速度が落ちる可能性が高いです。もしかすると、最適化したコンパイルをしてくれるかもしれませんが、プログラムする時点で明示的に掛け算で書いたほうが速いと思います。

    Double dx = CDbl(xi) - Cdbl(xj)
    Double dy = Cdbl(yi) - Cdbl(yj)
    Double zz = Math.Sqrt(dx * dx + dy * dy)

    こんな感じです。でも、まぁ、ほとんど関係ないようにも思いますが、もし、モンテカルロ法で円の面積の計算なんかをやるのであれば、効くかもしれません。


    (ホームページを再開しました)
    2009年9月25日 1:14
  • Double dx = CDbl(xi) - Cdbl(xj)
    Double dy = Cdbl(yi) - Cdbl(yj)
    Double zz = Math.Sqrt(dx * dx + dy * dy)
    無粋な突っ込みかもしれませんが、VB と C# が混じってます。
    Double ** という変数宣言はできませんので…。

    Dim dx As Double = CDbl(xi) - CDbl(xj)
    Dim dy As Double = CDbl(yi) - CDbl(yj)
    Dim zz As Double = Math.Sqrt(dx * dx + dy * dy)
    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    2009年9月25日 15:12
    モデレータ