Doberenz_gewinnus_ Arrays mit LINQ initialisieren5In dieser Blogserie möchten wir Ihnen eine kleine Auswahl aus den „Rezepten“ unserer neuen Titel Visual Basic 2015 und Visual C# 2015 präsentieren und Ihnen als künftigen oder fortgeschrittenen VB- oder C#-Programmierer einen Vorgeschmack darauf geben, wie Sie einzelne Komponenten für das Microsoft .NET Framework programmieren können. Die ersten Rezepte, Arrays mit LINQ initialisieren, Echte ZIP-Dateien erstellen und Eine Aktionsabfrage aufrufen, haben wir Ihnen schon geliefert, heute geht es darum, Rechner für komplexe Zahlen zu programmieren.

 

Los gehts mit dem Visual C#-Rezept, Rechner für komplexe Zahlen:

Auch mit dieser Anwendung wollen wir nicht nur die Lösung eines mathematischen Problems zeigen, sondern (was viel wichtiger ist) grundlegendes Hand­werks­­zeug des .NET-Program­­­mierers demonstrieren:

  • Sinnvolle Auslagerung von Quellcode in Klassen, um das Verständnis der OOP zu vertiefen,
  • Prinzip der Operatorenüberladung in Visual Basic,
  • Strukturierung des Codes der Benutzerschnittstelle nach dem EVA-Prinzip (Eingabe – Ver­arbeitung – Ausgabe).

Doch ehe wir mit der Praxis beginnen, scheint ein kurzer Abstieg in die Untiefen der Mathe­matik unumgänglich.

Was sind komplexe Zahlen?

Eine besondere Bedeutung haben komplexe Zahlen beispielsweise in der Schwingungs­lehre und in der Wechselstromtechnik, einem bedeutenden Teilgebiet der Elektrotechnik.

Zur Darstellung einer komplexen Zahl Z bieten sich zwei Möglichkeiten an:

  • Kartesische Koordinaten (Real-/Imaginärteil)
  • Polarkoordinaten (Betrags-/Winkeldarstellung)

Die folgende Tabelle zeigt eine Zusammenstellung der Umrechnungsformeln:

Kartesische Koordinaten Polarkoordinaten
Z = Re{Z} + jIm{Z}

Realteil: Re{Z} = |Z| cos jz

Imaginärteil: Im{Z} = |Z| sin jz

Z = |Z| ejjz

Betrag:

Phasenwinkel:

Am besten lassen sich diese Zusammenhänge am Einheitskreis erläutern, wobei Z als Punkt in der komplexen Ebene erscheint:

Abb. Einheitskreis aus dem Fachbuch Visual Basic 2015 von Walter Doberenz und Thomas Gewinnus, Carl Hanser Verlag, München 2015.

Abb. Einheitskreis aus dem Fachbuch Visual Basic 2015 von Walter Doberenz und Thomas Gewinnus, Carl Hanser Verlag, München 2015.

Die kartesische Form eignet sich besonders gut für die Ausführung von Addition und Subtrak­tion:

Mit

Z1 = a1 + jb1 und Z2 = a2 + jb2

ergibt sich

Z1 + Z2 = a1 + a2 + j(b1 + b2) bzw. Z1 – Z2 = a1 – a2 + j(b1 – b2)

Andererseits bevorzugt man für Multiplikation und Division die Zeigerform:

Mit

Z1 = c1 · ejj1 und Z2 = c2 · ejj2

erhalten wir

Z1 · Z2 = c1 · c2 · ej(j1 + j2) bzw. Z1/Z2 = c1/c2 · ej(j1 – j2)

Für die Angabe des Phasenwinkels hat man die Wahl zwischen Radiant (Bogenmaß) und Grad. Für die gegenseitige Umrechnung gilt die Beziehung.

Hinweis: Die Maßeinheit „Grad“ wird aufgrund ihrer Anschaulichkeit vom Praktiker für die Ein- und Ausgabe bevorzugt, während „Radiant“ für interne Berechnungen günstiger ist.

Programmierung der Klasse CComplexN

Öffnen Sie ein neues Projekt vom Typ Windows Forms-Anwendung. Das Startformular Form1 lassen Sie zunächst unbeachtet liegen, denn der routinierte .NET-Programmierer kapselt seinen Code in Klassen, anstatt ihn einfach zum Formularcode hinzuzufügen.

Die zweckmäßige Aufteilung einer praktischen Problemstellung in verschiedene Klassen und die Definition der Abhängigkeiten ist sicherlich der schwierigste Part der OOP und erfordert einige Übung und Routine, bis das dazu erforderliche abstrakte Denken schließlich zur Selbstverständlichkeit wird[1]

Die hier vorgeschlagene Lösung benutzt die Klasse CComplexN, welche eine komplexe Zahl repräsentiert. Diese Klasse speichert in den Zustandsvariablen Re und Im (die in unserem Fall gleichzeitig Eigenschaften sind) den Wert der komplexen Zahl in Kartesischen Koordinaten. Die beiden anderen Eigenschaften (Len und Ang) repräsentieren dieselbe Zahl in Polar-Koordinaten. Allerdings werden Len und Ang nicht direkt in den Objekten gespeichert, sondern in so genannten Eigenschaftenmethoden (property procedures) jeweils aus Re und Im be­rechnet.

Über das Menü Projekt|Klasse hinzufügen… erstellen Sie den Rahmencode der Klasse.

Public Class CComplexN
Die beiden öffentlichen Zustandsvariablen Re und Im bilden das "Gedächtnis" 
der Klasse und können quasi wie Eigenschaften benutzt werden[2]:
Public Re, Im As Double ' Real- und Imaginärteil
Ein Konstruktor (den Rahmencode können Sie sich von der IDE erzeugen lassen) 
setzt die Zustandsvariablen auf ihre Anfangswerte:
Public Sub New(r As Double, i As Double)
Re = r : Im = i
End Sub
Die "intelligente" Eigenschaftsmethode Ang berechnet den Phasenwinkel 
aus den Zustands­variablen Re und Im:
Public Property Ang() As Double
Get
Dim g As Double = 0
If Re <> 0 Then
g = 180 / Math.PI * Math.Atan(Im / Re)
If Re < 0 Then g += 180
Else
If Im <> 0 Then
If Im > 0 Then g = 90
Else
g = -90
End If
End If
Return g
End Get
Set(value As Double)
Dim b, l As Double
b = value * Math.PI / 180
l = Math.Sqrt(Re * Re + Im * Im)
Re = l * Math.Cos(b) ' neuer Realteil
Im = l * Math.Sin(b) ' neuer Imaginärteil
End Set
End Property
Die Eigenschaft Len ermittelt den Betrag (die Länge des Zeigers) 
aus Re und Im:
Public Property Len() As Double
Get
Return Math.Sqrt(Re * Re + Im * Im)
End Get
Set(value As Double)
Dim b As Double = Math.Atan(Im / Re)
Re = value * Math.Cos(b)
Im = value * Math.Sin(b)
End Set
End Property
Besonders interessant sind die folgenden drei (statischen) Methoden, 
welche die Operatoren­­überladungen für Addition, Multiplikation und Division 
neu definieren.
Der "+"-Operator erhält eine neue Bedeutung, er addiert jetzt zwei 
komplexe Zahlen:
Public Shared Operator +(a As CComplexN, b As CComplexN) As CComplexN
Dim z As New CComplexN(0, 0)
z.Re = a.Re + b.Re
z.Im = a.Im + b.Im
Return z
End Operator
Der "*"-Operator multipliziert zwei komplexe Zahlen:
Public Shared Operator *(a As CComplexN, b As CComplexN) As CComplexN
Dim z As New CComplexN(0, 0)
z.Re = a.Re * b.Re - a.Im * b.Im
z.Im = a.Re * b.Im + a.Im * b.Re
Return z
End Operator
Der "/"-Operator dividiert zwei komplexe Zahlen:
Public Shared Operator /(a As CComplexN, b As CComplexN) As CComplexN
Dim z As New CComplexN(0, 0)
z.Re = (a.Re * b.Re + a.Im * b.Im) / (b.Re * b.Re + b.Im * b.Im)
z.Im = (a.Im * b.Re - a.Re * b.Im) / (b.Re * b.Re + b.Im * b.Im)
Return z
End Operator
End Class

 

Hinweis: Vielleicht sticht Ihnen bereits jetzt ein gravierender Unterschied zur klassischen „Geradeausprogrammierung“ ins Auge: Spezielle Methoden zur Umrechnung zwischen Kartesischen- und Polarkoordinaten sind Fehlanzeige, da ein Objekt vom Typ CComplexN beide Darstellungen bereits als Eigenschaften kapselt!

Bedienoberfläche für Testprogramm

Um uns von der Funktionsfähigkeit der entwickelten Klassen zu überzeugen, brauchen wir ein kleines Test­programm, das die Ein- und Ausgabe von komplexen Zahlen und die Auswahl der Rechenoperation sowie der Koordinatendarstellung ermöglicht.

Wir verwenden dazu das bereits vorhandene Startformular Form1, das wir entsprechend der folgenden Abbildung gestalten.

Hinweis: Es kann nicht schaden, wenn Sie ReadOnly für TextBox3 und TextBox6 auf True und TabStop auf False setzen, da Sie diese beiden rechten Felder nur zur Er­gebnis­­anzeige brauchen.

Abb. Rechner für komplexe Zahlen, S. 235, aus dem Fachbuch Visual C# von Walter Doberenz und Thomas Gewinnus, Carl Hanser Verlag, München 2015.

Abb. Rechner für komplexe Zahlen, S. 235, aus dem Fachbuch Visual C# von Walter Doberenz und Thomas Gewinnus, Carl Hanser Verlag, München 2015.

Quellcode für Testprogramm

Das an legendäre DOS-Zeiten erinnernde EVA-Prinzip (Eingabe, Verarbeitung, Anzeige) hat auch unter .NET nichts von seiner grundlegenden Be­deu­tung eingebüßt.

Der clientseitige Quellcode entspricht vom prinzipiellen Ablauf her der klassischen Gerade­aus­programmierung, ist allerdings deutlich übersichtlicher und problemnäher, denn wir ar­beiten mit drei Objektvariablen, die bereits komplexe Zahlen sind und nicht mit einer Vielzahl skalarer Variablen!

Public Class Form1
Die benötigten Objektvariablen:
Private A As New CComplexN(1, 1) ' Operand A
Private B As New CComplexN(1, 1) ' Operand B
Private Z As New CComplexN(0, 0) ' Ergebnis Z
Unter Berücksichtigung der eingestellten Anzeigeart (Rechteck- 
oder Polarkoordinaten) liest die folgende Methode die Werte aus der 
Eingabemaske in die Objekte:

Private Sub Eingabe()
If RadioButton4.Checked Then ' Rechteck-Koordinaten
A.Re = Convert.ToDouble(TextBox1.Text)
B.Re = Convert.ToDouble(TextBox2.Text)
A.Im = Convert.ToDouble(TextBox4.Text)
B.Im = Convert.ToDouble(TextBox5.Text)
Else ' Polar-Koordinaten
A.Len = Convert.ToDouble(TextBox1.Text)
B.Len = Convert.ToDouble(TextBox2.Text)
A.Ang = Convert.ToDouble(TextBox4.Text)
B.Ang = Convert.ToDouble(TextBox5.Text)
End If
End Sub
Die Verarbeitungsroutine führt die gewünschte Rechenoperation mit den 
bekannten Symbolen für Addition, Multiplikation und Division aus. Dazu 
werden die in der Klasse CComplexN definierten Operatorenüberladungen 
benutzt:
Private Sub Verarbeitung()
If RadioButton1.Checked Then Z = A + B ' Addition
If RadioButton2.Checked Then Z = A * B ' Multiplikation
If RadioButton3.Checked Then Z = A / B ' Division
End Sub
Als Pendant zur Eingabe-Methode sorgt die Methode Ausgabe für die Anzeige 
von A, B und Z, wozu auch die Anpassung der Beschriftung der Eingabefelder 
gehört:
Private Sub Anzeige()
If RadioButton4.Checked Then ' Anzeige in Rechteck-Koordinaten
Label1.Text = "Realteil A"
Label2.Text = "Realteil B"
Label3.Text = "Realteil Z"
Label4.Text = "Imaginärteil A"
Label5.Text = "Imaginärteil B"
Label6.Text = "Imaginärteil Z"
TextBox1.Text = A.Re.ToString() ' Anzeige Realteil A
TextBox4.Text = A.Im.ToString() ' Anzeige Imaginärteil A
TextBox2.Text = B.Re.ToString() ' Anzeige Realteil B
TextBox5.Text = B.Im.ToString() ' Anzeige Imaginärteil B
TextBox3.Text = Z.Re.ToString() ' Anzeige Realteil Z
TextBox6.Text = Z.Im.ToString() ' Anzeige Imaginärteil Z
Else ' Anzeige in Polarkoordinaten
Label1.Text = "Betrag A"
Label2.Text = "Betrag B"
Label3.Text = "Betrag Z"
Label4.Text = "Winkel A"
Label5.Text = "Winkel B"
Label6.Text = "Winkel Z"TextBox1.Text = A.Len.ToString() ' Anzeige Betrag A
TextBox4.Text = A.Ang.ToString() ' Anzeige Winkel A
TextBox2.Text = B.Len.ToString() ' Anzeige Betrag B
TextBox5.Text = B.Ang.ToString() ' Anzeige Winkel B
TextBox3.Text = Z.Len.ToString() ' Anzeige Betrag Z
TextBox6.Text = Z.Ang.ToString() ' Anzeige Winkel Z
End If
End Sub
Die Start-Schaltfläche:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Eingabe()
Verarbeitung()
Anzeige()
End Sub
Nach Umschaltung zwischen Rechteck- und Polarkoordinaten 
muss die Anzeige aktualisiert werden:
Private Sub RadioButton4_CheckedChanged(sender As Object, 
 e As EventArgs) Handles RadioButton4.CheckedChanged
Anzeige()
End Sub
...

Programmtest

Wenn zum Beispiel die Aufgabe

(2.5 + 3j) / (-2 + j)

gelöst werden soll, so stellen Sie zunächst die Anzeige auf „Rechteck“ ein. Geben Sie dann links oben den Realteil (2,5) und darunter den Imaginärteil (3) des ersten Operanden ein. Analog dazu geben Sie rechts oben den Realteil (-2) und darunter den Imaginärteil (1) des zweiten Operanden ein. Abschließend klicken Sie auf die gewünschte Operation (/).

Nach Betätigen der Start-Taste erscheint als Ergebnis die komplexe Zahl -0.4 -1.7j (siehe folgende Abbildung).

Abb. Rechner für komplexe Zahlen, S. 238, aus dem Fachbuch Visual Basic 2015 von Walter Doberenz und Thomas Gewinnus, Carl Hanser Verlag, München 2015.

Abb. Rechner für komplexe Zahlen, S. 238, aus dem Fachbuch Visual Basic 2015 von Walter Doberenz und Thomas Gewinnus, Carl Hanser Verlag, München 2015.

Die äquivalenten Polarkoordinaten liefern für das gleiche Beispiel einen Zeiger mit der Länge von ca. 1.746 und einem Winkel von ca. 256.76 Grad.

Abb. Rechner für komplexe Zahlen, S. 238, aus dem Fachbuch Visual Basic 2015 von Walter Doberenz und Thomas Gewinnus, Carl Hanser Verlag, München 2015.

Abb. Rechner für komplexe Zahlen, S. 238, aus dem Fachbuch Visual Basic 2015 von Walter Doberenz und Thomas Gewinnus, Carl Hanser Verlag, München 2015.

Hinweis: Wenn Sie die Anzeige zwischen Rechteck- und Polarkoordinaten umgeschaltet haben, müssen Sie anschließend die Start-Schaltfläche klicken!

Bemerkungen

  • Die Vorteile eines gut strukturierten, objektorientierten Programms liegen bekanntermaßen in der leichteren Lesbarkeit des Quellcodes („sprechender“ Code) und in der besseren Wartbarkeit und Erweiterungs­fähigkeit.
  • Beim Arbeiten mit Visual Studio informiert Sie die Intellisense stets aktuell über die vor­han­de­nen Klassenmitglieder und deren Signaturen.
  • Bereits mit .NET 4.0 wurde die Klasse System.Numerics.Complex eingeführt. Alle Versuche der Autoren, diese Klasse als Ersatz für CComplexN zu verwenden und den „Rechner für komplexe Zahlen“ damit zu realisieren, scheiterten am unbefriedigenden und praxisfremden Programmier­modell der Complex-Klasse. So sind die Eigenschaften Real und Imaginary schreibgeschützt und nur über den Konstruktor zuweisbar, die Polar­koordinaten hingegen können nur über eine statische Methode gesetzt werden. Der Code wird dadurch unnötig auf­gebläht und verliert an Transparenz. Wir haben deshalb auf die Anwendung dieser Klasse verzichtet und bevorzugen weiterhin unsere „Eigen­produktion“ CComplexN

Hinweis: Wer mit der systemeigenen Klasse Complex dennoch experimentieren möchte, muss in der Regel vorher einen Verweis auf die System.Numerics.dll hinzufügen.

[1] Die UML (Unified Modelling Language) stellt dazu spezielle Werkzeuge bereit.

[2] Die Verwendung öffentlicher Zustandsvariabler als Eigenschaften ist zwar nicht der sauberste, in unserem Fall aber der effektivste Weg.