0
\$\begingroup\$

I have written a class in VB.NET that has the task of applying various arithmetic operations to two complex numbers. The class can also determine the magnitude and phase of a complex number. The project is just an exercise, but I want to make it as good as possible.

For each calculation, 3 strings are created for output – as rectangular form, as trigonometric form and as polar form. The method "SetOutputStrings" needs the methods "AbsoluteValue" and "AngleInRad" for this, for which I need new instances of ComplexNumber. I need to pass a Boolean to avoid an overflow. Outside, in FormMain, I need to pass ‘False’ if I simply want to create two complex numbers that will be used in the respective method, and to avoid creating unnecessary OutputStrings. To be precise, I don't want to have to have a Boolean as a parameter for the constructor. Can you guys get this to work more elegantly?

Visual Studio 2019, WinForms application

Class ComplexNumber.vb

Friend NotInheritable Class ComplexNumber
    Private _Real As Double
    Private _Imag As Double
    Private _Output_Rectangular_form As String
    Private _Output_Exponential_form As String
    Private _Output_Trigonometric_form As String

    Friend Property Output_Rectangular_form As String ' 3+4⋅i
        Get
            Return _Output_Rectangular_form
        End Get
        Private Set
            _Output_Rectangular_form = Value
        End Set
    End Property

    Friend Property Output_Trigonometric_form As String ' r⋅(cos(ϕ)+i⋅sin(ϕ))
        Get
            Return _Output_Trigonometric_form
        End Get
        Private Set
            _Output_Trigonometric_form = Value
        End Set
    End Property

    Friend Property Output_Exponential_form As String ' r⋅e^(i⋅ϕ)
        Get
            Return _Output_Exponential_form
        End Get
        Private Set
            _Output_Exponential_form = Value
        End Set
    End Property

    Friend Property Real As Double
        Get
            Return _Real
        End Get
        Private Set
            _Real = Value
        End Set
    End Property

    Friend Property Imag As Double
        Get
            Return _Imag
        End Get
        Private Set
            _Imag = Value
        End Set
    End Property

    Friend Sub New(real As Double, imag As Double, setOutput As Boolean)
        Me.Real = real
        Me.Imag = imag
        If setOutput Then
            SetOutputStrings(real, imag)
        End If
    End Sub

    Friend Shared Function AbsoluteValue(v As ComplexNumber) As Double
        Return Math.Sqrt(Math.Pow(v.Real, 2.0) + Math.Pow(v.Imag, 2.0))
    End Function

    Friend Shared Function AngleInDegrees(v As ComplexNumber) As Double
        Dim phase As Double = Math.Atan(v.Imag / v.Real)

        If v.Real >= 0.0 AndAlso v.Imag >= 0.0 Then
            Return phase * 180.0 / Math.PI
        ElseIf v.Real < 0.0 AndAlso v.Imag >= 0.0 Then
            Return (phase * 180.0 / Math.PI) + 180.0
        ElseIf v.Real < 0.0 AndAlso v.Imag < 0.0 Then
            Return (phase * 180.0 / Math.PI) + 180.0
        ElseIf v.Real >= 0.0 AndAlso v.Imag < 0.0 Then
            Return (phase * 180.0 / Math.PI) + 360.0
        Else ' won't happen but I must have an ‘Return’ here
            Return Double.MaxValue
        End If
    End Function

    Friend Shared Function Add(v1 As ComplexNumber, v2 As ComplexNumber) As ComplexNumber
        Return New ComplexNumber(v1.Real + v2.Real, v1.Imag + v2.Imag, True)
    End Function

    Friend Shared Function Subtract(v1 As ComplexNumber, v2 As ComplexNumber) As ComplexNumber
        Return New ComplexNumber(v1.Real - v2.Real, v1.Imag - v2.Imag, True)
    End Function

    Friend Shared Function Multiply(v1 As ComplexNumber, v2 As ComplexNumber) As ComplexNumber
        Return New ComplexNumber(v1.Real * v2.Real - v1.Imag * v2.Imag,
                                 v1.Real * v2.Imag + v1.Imag * v2.Real,
                                 True)
    End Function

    Friend Shared Function Divide(v1 As ComplexNumber, v2 As ComplexNumber) As ComplexNumber
        ' a+b*i   c-d*i     a*c+b*d -a*d*i +b*c*i
        ' ––––– * ––––– =  –––––––––––––––––––––––
        ' c+d*i   c-d*i        c^2 + d^2

        Dim realPart As Double = v1.Real * v2.Real + v1.Imag * v2.Imag

        Dim denominator As Double = Math.Pow(v2.Real, 2.0) + Math.Pow(v2.Imag, 2.0)

        Dim imaginaryPart As Double = -v1.Real * v2.Imag + v1.Imag * v2.Real

        Return New ComplexNumber(realPart / denominator, imaginaryPart / denominator, True)
    End Function

    Friend Shared Function Conjugate(v1 As ComplexNumber) As ComplexNumber
        Return New ComplexNumber(v1.Real, -v1.Imag, True)
    End Function

    Friend Shared Function Inverse(v1 As ComplexNumber) As ComplexNumber
        Dim conj As ComplexNumber = Conjugate(v1)
        Return New ComplexNumber(conj.Real / (Math.Pow(v1.Real, 2.0) + Math.Pow(v1.Imag, 2.0)),
                                 conj.Imag / (Math.Pow(v1.Real, 2.0) + Math.Pow(v1.Imag, 2.0)),
                                 True)
    End Function

    Private Sub SetOutputStrings(real As Double, imag As Double)
        Dim deu As New System.Globalization.CultureInfo("de-DE")
        Dim plusOrMinus As String
        If imag >= 0.0 Then
            plusOrMinus = " + "
        Else
            plusOrMinus = " "
        End If
        Me.Output_Rectangular_form = $"{Math.Round(real, 3).ToString(deu)}{plusOrMinus}{Math.Round(imag, 3).ToString(deu)} * i"
        Dim cN As New ComplexNumber(real, imag, False)
        Dim absValue As Double = AbsoluteValue(cN)
        Dim angle As Double = AngleInRad(cN)
        Me.Output_Exponential_form = Math.Round(absValue, 3).ToString(deu) + $" * e^({Math.Round(angle, 3).ToString(deu)} * i)"
        Me.Output_Trigonometric_form = Math.Round(absValue, 3).ToString(deu) + $" * (cos({Math.Round(angle, 3).ToString(deu)}) + i * sin({Math.Round(angle, 3).ToString(deu)}))"
    End Sub

    Friend Shared Function AngleInRad(v As ComplexNumber) As Double
        Dim phase As Double = Math.Atan(v.Imag / v.Real)

        If v.Real >= 0.0 AndAlso v.Imag >= 0.0 Then
            Return phase
        ElseIf v.Real < 0.0 AndAlso v.Imag >= 0.0 Then
            Return phase + Math.PI
        ElseIf v.Real < 0.0 AndAlso v.Imag < 0.0 Then
            Return phase + Math.PI
        ElseIf v.Real >= 0.0 AndAlso v.Imag < 0.0 Then
            Return phase + 2.0 * Math.PI
        Else ' won't happen but I must have an ‘Return’ here
            Return Double.MaxValue
        End If
    End Function
End Class

FormMain.vb

Public NotInheritable Class FormMain
    Private Real1, Imag1, Real2, Imag2 As Double
    Private Sub Textbox_r1_TextChanged(sender As Object, e As EventArgs) Handles Textbox_r1.TextChanged
        Double.TryParse(Textbox_r1.Text, Real1)
    End Sub

    Private Sub Textbox_i1_TextChanged(sender As Object, e As EventArgs) Handles Textbox_i1.TextChanged
        Double.TryParse(Textbox_i1.Text, Imag1)
    End Sub

    Private Sub Textbox_r2_TextChanged(sender As Object, e As EventArgs) Handles Textbox_r2.TextChanged
        Double.TryParse(Textbox_r2.Text, Real2)
    End Sub

    Private Sub Textbox_i2_TextChanged(sender As Object, e As EventArgs) Handles Textbox_i2.TextChanged
        Double.TryParse(Textbox_i2.Text, Imag2)
    End Sub

    Private Sub Button_absolute_value_Click(sender As Object, e As EventArgs) Handles Button_absolute_value.Click
        Dim cN1 As New ComplexNumber(Me.Real1, Me.Imag1, True)
        Debug.WriteLine(ComplexNumber.AbsoluteValue(cN1))
    End Sub

    Private Sub Button_angle_Click(sender As Object, e As EventArgs) Handles Button_angle.Click
        Dim cN1 As New ComplexNumber(Me.Real1, Me.Imag1, True)
        Dim deu As New System.Globalization.CultureInfo("de-DE")
        Debug.WriteLine(ComplexNumber.AngleInDegrees(cN1).ToString(deu) + " °")
        Debug.WriteLine(ComplexNumber.AngleInRad(cN1).ToString(deu))
    End Sub

    Private Sub Button_add_Click(sender As Object, e As EventArgs) Handles Button_add.Click
        Dim cN1 As New ComplexNumber(Me.Real1, Me.Imag1, False)
        Dim cN2 As New ComplexNumber(Me.Real2, Me.Imag2, False)
        Dim result As ComplexNumber = ComplexNumber.Add(cN1, cN2)
        Debug.WriteLine(result.Output_Rectangular_form())
        Debug.WriteLine(result.Output_Exponential_form())
        Debug.WriteLine(result.Output_Trigonometric_form())
    End Sub

    Private Sub Button_subtract_Click(sender As Object, e As EventArgs) Handles Button_subtract.Click
        Dim cN1 As New ComplexNumber(Me.Real1, Me.Imag1, False)
        Dim cN2 As New ComplexNumber(Me.Real2, Me.Imag2, False)
        Dim result As ComplexNumber = ComplexNumber.Subtract(cN1, cN2)
        Debug.WriteLine(result.Output_Rectangular_form())
        Debug.WriteLine(result.Output_Exponential_form())
    End Sub

    Private Sub Button_multiply_Click(sender As Object, e As EventArgs) Handles Button_multiply.Click
        Dim cN1 As New ComplexNumber(Me.Real1, Me.Imag1, False)
        Dim cN2 As New ComplexNumber(Me.Real2, Me.Imag2, False)
        Dim result As ComplexNumber = ComplexNumber.Multiply(cN1, cN2)
        Debug.WriteLine(result.Output_Rectangular_form())
        Debug.WriteLine(result.Output_Exponential_form())
    End Sub

    Private Sub Button_divide_Click(sender As Object, e As EventArgs) Handles Button_divide.Click
        Dim cN1 As New ComplexNumber(Me.Real1, Me.Imag1, False)
        Dim cN2 As New ComplexNumber(Me.Real2, Me.Imag2, False)
        Dim result As ComplexNumber = ComplexNumber.Divide(cN1, cN2)
        Debug.WriteLine(result.Output_Rectangular_form())
        Debug.WriteLine(result.Output_Exponential_form())
    End Sub

    Private Sub Button_conj_Click(sender As Object, e As EventArgs) Handles Button_conj.Click
        Dim cN1 As New ComplexNumber(Me.Real1, Me.Imag1, False)
        Dim result As ComplexNumber = ComplexNumber.Conjugate(cN1)
        Debug.WriteLine(result.Output_Rectangular_form())
        Debug.WriteLine(result.Output_Exponential_form())
    End Sub

    Private Sub Button_inverse_Click(sender As Object, e As EventArgs) Handles Button_inverse.Click
        Dim cN1 As New ComplexNumber(Me.Real1, Me.Imag1, False)
        Dim result As ComplexNumber = ComplexNumber.Inverse(cN1)
        Debug.WriteLine(result.Output_Rectangular_form())
        Debug.WriteLine(result.Output_Exponential_form())
    End Sub
End Class
```
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

First of all, although you only want to get rid of the boolean parameter, let us look at the code and see if we can improve it.

I would ask myself, do the methods AbsoluteValue() and AngleInDegrees() have to be Friend Shared functions? Performance should not be an issue because you call these methods each time you pass True in the constructor hence you could always calculate these values and provide them as a property of your class.

Because you assign values to Imag and Real inside the constructor you should make them Readonly properties.

Mixing languages while coding isn't that good. You should name stuff in just one language. So Dim deu As New System.Globalization.CultureInfo("de-DE") should be Dim german As New System.Globalization.CultureInfo("de-DE") or germanCulture. While coding you should always keep in mind that maybe Sam the maintainer won't speak German but mostly will speak English.

But let us stay at that CultureInfo object. Wouldn't it be better to have this as an optional parameter for the constructor?

About naming I would also like to mention that there exists Naming guidlines which should be taken into account when naming stuff. Like e.g Output_Trigonometric_form should at least be OutputTrigonometricForm or imo better TrigonometricForm because Output doesn't add any value.

If you use one style you should keep to it. Having a look at SetOutputStrings() shows

Me.Output_Rectangular_form = $"{Math.Round(real, 3).ToString(deu)}{plusOrMinus}{Math.Round(imag, 3).ToString(deu)} * i"

and

 Me.Output_Exponential_form = Math.Round(absValue, 3).ToString(deu) + $" * e^({Math.Round(angle, 3).ToString(deu)} * i)"  

using two different styles.

AngleInDegrees() and AngleInRad()

You each have a Else here with the comment

won't happen but I must have an ‘Return’ here

which you could remove by changing the last ElseIf to an Else. But if you want to show the condition of the last evaluation it is imo ok to just throw an exception instead of returning some value.

Well, now I will show you both ways about your problem with the setOutput parameter. You could have an overloaded constructor in which you call the former constructor passing the desired value for setOutput like so

Friend Sub New(real As Double, imag As Double)
    Me.New(real, imag, False)
End Sub

But it really could be much simpler. Instead of passing a new ComplexNumber to AbsoluteValue() and AngleInRad() you could just pass Me as the argument.

But now let us take a look at the refactoring I suggested

Friend NotInheritable Class ComplexNumber

    Private plusOrMinus As String = " "
    Friend ReadOnly Property Radiant As Double
    Friend ReadOnly Property AbsoluteValue As Double
    Friend ReadOnly Property RectangularForm As String ' 3+4⋅i
    Friend ReadOnly Property TrigonometricForm As String ' r⋅(cos(ϕ)+i⋅sin(ϕ))
    Friend ReadOnly Property ExponentialForm As String ' r⋅e^(i⋅ϕ)
    Friend ReadOnly Property Real As Double
    Friend ReadOnly Property Imag As Double

    Friend Sub New(real As Double, imag As Double, Optional currentCulture As System.Globalization.CultureInfo = Nothing)

        Me.Real = real
        Me.Imag = imag

        If currentCulture Is Nothing Then
            currentCulture = New System.Globalization.CultureInfo("de-DE")
        End If

        If imag >= 0.0 Then
            plusOrMinus = " + "
        End If

        AbsoluteValue = Math.Sqrt(Math.Pow(real, 2.0) + Math.Pow(imag, 2.0))
        Radiant = AngleInRad(Me)

        RectangularForm = $"{Math.Round(real, 3).ToString(currentCulture)}{plusOrMinus}{Math.Round(imag, 3).ToString(currentCulture)} * i"
        ExponentialForm = $"{Math.Round(AbsoluteValue, 3).ToString(currentCulture)} * e^({Math.Round(Radiant, 3).ToString(currentCulture)} * i)"
        TrigonometricForm = $"{Math.Round(AbsoluteValue, 3).ToString(currentCulture)} * (cos({Math.Round(Radiant, 3).ToString(currentCulture)}) + i * sin({Math.Round(Radiant, 3).ToString(currentCulture)}))"

    End Sub

    Friend Shared Function AngleInDegrees(v As ComplexNumber) As Double
        Dim phase As Double = Math.Atan(v.Imag / v.Real)

        If v.Real >= 0.0 AndAlso v.Imag >= 0.0 Then
            Return phase * 180.0 / Math.PI
        ElseIf v.Real < 0.0 AndAlso v.Imag >= 0.0 Then
            Return (phase * 180.0 / Math.PI) + 180.0
        ElseIf v.Real < 0.0 AndAlso v.Imag < 0.0 Then
            Return (phase * 180.0 / Math.PI) + 180.0
        Else
            Return (phase * 180.0 / Math.PI) + 360.0
        End If
    End Function

    Friend Shared Function Add(v1 As ComplexNumber, v2 As ComplexNumber, Optional currentCulture As System.Globalization.CultureInfo = Nothing) As ComplexNumber
        Return New ComplexNumber(v1.Real + v2.Real, v1.Imag + v2.Imag, currentCulture)
    End Function

    Friend Shared Function Subtract(v1 As ComplexNumber, v2 As ComplexNumber, Optional currentCulture As System.Globalization.CultureInfo = Nothing) As ComplexNumber
        Return New ComplexNumber(v1.Real - v2.Real, v1.Imag - v2.Imag, currentCulture)
    End Function

    Friend Shared Function Multiply(v1 As ComplexNumber, v2 As ComplexNumber, Optional currentCulture As System.Globalization.CultureInfo = Nothing) As ComplexNumber
        Return New ComplexNumber(v1.Real * v2.Real - v1.Imag * v2.Imag,
                                 v1.Real * v2.Imag + v1.Imag * v2.Real, currentCulture)
    End Function

    Friend Shared Function Divide(v1 As ComplexNumber, v2 As ComplexNumber, Optional currentCulture As System.Globalization.CultureInfo = Nothing) As ComplexNumber
        ' a+b*i   c-d*i     a*c+b*d -a*d*i +b*c*i
        ' ––––– * ––––– =  –––––––––––––––––––––––
        ' c+d*i   c-d*i        c^2 + d^2

        Dim realPart As Double = v1.Real * v2.Real + v1.Imag * v2.Imag

        Dim denominator As Double = Math.Pow(v2.Real, 2.0) + Math.Pow(v2.Imag, 2.0)

        Dim imaginaryPart As Double = -v1.Real * v2.Imag + v1.Imag * v2.Real

        Return New ComplexNumber(realPart / denominator, imaginaryPart / denominator, currentCulture)
    End Function

    Friend Shared Function Conjugate(v1 As ComplexNumber, Optional currentCulture As System.Globalization.CultureInfo = Nothing) As ComplexNumber
        Return New ComplexNumber(v1.Real, -v1.Imag, currentCulture)
    End Function

    Friend Shared Function Inverse(v1 As ComplexNumber, Optional currentCulture As System.Globalization.CultureInfo = Nothing) As ComplexNumber
        Dim conj As ComplexNumber = Conjugate(v1)
        Return New ComplexNumber(conj.Real / (Math.Pow(v1.Real, 2.0) + Math.Pow(v1.Imag, 2.0)),
                                 conj.Imag / (Math.Pow(v1.Real, 2.0) + Math.Pow(v1.Imag, 2.0)), currentCulture)
    End Function

    Friend Shared Function AngleInRad(v As ComplexNumber) As Double
        Dim phase As Double = Math.Atan(v.Imag / v.Real)

        If v.Real >= 0.0 AndAlso v.Imag >= 0.0 Then
            Return phase
        ElseIf v.Real < 0.0 AndAlso v.Imag >= 0.0 Then
            Return phase + Math.PI
        ElseIf v.Real < 0.0 AndAlso v.Imag < 0.0 Then
            Return phase + Math.PI
        Else
            Return phase + 2.0 * Math.PI
        End If
    End Function
End Class
\$\endgroup\$
1
  • \$\begingroup\$ Hi Heslacher, thanks for the solution and tips! Specially to pass ‘Me’ as argument – because of such improvements I post here :) You are right, the strings can be properties, too. By the way: I had made the properties ‘Real’ and ‘Imag’ somewhat readonly, since I wrote ‘Private Set’. But your suggestion is better. \$\endgroup\$
    – Daniel
    Jul 17, 2023 at 17:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.