none
Stack overflow Exception in VB but in C# of equivalent codes! RRS feed

  • Question

  • Hi,

    I build a function that calculates the determinant, yet it gives a stack overflow exception. I tried hard to figure out what is the error but fails to identify anything wrong. Everything seems to me to be very consistent but not for the compiler. Here is the code that tests the function.

    Option Strict On
    Imports System.Math
    
    Public Class Form1
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim Mat(,) As Double = {{1, 1, 0}, {2, -1, 1}, {3, 3, -2}}
            Dim D As Double = Det(Mat)
            Me.Text = CStr(D)
        End Sub
    
        Public Function Det(ByRef Matrix(,) As Double) As Double
            If Not Sqrt(Matrix.Length) = CInt(Sqrt(Matrix.Length)) Then
                Throw New Exception("Not a square matrix")
            End If
            Dim Size As Integer = CInt(Sqrt(Matrix.Length))
            If Size = 1 Then Return Matrix(0, 0)
            Dim Result As Double = 0
            Dim SubMatrix(Size - 1, Size - 1) As Double 'This line generates error
            For i As Integer = 0 To Size - 1
                For j As Integer = 0 To i - 1
                    For k As Integer = 0 To Size - 2
                        SubMatrix(k, j) = Matrix(k + 1, j)
                    Next
                Next
                For j = i + 1 To Size - 1
                    For k As Integer = 0 To Size - 2
                        SubMatrix(k, j - 1) = Matrix(k + 1, j)
                    Next
                Next
                Result += (-1) ^ i * Matrix(0, i) * Det(SubMatrix)
            Next
            Return Result
        End Function
    
    End Class

    It interesting to note that when I tried to write the completely equivalent code in C#, it did not give any exceptions which baffled me much more. Here is the equivalent code in C# that does not give any exceptions:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                double[,] mat = { { 1, 1, 0 }, { 2, -1, 1 }, { 3, 3, -2 } };
                double d = det(mat);
                this.Text = d.ToString();
            }
    
            //Determinant
            double det(double[,] matrix)
            {
                if (Math.Sqrt(matrix.Length) != (int)Math.Sqrt(matrix.Length))
                    throw new Exception("Not square matrix");
                int size = (int)Math.Sqrt(matrix.Length);
                if (size == 1) return matrix[0, 0];
                double result = 0;
                double[,] submatrix = new double[size - 1, size - 1];
                for (int i = 0; i < size; i++)
                {
                    for (int j = 0; j < i; j++)
                    {
                        for (int k = 0; k < size - 1; k++)
                        {
                            submatrix[k, j] = matrix[k + 1, j];
                        }
                    }
                    for (int j = i + 1; j < size; j++)
                    {
                        for (int k = 0; k < size - 1; k++)
                        {
                            submatrix[k, j - 1] = matrix[k + 1, j];
                        }
                    }
                    result += Math.Pow(-1, i) * matrix[0, i] * det(submatrix);
                }
                return result;
            }
        }
    }

    Thanks to any help.


    • Edited by BGQQ Monday, April 15, 2013 7:30 PM
    Monday, April 15, 2013 7:11 PM

Answers

  • Ah - then the equivalent VB code is:

    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.ComponentModel
    Imports System.Data
    Imports System.Drawing
    Imports System.Linq
    Imports System.Text
    Imports System.Windows.Forms
    
    Namespace WindowsFormsApplication1
    	Partial Public Class Form1
    		Inherits Form
    
    		Public Sub New()
    			InitializeComponent()
    		End Sub
    
    		Private Sub button1_Click(ByVal sender As Object, ByVal e As EventArgs)
    			Dim mat(,) As Double = { { 1, 1, 0 }, { 2, -1, 1 }, { 3, 3, -2 } }
    			Dim d As Double = det(mat)
    			Me.Text = d.ToString()
    		End Sub
    
    		'Determinant
    		Private Function det(ByVal matrix(,) As Double) As Double
    			If Math.Sqrt(matrix.Length) <> CInt(Fix(Math.Sqrt(matrix.Length))) Then
    				Throw New Exception("Not square matrix")
    			End If
    'INSTANT VB NOTE: The variable size was renamed since Visual Basic does not handle local variables named the same as class members well:
    			Dim size_Renamed As Integer = CInt(Fix(Math.Sqrt(matrix.Length)))
    			If size_Renamed = 1 Then
    				Return matrix(0, 0)
    			End If
    			Dim result As Double = 0
    			Dim submatrix(size_Renamed - 2, size_Renamed - 2) As Double
    			For i As Integer = 0 To size_Renamed - 1
    				For j As Integer = 0 To i - 1
    					For k As Integer = 0 To size_Renamed - 2
    						submatrix(k, j) = matrix(k + 1, j)
    					Next k
    				Next j
    				For j As Integer = i + 1 To size_Renamed - 1
    					For k As Integer = 0 To size_Renamed - 2
    						submatrix(k, j - 1) = matrix(k + 1, j)
    					Next k
    				Next j
    				result += Math.Pow(-1, i) * matrix(0, i) * det(submatrix)
    			Next i
    			Return result
    		End Function
    	End Class
    End Namespace


    Convert between VB, C#, C++, & Java (http://www.tangiblesoftwaresolutions.com)
    Instant C# - VB to C# Converter
    Instant VB - C# to VB Converter

    • Marked as answer by BGQQ Tuesday, April 16, 2013 7:58 PM
    Monday, April 15, 2013 7:36 PM
  • BGQQ,

    In Visual Basic arrays are declared not by the length (or size) of each dimension but by its upper-bound. This is an artifact of an earlier time when the bounds of each dimension could be arbitrary (not 0 based). Anyway, this line here:

    Dim SubMatrix(Size - 1, Size - 1) As Double  

    is incorrect - you need to subtract 2 to create an actual sub-matrix. This code actually just creates a matrix of the same size as the input which is why your recursive function never terminates - causing a StackOverflowException. Sometimes it helps to write this as

    Dim SubMatrix(0 To Size - 2, 0 To Size - 2) As Double

    to help remind you that you're specifying the bounds, not the lengths. Trying changing to Size - 2 and see if that fixes your issue.

    Other miscellaneous tips unrelated to your original question:

    Also it shouldn't be necessary to take the square root of the length - if you call matrix.GetLength(0) it'll return the length of the first dimension of the array, and matrix.GetLength(1) is the length of the second dimension.

    No need to make the 'matrix' parameter ByRef, in fact it's more dangerous if you do. Unless your function is going to modify the variable of the caller you don't need it to be ByRef. Note that this is not the same as modifying the matrix passed in but rather modifying which matrix the variable refers to entirely. The original C# source doesn't do this.

    Avoid using PascalCasing for local variables. Generally, as in the case of your form PascalCasing is reserved for member-level declarations - like properties, control instances (Button1), subs, and functions (e.g. Text) and camelCasing is used for local-scope declarations - like parameters and local variables and constants. Sticking to this convention helps readers (us) see very clearly whether a variable is entirely contained in local state or exists globally somewhere in your program.

    Nice job importing System.Math - it's the right thing to do in a situation like this.

    Regards,

    -ADG


    Anthony D. Green | Program Manager | Visual Basic & C# Languages Team

    • Marked as answer by BGQQ Tuesday, April 16, 2013 7:58 PM
    Monday, April 15, 2013 9:05 PM
    Moderator

All replies

  • "CInt" in VB is not equivalent to an int cast in C#.

    Here's the equivalent code in C#:

    using System;
    
    public class Form1
    {
    
    	private void Button1_Click(object sender, System.EventArgs e)
    	{
    		double[,] Mat = {{1, 1, 0}, {2, -1, 1}, {3, 3, -2}};
    		double D = Det(ref Mat);
    		this.Text = Convert.ToString(D);
    	}
    
    	public double Det(ref double[,] Matrix)
    	{
    		if (!(Math.Sqrt(Matrix.Length) == Convert.ToInt32(Math.Sqrt(Matrix.Length))))
    		{
    			throw new Exception("Not a square matrix");
    		}
    		int Size = Convert.ToInt32(Math.Sqrt(Matrix.Length));
    		if (Size == 1)
    		{
    			return Matrix[0, 0];
    		}
    		double Result = 0;
    		double[,] SubMatrix = new double[Size, Size]; //This line generates error
    		for (int i = 0; i < Size; i++)
    		{
    			for (int j = 0; j < i; j++)
    			{
    				for (int k = 0; k <= Size - 2; k++)
    				{
    					SubMatrix[k, j] = Matrix[k + 1, j];
    				}
    			}
    			for (var j = i + 1; j < Size; j++)
    			{
    				for (int k = 0; k <= Size - 2; k++)
    				{
    					SubMatrix[k, j - 1] = Matrix[k + 1, j];
    				}
    			}
    			Result += Math.Pow((-1), i * Matrix[1, i] * Det(ref SubMatrix));
    		}
    		return Result;
    	}
    
    
    	public Form1()
    	{
    
    		SubscribeToEvents();
    	}
    
    //INSTANT C# NOTE: Converted event handler wireups:
    	private bool EventsSubscribed = false;
    	private void SubscribeToEvents()
    	{
    		if (EventsSubscribed)
    			return;
    		else
    			EventsSubscribed = true;
    
    		Button1.Click += Button1_Click;
    	}
    
    }


    Convert between VB, C#, C++, & Java (http://www.tangiblesoftwaresolutions.com)
    Instant C# - VB to C# Converter
    Instant VB - C# to VB Converter

    Monday, April 15, 2013 7:20 PM
  • "CInt" in VB is not equivalent to an int cast in C#.


    Thanks to your reply.

    The problem is not in the C# code; it works well. My problem is that the VB code give an exception about stack overflow which I can't understand why and how to fix it in the line:

    Dim SubMatrix(Size - 1, Size - 1) As Double

    Monday, April 15, 2013 7:29 PM
  • Ah - then the equivalent VB code is:

    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.ComponentModel
    Imports System.Data
    Imports System.Drawing
    Imports System.Linq
    Imports System.Text
    Imports System.Windows.Forms
    
    Namespace WindowsFormsApplication1
    	Partial Public Class Form1
    		Inherits Form
    
    		Public Sub New()
    			InitializeComponent()
    		End Sub
    
    		Private Sub button1_Click(ByVal sender As Object, ByVal e As EventArgs)
    			Dim mat(,) As Double = { { 1, 1, 0 }, { 2, -1, 1 }, { 3, 3, -2 } }
    			Dim d As Double = det(mat)
    			Me.Text = d.ToString()
    		End Sub
    
    		'Determinant
    		Private Function det(ByVal matrix(,) As Double) As Double
    			If Math.Sqrt(matrix.Length) <> CInt(Fix(Math.Sqrt(matrix.Length))) Then
    				Throw New Exception("Not square matrix")
    			End If
    'INSTANT VB NOTE: The variable size was renamed since Visual Basic does not handle local variables named the same as class members well:
    			Dim size_Renamed As Integer = CInt(Fix(Math.Sqrt(matrix.Length)))
    			If size_Renamed = 1 Then
    				Return matrix(0, 0)
    			End If
    			Dim result As Double = 0
    			Dim submatrix(size_Renamed - 2, size_Renamed - 2) As Double
    			For i As Integer = 0 To size_Renamed - 1
    				For j As Integer = 0 To i - 1
    					For k As Integer = 0 To size_Renamed - 2
    						submatrix(k, j) = matrix(k + 1, j)
    					Next k
    				Next j
    				For j As Integer = i + 1 To size_Renamed - 1
    					For k As Integer = 0 To size_Renamed - 2
    						submatrix(k, j - 1) = matrix(k + 1, j)
    					Next k
    				Next j
    				result += Math.Pow(-1, i) * matrix(0, i) * det(submatrix)
    			Next i
    			Return result
    		End Function
    	End Class
    End Namespace


    Convert between VB, C#, C++, & Java (http://www.tangiblesoftwaresolutions.com)
    Instant C# - VB to C# Converter
    Instant VB - C# to VB Converter

    • Marked as answer by BGQQ Tuesday, April 16, 2013 7:58 PM
    Monday, April 15, 2013 7:36 PM
  • BGQQ,

    In Visual Basic arrays are declared not by the length (or size) of each dimension but by its upper-bound. This is an artifact of an earlier time when the bounds of each dimension could be arbitrary (not 0 based). Anyway, this line here:

    Dim SubMatrix(Size - 1, Size - 1) As Double  

    is incorrect - you need to subtract 2 to create an actual sub-matrix. This code actually just creates a matrix of the same size as the input which is why your recursive function never terminates - causing a StackOverflowException. Sometimes it helps to write this as

    Dim SubMatrix(0 To Size - 2, 0 To Size - 2) As Double

    to help remind you that you're specifying the bounds, not the lengths. Trying changing to Size - 2 and see if that fixes your issue.

    Other miscellaneous tips unrelated to your original question:

    Also it shouldn't be necessary to take the square root of the length - if you call matrix.GetLength(0) it'll return the length of the first dimension of the array, and matrix.GetLength(1) is the length of the second dimension.

    No need to make the 'matrix' parameter ByRef, in fact it's more dangerous if you do. Unless your function is going to modify the variable of the caller you don't need it to be ByRef. Note that this is not the same as modifying the matrix passed in but rather modifying which matrix the variable refers to entirely. The original C# source doesn't do this.

    Avoid using PascalCasing for local variables. Generally, as in the case of your form PascalCasing is reserved for member-level declarations - like properties, control instances (Button1), subs, and functions (e.g. Text) and camelCasing is used for local-scope declarations - like parameters and local variables and constants. Sticking to this convention helps readers (us) see very clearly whether a variable is entirely contained in local state or exists globally somewhere in your program.

    Nice job importing System.Math - it's the right thing to do in a situation like this.

    Regards,

    -ADG


    Anthony D. Green | Program Manager | Visual Basic & C# Languages Team

    • Marked as answer by BGQQ Tuesday, April 16, 2013 7:58 PM
    Monday, April 15, 2013 9:05 PM
    Moderator
  • If you're new to VB, this might help you understand the language/platform better:


    Public Class Form1

        Private Sub Button1_Click() Handles Button1.Click

            ' Determinant = 6
            Dim matrix As Double(,) = {{1, 1, 0},
                                       {2, -1, 1},
                                       {3, 3, -2}}

            '' Determinant = 17
            'matrix = {{1, 3, 2},
            '          {4, 1, 3},
            '          {2, 5, 2}}

            '' Determinant = 0
            'matrix = {{1, 2, 3},
            '          {4, 5, 6},
            '          {7, 8, 9}}

            '' Determinant = 0
            'matrix = {{1, 2, 3, 4},
            '          {5, 6, 7, 8},
            '          {9, 10, 11, 12},
            '          {13, 14, 15, 16}}

            '' Determinant = 6
            'matrix = {{-2, 2, 3},
            '          {-1, 1, 3},
            '          {2, 0, -1}}

            Text = ComputeDeterminant(matrix).ToString()
        End Sub

        Public Function ComputeDeterminant(matrix As Double(,)) As Double
            Const rowDimension = 0, columnDimension = 1

            If matrix Is Nothing Then Throw New ArgumentNullException("matrix")
            If matrix.GetLength(rowDimension) <> matrix.GetLength(columnDimension) Then Throw New ArgumentException("'matrix' must be a square matrix.", "matrix")

            Select Case matrix.GetLength(rowDimension)
                Case 0
                    Return 1

                Case 1
                    Return matrix(0, 0)

                Case 2
                    Return matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0)

                Case Else

                    Dim createSubmatrix = Function(sourceMatrix As Double(,), excludedRow As Integer, excludedColumn As Integer) As Double(,)

                                              Dim targetMatrix = New Double(0 To sourceMatrix.GetUpperBound(rowDimension) - 1, 0 To sourceMatrix.GetUpperBound(columnDimension) - 1) {}

                                              Dim targetRow = 0

                                              For sourceRow = 0 To sourceMatrix.GetUpperBound(rowDimension)
                                                  If sourceRow = excludedRow Then Continue For

                                                  Dim targetColumn = 0

                                                  For sourceColumn = 0 To sourceMatrix.GetUpperBound(columnDimension)
                                                      If sourceColumn = excludedColumn Then Continue For

                                                      targetMatrix(targetRow, targetColumn) = sourceMatrix(sourceRow, sourceColumn)

                                                      targetColumn += 1
                                                  Next

                                                  targetRow += 1
                                              Next

                                              Return targetMatrix
                                          End Function

                    Dim sign = 1.0

                    Dim sum = 0.0

                    For c = 0 To matrix.GetUpperBound(columnDimension)

                        sum += matrix(0, c) * sign * ComputeDeterminant(createSubmatrix(matrix, 0, c))

                        sign *= -1.0
                    Next

                    Return sum
            End Select

        End Function

    End Class

     

    Regards,

    -ADG


    Anthony D. Green | Program Manager | Visual Basic & C# Languages Team


    Tuesday, April 16, 2013 12:43 AM
    Moderator
  • I don't think that it is important what the C# code does. 

    It simply goes in a also for me undefinable loop. Simply set a breakpoint under the method Det at the first line you will see that it goes wrong. And therefore gives a stack overflow.

    I think that the best thing you can do is filing a bug from this by clicking on the button for that "file a bug". Maybe the reason is that strange and by me very much disliked array which was created to keep VB6 more compatible with VB7 by strong demands of VB6 MVP's.

    But you keep in code in my idea everything correct with that by completely ignoring that strange way and hard coding the bounds by setting the length in your code (completely the same as C#).

    So whatever it is, this kind of recursive behaviour should not happen.

     


    Success
    Cor

    Tuesday, April 16, 2013 8:29 AM
  • Thank you all very much...

    Your posts are very helpful.

    I really missed the array boundary conventions in VB in my code.

    Tuesday, April 16, 2013 8:03 PM

  • No need to make the 'matrix' parameter ByRef, in fact it's more dangerous if you do. Unless your function is going to modify the variable of the caller you don't need it to be ByRef. Note that this is not the same as modifying the matrix passed in but rather modifying which matrix the variable refers to entirely. 

    Thank you very much for your helpful posts and suggestions, but as I know that arrays are always passed by reference even if passed with the "ByVal" keyword, so I feel it is more convenient to pass it with "ByRef" keyword to be compatible with the real behavior. At the end, even with "ByVal" keyword, any changes will affect the original array. 
    Tuesday, April 16, 2013 8:11 PM
  • " to keep VB6 more compatible with VB7 by strong demands of VB6 MVP's."

    What in the world is VB7?

    Renee


    "MODERN PROGRAMMING is deficient in elementary ways BECAUSE of problems INTRODUCED by MODERN PROGRAMMING." Me

    Wednesday, April 17, 2013 3:04 AM
  • " to keep VB6 more compatible with VB7 by strong demands of VB6 MVP's."

    What in the world is VB7?

    Renee


    "MODERN PROGRAMMING is deficient in elementary ways BECAUSE of problems INTRODUCED by MODERN PROGRAMMING." Me

    The version name of the program language which has as product name VB.Net and currently also often is called 2002. It uses Framework 1.0. 

    The successor was VB 7.1 also which has as productname VB.Net 2003

    By the way, is this constant terroristic behaviour of thread hacking really needed. 


    Success
    Cor

    Wednesday, April 17, 2013 11:54 AM

  • 'INSTANT VB NOTE: The variable size was renamed since Visual Basic does not handle local variables named the same as class members well:

    Good catch Dave. Or was it your converter.

    :-)


    Success
    Cor

    Wednesday, April 17, 2013 11:59 AM
  • "By the way, is this constant terroristic behaviour of thread hacking really needed. "

    It was exactly on your topic.

    Renee


    "MODERN PROGRAMMING is deficient in elementary ways BECAUSE of problems INTRODUCED by MODERN PROGRAMMING." Me

    Wednesday, April 17, 2013 12:42 PM
  • Arrays are not always passed by reference.  If the ByVal keyword is used or omitted, then they are passed by value.  The question is what is passed.  When an array is passed "ByVal" then a copy of the reference is passed.  Since the copy of the reference points to the original array, you can change the *contents* of the array but you cannot change the array reference itself.    The difference is subtle with arrays.  Here is some code that illustrates the point:

    Sub Main()
    
        'Create an array of int
        Dim arrayOfInt = {1, 2, 3, 4}
    
        Console.WriteLine("Original array...")
    
        'prints 1, 2, 3, 4
        For Each i As Integer In arrayOfInt
            Console.WriteLine(i)
        Next
    
        'call method passing ByVal
        passArrayByVal(arrayOfInt)
    
        Console.WriteLine("Array in calling method still points to the original array")
    
        'still prints 1, 2, 3, 4
        For Each i As Integer In arrayOfInt
            Console.WriteLine(i)
        Next
    
        'call method passing ByRef
        passArrayByRef(arrayOfInt)
    
        Console.WriteLine("Array in calling method now points to the new array")
    
        'prints 10, 11, 12, 13, 14, 15, 16, 17 because the array reference was changed
        For Each i As Integer In arrayOfInt
            Console.WriteLine(i)
        Next
    
        Console.ReadLine()
    End Sub
    
    Private Sub passArrayByVal(ByVal array As Integer())
        'Change the array to a new larger array with different values
        'this will NOT be reflected in the array passed in
        array = {9, 8, 7, 6, 5, 4, 3, 2, 1}
    
        Console.WriteLine("Inside passArrayByVal...")
    
        For Each i As Integer In array
            Console.WriteLine(i)
        Next
    
    End Sub
    
    Private Sub passArrayByRef(ByRef array As Integer())
        'Change the array to a new larger array with different values
        'this WILL be reflected in the array passed in
        array = {10, 11, 12, 13, 14, 15, 16, 17}
    
        Console.WriteLine("Inside passArrayByRef...")
    
        For Each i As Integer In array
            Console.WriteLine(i)
        Next
    End Sub


    Wednesday, April 17, 2013 6:42 PM