StackOverflowExceptionについて

タグの編集
投稿者 トマト  (小学生) 投稿日時 2009/8/27 01:45:07
こんにちは。質問ばかりですいません。

分数のクラスを作っているのですが計算中にStackOverflowExceptionが発生します。
MSDNによると「入れ子のメソッド呼び出しが多すぎて実行スタックがオーバーフローすると、 StackOverflowException 例外がスローされます。」とのことです。
これを回避する方法はありませんか?

コードを載せておきます。

''' <summary>小学校で習うような簡単な数学を集めました。</summary> 
Public Module EasyMath

'中略 

''' <summary>最大公約数を取得します。</summary> 
''' <param name="Int1">1つ目の数字を指定します。</param> 
''' <param name="Int2">2つ目の数字を指定します。</param> 
Public Function GetGCD(ByVal Int1 As IntegerByVal Int2 As IntegerAs Integer
    '小さいほうを探す 
     Dim Mini As Integer = Math.Min(Int1, Int2)
    
    Dim Int1_List As New List(Of Integer)
    Dim OKList As New List(Of Integer)
    Dim inting As Double
    'Int1を確認 
    For i As Integer = 1 To Mini
        inting = Int1 / i
        If Not IsDecimal(inting) Then 
            Int1_List.Add(i)
        End If
    Next
    'Int2を確認 
     For i As Integer = 1 To Mini
         inting = Int2 / i
         If Not IsDecimal(inting) Then
             If Int1_List.Contains(i) Then
                 OKList.Add(i)
             End If
         End If
     Next
     Return OKList(OKList.Count - 1)
End Function

''' <summary>数字が小数か確認します。</summary> 
''' <param name="Nomber">確認する数字を指定します。</param> 
''' <returns>小数ならTrue でないならFalseを返します。</returns> 
<Extension()> Public Function IsDecimal(ByVal Nomber As DecimalAs Boolean
    Dim NomberStr As String = CStr(Nomber)
    For Each c As Char In NomberStr
        If c = "."Then
            Return True
        End If
    Next
    Return False
End Function

''' <summary>分数を表します。</summary> 
''' <remarks>約分は自動で行われます。</remarks> 
<Serializable()> Public Class Fraction
    Implements IConvertible

'中略 

     ''' <summary>約分をします。</summary> 
     Protected Friend Sub Reduce() '計算のメソッドがこのメソッドを実行したときにどこかの行で発生します。 
        Dim gcd = GetGCD(Me.Denominator, Me.Numerator)
        Me.Denominator \= gcd
        Me.Numerator \= gcd
    End Sub

'中略 

End Class
End Module

ちょっと読みにくくなってしまいました。
投稿者 まだまだ  (中学生) 投稿日時 2009/8/27 06:25:17
こんにちは
すみません、答えではないのですが、最大公約数は
「GCD」ではなく「GCM」ではなかったでしょうか?
答えではなくてすみません。
投稿者 トマト  (小学生) 投稿日時 2009/8/27 07:53:58
一応Yahoo!辞書で調べたのですが…。
投稿者 おおぎっち  (社会人) 投稿日時 2009/8/27 07:56:55
答えじゃありませんが気になったので
CCDもGCMもあってます。
ほかにはGCFとかいったりもします。

嗚呼、これだけですみません。
投稿者 刈谷勇  (社会人) 投稿日時 2009/8/27 22:19:55
こんにちは、トマトさん。

>分数のクラスを作っているのですが計算中にStackOverflowExceptionが発生します。
StackOverflowExceptionが発生する場所はどこで出ているのでしょうか?
また、提示していただいたプログラムのどのメソッドを呼んで、そのときの各変数等の値がどのようになっているのかを提示していただけないでしょうか?

※GetGCDはもっと簡単に書けるような気が・・・
投稿者 るきお  (社会人) 投稿日時 2009/8/27 22:26:35
こんにちは。

現象が再現できるコードがないと回答しにくいです。

一般的には数学などのように同じ処理を何度も何度も繰り返す場合、
同じメソッドが何度も何度も入れ子上に呼び出されStackOverflowの例外が発生します。

次の例は、処理のないように意味はありませんがStackOverflowが発生するシンプルな例です。
    
Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click
    Dim i As Integer
    i = AddOne(0)
End Sub

Private Function AddOne(ByVal value As IntegerAs Integer
    value += 1
    Return AddOne(value)
End Function


この例外を回避する代表的な方法は、
1.ロジックを見直して再帰が発生しないようにする。→再帰ではなくループを使う。
3.求める精度を低くする。→再帰回数カウント、求める値の桁数のチェックなどをして処理の停止条件とする。
3.StackOverflowの例外をCatchする。
といったところです。
現在では3は推奨されていないようです。
投稿者 トマト  (小学生) 投稿日時 2009/8/28 02:47:32
よくよくコードを見てみるとGetGCDに無駄がありそうです。
これから、修正してみます。
投稿者 トマト  (小学生) 投稿日時 2009/8/28 03:08:32
今、GetGCDを改良してみました。
        Public Function GetGCD(ByVal Int1 As IntegerByVal Int2 As IntegerAs Integer
            '小さいほうを探す 
            Dim Mini As Integer = Math.Min(Int1, Int2)

            Dim Int1_List As New List(Of Integer)
            Dim OKList As New List(Of Integer)
            Dim inting As Double
            'Int1を確認 
            For i As Integer = 1 To Mini
                inting = Int1 / i
                If Not IsDecimal(inting) Then
                    Int1_List.Add(i)
                End If
            Next
            'Int2を確認 
            For i As Integer = 1 To Mini
                inting = Int2 / i
                If Not IsDecimal(inting) AndAlso Int1_List.Contains(i) Then 'ここを改良しました。 
                    OKList.Add(i)
                End If
            Next
            Return OKList(OKList.Count - 1)
        End Function

結果は失敗です。
というよりも変な現象が起きました。

テスト用コード
    Sub Main()
        Application.EnableVisualStyles()
        Dim f As New Fraction(5, 87)
        MsgBox(f.ToDouble)
    End Sub

これを実行すると、GetGCDの"If Not IsDecimal(inting) Then"の行で呼び出しているIsDecimalの "For Each c As Char In Nomber.ToString"の行で発生しました。

でも変です。
ToDoubleではReduceは呼び出さないはずです。

IsDecimalのローカル
IsDecimal False
Nomber    1.25


もっと探ってみました。
すると、Denominatorという分母のプロパティのSetの中のReduce()の背景の色が変わっていました。
どこからも呼び出していないのに・・・。不思議です。
投稿者 トマト  (小学生) 投稿日時 2009/8/28 03:47:34
解決しました。まだちょっとしかテストしていませんが。
''' <summary>約分をします。</summary> 
Protected Friend Sub Reduce()
    Dim gcd = GetGCD(Me.Denominator, Me.Numerator)
    Me._denominator \= gcd 'この行と 
    Me._numerator \= gcd 'この行が原因でした。 
End Sub

ここでDenominatorにアクセスして無限にループしていたみたいです。