Rubrik: Variablen/Strings · Algorithmen/Mathematik | VB-Versionen: VB.NET | 31.05.07 |
Eine Klasse für die Bruch-Rechnung Klasse für das Rechnen mit Brüchen auf der Basis von Ganzzahl-Arithmetik | ||
Autor: Manfred Bohn | Bewertung: | Views: 15.835 |
ohne Homepage | System: WinNT, Win2k, WinXP, Win7, Win8, Win10, Win11 | Beispielprojekt auf CD |
Problemstellung:
Visual Basic stellt Datentypen für Ganzzahl- und für Gleitkomma-Variable zur Verfügung. Bei Verwendung der integrierten Datentypen müssen Berechnungen mit Bruchzahlen deshalb in der Form von Dezimalbrüchen durch Gleitkomma-Variable erledigt werden.
Das bringt Tücken mit sich.
Bruchzahlen können in Dezimaldarstellung bekanntlich zu unendlichen (z.B. periodischen) Brüchen werden - und die lassen sich nur fehlerbehaftet durch Gleitkomma-Variable abbilden. Berechnungen mit Bruchzahlen, die sich nur gering von 0 unterscheiden, führen bei Gleitkomma-Variablen zu einem erheblichen Verlust gültiger Stellen im Ergebnis. Insbesondere iterativ arbeitende Algorithmen können auf die daraus resultierende Fehlerakkumulation im Einzelfall sehr negativ reagieren und liefern dann extrem ungenaue Resultate.
Es liegt deshalb nahe, mit einer Klasse zu arbeiten, die tatsächlich Brüche verarbeitet - wo also Bruchzahlen aus (ganzzahligem) Zähler und Nenner bestehen und auch entsprechend den Regeln des Bruchrechnens manipuliert werden.
Die hier vorgestellte Klasse 'cBruchzahl' ist so aufgebaut.
Sie kann auch als einführendes Beispiel für die Programmierung von Operator-Überladungen herangezogen werden, die seit VB 2005 auch in MS-BASIC möglich ist.
Überblick über die Klasse 'cBruchzahl':
Eine Instanz der Klasse 'cBruchzahl' steht für eine einzelne Bruchzahl, bestehend aus Zähler und Nenner, die in zwei klassenglobalen LONG-Variablen abgelegt werden.
Die von 'cBruchzahl' durchgeführten Rechen-Operationen beruhen auf der Ganzzahl-Arithmetik und sind deshalb exakt - müssen sich aber im Rahmen des Datentyps LONG bewegen. Da es sich dabei um eine 8-Byte-Ganzzahl handelt, stellt das keine besonders gravierende Einschränkung dar. (Im Prinzip könnte man auch auf ganzzahlig gerundete DECIMAL-Werte zurückgreifen (12 Byte), aber das hätte erhebliche Leistungseinbußen zur Folge. Zudem kann dann nicht mehr auf die VB-Modulo-Division zurückgegriffen werden - das VBARCHIV enthält einen entsprechenden Tipp, der zeigt, wie es statt dessen geht.)
Die Wert-Zuweisung (Zähler und Nenner) kann über den Konstruktor oder durch die (überladene) Methode 'Zuweisung' erfolgen, wobei jeweils ein LONG-Wert für den Zähler und für den Nenner des Bruchs gefordert ist.
Bei Bedarf kann auch ein DECIMAL-Wert direkt zugewiesen werden - wobei allerdings nur vier Nachkommastellen bei der Bildung des Bruchs beachtet werden (schreibgeschützte Methode: 'FromDecimal').
Bei der Wert-Zuweisung, aber auch bei allen Berechnungen mit Brüchen, wird das Ergebnis stets automatisch soweit möglich gekürzt und danach klassenglobal gespeichert. Der Nenner kann dabei nicht den Wert 0 annehmen (d.h. eine Ausnahme wird ausgelöst). Dieses Verhalten entspricht der mathematischen Definition rationaler Zahlen.
Folgende Readonly-Abfrage-Methoden sind in der Klasse enthalten:
- Zähler (der Wert des Zählers wird als LONG zurückgegeben)
- Nenner (der Wert des Nenners wird als LONG zurückgegeben)
- ToDecimal (der Bruch wird als Dezimalbruch vom Typ DECIMAL zurückgegeben)
- ToDouble (der Bruch wird als Dezimalbruch zurückgegeben, gewandelt in einen DOUBLE-Wert)
- ToString (der Bruch wird als nichtnumerischer Ausdruck-String zurückgeben, z.B. "2 / 3")
Die Abfrage per 'ToDecimal' oder 'ToDouble' birgt das Risiko des Informationsverlustes.
Folgende Vergleichsoperatoren sind in der Klasse enthalten:
- gleich, ungleich, größer, kleiner, größergleich, kleinergleich
Man beachte, dass die Vergleiche auf (ganzzahligen) Decimal-Werten beruhen und keine Epsilontik enthalten ist. Nur numerisch exakt identische Brüche werden als 'gleich' ausgewiesen. (Im Einzelfall können andere Vergleichs-Varianten zweckmäßiger sein.)
Die Operatoren für die vier Grundrechenarten sind in der Klasse implementiert.
Die Kommentarzeilen im Code enthalten weitere Details zu den Methoden.
Die Berechnung des größten gemeinsamen Teilers (GGT) habe ich einem Tipp aus dem VBARCHIV entnommen und für VB 2005 geringfügig überarbeitet.
Hinweise zum Demonstrations-Beispiel:
Das beigefügte Demonstrationsbeispiel (SubRoutine: Bruchzahl_Demo) ermittelt in einer Schleife jeweils vier ganzzahlige, vorzeichenbehaftete Zufallszahlen und verwendet diese als Zähler bzw. Nenner für zwei Bruchzahlen. Mit diesen Bruchzahlen werden arithmetische und Vergleichsoperationen ausgeführt - simultan mit der impliziten Ganzzahl-Arithmetik des Datentyps DECIMAL und mit den überladenen Operatoren der Klasse 'cBruchzahl'. Die Ergebnisse werden jeweils (als Decimal-Werte) verglichen.
Es ist zu beachten, dass eventuell bei Brüchen nahe 0 die Dezimalbruchberechnung weniger genau ist als die direkte Bruchrechnung. Es kann deshalb zu geringfügigen Abweichungen kommen (d.h. eine der STOP-Bedingungen tritt auf). In diesem Fall kann das Vergleichsformat 'fmt' geändert werden, damit ein weniger sensibler Vergleich der beiden Berechnungsweisen durchgeführt wird.
Aus der Klasse 'cBruchzahl' können ein- und mehrdimensionale statische und dynamische Arrays gebildet werden. Dabei ist lediglich zu beachten, dass jedes einzelne Array-Element vor seiner Verwendung 'konstruiert' werden muss. Die Routine 'BruchzahlArray_Demo' zeigt, wie es geht.
Die Klasse 'cBruchzahl'
Option Strict On Option Explicit On Public Class cBruchzahl ' Die Klasse kapselt eine Bruchzahl und enthält ' Bruchzahl-Operatoren (+ - * / < <= = <> => >) ' Manfred Bohn für VBARCHIV ' Mai 2007 ' =============================================== ' globale Hilfs-Variable für gekapselte Bruchzahl ' =============================================== Dim gZähler As Long Dim gNenner As Long
' ========================================================== ' Konstruktoren ' ========================================================== Public Sub New() ' Standard-Konstruktor erforderlich: ' Nenner-Null muss verhindert werden gZähler = 0 : gNenner = 1 End Sub Public Sub New(ByVal Zähler As Long, ByVal Nenner As Long) ' zusätzlicher Konstruktor mit Zuweisungs-Parametern ' und automatischer Kürzung des Bruchs Zuweisung(Zähler, Nenner, True) End Sub
'================================================== 'Zuweisungs-Methoden '================================================== Public Overloads Sub Zuweisung(ByVal Zähler As Long, _ ByVal Nenner As Long, _ Optional ByVal Bruch_Kürzen As Boolean = True) ' Explizite Zuweisung von Zähler und Nenner der Bruchzahl ' Der dritte Parameter bestimmt, ob der Bruch vor der Zuweisung ' gekürzt wird (verwendet für interne Zwecke) ' Beide Parameter können vorzeichenbehaftet sein ' Man beachte jedoch, dass negative Werte sowohl im ' Zähler als auch im Nenner zu einem positiven Wert ' des Bruchs führen ' Hilfsvariable sinnvoll initialisieren gZähler = 0 : gNenner = 1 ' ggf. sofort Überlauf auslösen If Nenner = 0 Then ' so nicht !!! Throw New System.DivideByZeroException ElseIf Zähler = 0 Then ' Nenner sofort 'kürzen' Nenner = 1 ElseIf Bruch_Kürzen Then ' falls gewünscht, Bruch kürzen Kürzen(Zähler, Nenner) End If ' Zuweisung der Parameter auf interne Variable durchführen gZähler = Zähler gNenner = Nenner End Sub Public Overloads Sub Zuweisung(ByVal Ganzzahl As Long, _ ByVal Zähler As Long, _ ByVal Nenner As Long) ' Zuweisung einer Ganzzahl mit Bruch (unechter Bruch) ' z.b. Zahl -1 1/2 --> Parameter: -1,1,2 ' In diesem Fall müssen Zähler und Nenner positiv sein ' weil bereits die Ganzzahl das Vorzeichen des ' unechten Bruchs definiert If Zähler < 0 Or Nenner < 1 Then ' beide Vorzeichen müssen positiv sein Throw New System.ArgumentOutOfRangeException Else Zuweisung(Zähler + Ganzzahl * Nenner, Nenner) End If End Sub Public WriteOnly Property FromDecimal() As System.Decimal ' Ein Dezimalbruch wird in eine ' Bruchzahl umgewandelt und zugewiesen ' Dabei werden VIER Nachkommastellen beachtet!! ' Beispiel: -0,125 ---> - 1 / 8 Zähler = -1 : Nenner = 8 Set(ByVal dec As Decimal) Dim sgn As Integer = Math.Sign(dec) dec = Math.Abs(dec) Dim Nenner As Long = 10000 ' Komma um 4 Stellen verschieben Dim Zähler As Long = _ CLng(Math.Round(((dec - Fix(dec)) * Nenner), 0)) Zuweisung(CLng(Fix(dec)), zähler, Nenner) ' Vorzeichen der Bruchzahl explizit eintragen gZähler *= sgn End Set End Property
' ============================================================ ' Abfragemethoden ' ============================================================ Public ReadOnly Property Zähler() As Long ' Abfrage des Zählers Get Zähler = gZähler End Get End Property Public ReadOnly Property Nenner() As Long ' Abfrage des Nenners Get Nenner = gNenner End Get End Property Public Shadows ReadOnly Property ToString() As String ' Die schattenwerfende Funktion gibt den Bruch ' bzw. die Ganzzahl (Nenner = 1) als String zurück ' SHADOWS ist erforderlich weil 'ToString' von der ' allgemeinen Basisklasse geerbt worden ist Get If gNenner <> 1 Then Return CStr(gZähler) + " / " + CStr(gNenner) Else Return CStr(gZähler) End If End Get End Property Public ReadOnly Property ToDecimal() As System.Decimal ' Die Funktion gibt den Wert des Bruchs ' bzw. die Ganzzahl als Decimal zurück ' (--> hohe Genauigkeit der Dezimaldarstellung) Get If gNenner <> 1 Then ' Wichtig: Erst in Decimal wandeln, dann dividieren, ' sonst kommt es zu einem Genauigkeitsverlust, weil ' lediglich eine Double-Division durchgeführt wird !!! Return CDec(gZähler) / CDec(gNenner) Else Return CDec(gZähler) End If End Get End Property Public ReadOnly Property ToDouble() As System.Double ' Die Funktion gibt den Wert des Bruchs ' bzw. die Ganzzahl als Double-Gleikomma-Zahl zurück ' (--> geringe Genauigkeit der Dezimaldarstellung) Get If gNenner <> 1 Then Return CDbl(gZähler) / CDbl(gNenner) Else Return CDbl(gZähler) End If End Get End Property
' ============================================================== ' Überladung der Vergleichs-Operatoren = <> <= < >= > ' ============================================================== Public Shared Operator >(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As Boolean ' Operator: Größenvergleich von 2 Bruchzahlen ' Es gilt: a/b > c/d falls a*d > b*c Dim Term1, Term2 As Decimal Compare_Help(Bruchzahl1, Bruchzahl2, Term1, Term2) Return Term1 > Term2 End Operator Public Shared Operator <(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As Boolean ' Operator: Größenvergleich von 2 Bruchzahlen Dim Term1, Term2 As Decimal Compare_Help(Bruchzahl1, Bruchzahl2, Term1, Term2) Return Term1 < Term2 End Operator Public Shared Operator >=(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As Boolean ' Operator: Größenvergleich von 2 Bruchzahlen Dim Term1, Term2 As Decimal Compare_Help(Bruchzahl1, Bruchzahl2, Term1, Term2) Return Term1 >= Term2 End Operator Public Shared Operator <=(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As Boolean ' Operator: Größenvergleich von 2 Bruchzahlen Dim Term1, Term2 As Decimal Compare_Help(Bruchzahl1, Bruchzahl2, Term1, Term2) Return Term1 <= Term2 End Operator Public Shared Operator =(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As Boolean ' Operator: numerische Gleichheit von 2 Bruchzahlen Dim Term1, Term2 As Decimal Compare_Help(Bruchzahl1, Bruchzahl2, Term1, Term2) Return Term1 = Term2 End Operator Public Shared Operator <>(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As Boolean ' Operator: numerische Ungleichheit von 2 Bruchzahlen Dim Term1, Term2 As Decimal Compare_Help(Bruchzahl1, Bruchzahl2, Term1, Term2) Return Term1 <> Term2 End Operator
' =========================================================== ' Überladung der mathematischen Operatoren + - * / ' =========================================================== Public Shared Operator +(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As cBruchzahl ' überladener Operator: Addition zweier Bruchzahlen ' Operationsergebnis an Operator-Zielvariable übergeben Return OperationTyp1(True, Bruchzahl1, Bruchzahl2) End Operator Public Shared Operator -(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As cBruchzahl ' überladener Operator: Subtraktion zweier Bruchzahlen ' Operationsergebnis an Operator-Zielvariable übergeben Return OperationTyp1(False, Bruchzahl1, Bruchzahl2) End Operator Public Shared Operator *(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As cBruchzahl ' überladener Operator: Multiplikation zweier Bruchzahlen ' Operationsergebnis an Operator-Zielvariable übergeben Return OperationTyp2(True, Bruchzahl1, Bruchzahl2) End Operator Public Shared Operator /(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As cBruchzahl ' überladener Operator: Division zweier Bruchzahlen ' Operationsergebnis an Operator-Zielvariable übergeben Return OperationTyp2(False, Bruchzahl1, Bruchzahl2) End Operator
' =========================================== ' Hilfsfunktion für Subtraktion und Addition ' =========================================== Private Shared Function OperationTyp1( _ ByVal Addition As Boolean, _ ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As cBruchzahl ' Die Funktion addiert oder subtrahiert zwei Brüche ' dabei wird der ggf. erforderliche gemeinsame Nenner ' hergestellt - und nach der Operation - falls möglich - ' wieder gekürzt ' beide Brüche auf den kleinsten gemeinsamen Nenner ' erweitern If Not Erweitern(Bruchzahl1, Bruchzahl2) Then Throw New System.OverflowException End If Dim erg As New cBruchzahl Dim Zähler, Nenner As Long ' Zähler der erweiterten Brüche addieren oder subtrahieren If Addition Then Zähler = Bruchzahl1.Zähler + Bruchzahl2.Zähler Else ' Subtraktion Zähler = Bruchzahl1.Zähler - Bruchzahl2.Zähler End If Nenner = Bruchzahl1.Nenner ' Readonly-Property auslesen ' Ergebnis-Bruch - falls möglich - kürzen Kürzen(Zähler, Nenner) ' Ergebnis-Bruchzahl erstellen erg.Zuweisung(Zähler, Nenner) ' Operations-Ergebnis zurückgeben Return erg End Function
' ============================================= ' Hilfsfunktion für Multiplikation und Division ' ============================================= Private Shared Function OperationTyp2( _ ByVal Multiplikation As Boolean, _ ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl) As cBruchzahl ' Die Funktion multipliziert oder dividiert zwei Brüche ' und nach der Operation - falls möglich - kürzt sie das ' Ergebnis ' Der Nenner darf bei Division nicht 0 werden Dim erg As New cBruchzahl Dim Zähler, Nenner As Long If Multiplikation Then Zähler = Bruchzahl1.Zähler * Bruchzahl2.Zähler Nenner = Bruchzahl1.Nenner * Bruchzahl2.Nenner Else ' Division = Multiplikation mit Kehrwert Zähler = Bruchzahl1.Zähler * Bruchzahl2.Nenner Nenner = Bruchzahl1.Nenner * Bruchzahl2.Zähler End If If Nenner = 0 Then Throw New System.DivideByZeroException Else ' Ergebnis-Bruch - falls möglich - kürzen Kürzen(Zähler, Nenner) ' Ergebnis-Bruchzahl erstellen erg.Zuweisung(Zähler, Nenner) End If ' Operations-Ergebnis zurückgeben Return erg End Function
' ========================================== ' Hilfsfunktion: Kürzen eines Bruchs (GGT) ' ========================================== Private Shared Function Kürzen _ (ByRef t1 As Long, ByRef t2 As Long) As Boolean ' erwartet: Ein Zählerwert und ein Nennerwert ' Funktion kürzt beide Werte soweit möglich If t1 = 0 Then t2 = 1 : Exit Function ' Absolutwerte sicherstellen Dim abs_t1 As Long = Math.Abs(t1) Dim abs_t2 As Long = Math.Abs(t2) ' Ermittlung: größter gemeinsamer Teiler Dim mn As Long = GGT(abs_t1, abs_t2) ' Zähler und Nenner kürzen ' Vorzeichen bleibt jeweils erhalten (mn stets positiv) t1 \= mn : t2 \= mn End Function
' =========================================== ' Hilfsfunktion: Erweitern eines Bruchs (KGV) ' =========================================== Private Shared Function Erweitern(ByRef t1 As cBruchzahl, _ ByRef t2 As cBruchzahl) As Boolean ' Die beiden Bruchzahlen werden auf einen gemeinsamen ' Nenner erweitert ' Rückgabe true: falls es geklappt hat Dim abs_t1n As Long = Math.Abs(t1.Nenner) Dim abs_t2n As Long = Math.Abs(t2.Nenner) Dim t1z, t2z As Long Try ' Ermittlung: Kleinstes gemeinsames Vielfaches der ' beiden Nenner ---> gemeinsamer Nenner Dim mx As Long = KGV(abs_t1n, abs_t2n) ' Transformation der beiden Zähler entsprechend dem ' gemeinsamen Nenner t1z = t1.Zähler * (mx \ t1.Nenner) t2z = t2.Zähler * (mx \ t2.Nenner) ' Erweiterung auf Bruchzahl-Instanzen zuweisen ' Kürzen bei Zuweisung durch Parameter 3 = false verhindern! t1.Zuweisung(t1z, mx, False) t2.Zuweisung(t2z, mx, False) Return True Catch ex As Exception ' vermutlich Überlauf (bei Modulo-Division) Return False End Try End Function</pre></code> <pre><code> ' =========================================================== ' Hilfsfunktion: KGV ' kleinstes gemeinsames Vielfaches zweier Ganzzahlen '========================================================== Private Shared Function KGV _ (ByVal z1 As Long, ByVal z2 As Long) As Long ' Berechnung des kleinsten gemeinsamen Vielfachen (KGV) ' durch Verwendung der Funktion GGT ' Es werden positiv ausgeprägte Parameter erwartet ' Die Klammern um die beiden ersten Argumente sind ' von entscheidender Bedeutung für ein korrektes Ergebnis Return (z1 \ GGT(z1, z2)) * z2 End Function
' ============================================================ ' Hilfsfunktion: GGT ' größter gemeinsamer Teiler zweier Ganzzahlen '============================================================ Private Shared Function GGT _ (ByVal z1 As Long, ByVal z2 As Long) As Long ' Berechnung des größten gemeinsamen Teilers (GGT) ' durch den Euklidischen Algorithmus ' Es werden positive Werte in den Parametern erwartet Dim dummy, rest As Long ' Wenn die erste Zahl größer ist, dann ' Zahlen tauschen If z1 < z2 Then dummy = z2 : z2 = z1 : z1 = dummy End If ' Rest bei Ganzzahl-Division rest = z1 Mod z2 If rest = 0 Then ' Wenn Rest = 0, ist GGT die kleinere Zahl Return z2 Else ' Ansonsten setzen wir den Wert von z1 auf den ' Wert von z2 und den Wert von z2 auf Rest z1 = z2 : z2 = rest ' Abbruchbedingung: Modulo der Zahlen = 0 While z1 Mod z2 <> 0 ' Rest berechnen und die Werte neu setzen rest = z1 Mod z2 z1 = z2 : z2 = rest End While ' Rückgabe: Rest = GGT Return rest End If End Function
' ================================================================= ' allgemeine Hilfsfunktion für Vergleichsoperatoren ' ================================================================= Private Shared Sub Compare_Help(ByVal Bruchzahl1 As cBruchzahl, _ ByVal Bruchzahl2 As cBruchzahl, _ ByRef Term1 As Decimal, _ ByRef Term2 As Decimal) ' Hilfsfunktion für Vergleichsoperatoren ' CrossOver-Multiplikationen ' um einen Überlauf möglichst zu vermeiden wird vor der ' Multiplikation explizit in DECIMAL gewandelt Term1 = CDec(Bruchzahl1.Zähler) * CDec(Bruchzahl2.Nenner) Term2 = CDec(Bruchzahl2.Zähler) * CDec(Bruchzahl1.Nenner) End Sub
End Class
Demonstration der Klasse 'cBruchzahl'
Public Sub Bruchzahl_Demo() ' Demonstration der Anwendung der Klasse 'cBruchzahl' ' Es werden Brüche aus Zufallszahlen gebildet und ' die Ergebnisse der Operatoren der Klasse 'cBruchzahl' ' mit den entsprechenden Gleitkomma-Operationen verglichen ' Hinweis: Operationen mit Brüchen sehr nahe bei 0 ' können unterschiedliche Resultate liefern ' weil die Gleitkomma-Berechnung gültige Stellen einbüßt Dim i ' Loop Dim zähler1, zähler2 As Long ' Zufallszahlen Dim nenner1, nenner2 As Long ' Zufallszahlen (-10000 bis +10000) Dim bz1, bz2, bz3 As New cBruchzahl ' Bruchzahlen Dim x1, x2, x3, xb3 As Decimal ' Dezimalzahlen Dim fmt As String = "g20" ' Format (Ergebnisvergleich) ' Zufallszahlengenerator initialisieren Randomize(Microsoft.VisualBasic.Timer) ' Schleife über Testdurchläufe For i = 1 To 100000 ' 4 ganzzahlige vorzeichenbehaftete Zufallszahlen ' bei jedem Umlauf ermitteln zähler1 = RandomLong() nenner1 = RandomLong() If i Mod 100 = 0 Then ' manchmal sind beide Bruchzahlen gleich zähler2 = zähler1 : nenner2 = nenner1 Else ' meist aber nicht zähler2 = RandomLong() nenner2 = RandomLong() End If ' aus den Zufallszahlen 2 Bruchzahlen erzeugen ' Verwendung des Konstruktors bz1 = New cBruchzahl(zähler1, nenner1) ' Verwendung der Zuweisungsfunktion bz2.Zuweisung(zähler2, nenner2) ' aus den Zufallszahlen 2 Dezimalbrüche erzeugen x1 = CDec(zähler1) / CDec(nenner1) x2 = CDec(zähler2) / CDec(nenner2) ' ab hier beginnen die Vergleichstests: ' Dezimalbruch in Bruch umwandeln (4 Nachkommastellen) bz3.FromDecimal = x1 xb3 = bz3.ToDecimal ' Bruch als Dezimalbruch abfragen ' Dezimalbruch auf 4 Stellen identisch ' mit Dezimaldarstellung der Bruchzahl ? If Math.Round(x1, 4).ToString(fmt) _ <> xb3.ToString(fmt) Then Stop ' Subtraktion durchführen x3 = x1 - x2 ' Dezimal bz3 = bz1 - bz2 ' Bruch ' Bruch in Dezimalbruchdarstellung abfragen xb3 = bz3.ToDecimal ' Ergebnis gerundet vergleichen If x3.ToString(fmt) <> xb3.ToString(fmt) Then Stop ' Addition durchführen x3 = x1 + x2 bz3 = bz1 + bz2 ' Bruch in Dezimalbruchdarstellung abfragen xb3 = bz3.ToDecimal ' Ergebnis gerundet vergleichen If x3.ToString(fmt) <> xb3.ToString(fmt) Then Stop ' Multiplikation durchführen x3 = x1 * x2 bz3 = bz1 * bz2 ' Bruch in Dezimalbruchdarstellung abfragen xb3 = bz3.ToDecimal ' Ergebnis gerundet vergleichen If x3.ToString(fmt) <> xb3.ToString(fmt) Then Stop ' Division durchführen x3 = x1 / x2 bz3 = bz1 / bz2 ' Bruch in Dezimalbruchdarstellung abfragen xb3 = bz3.ToDecimal ' Ergebnis gerundet vergleichen If x3.ToString(fmt) <> xb3.ToString(fmt) Then Stop ' Vergleichsoperatoren überprüfen ' Der Dezimal-Vergleich muss genauso ' ausfallen wie der Bruch-Vergleich If (x1 > x2) <> (bz1 > bz2) Then Stop If (x1 >= x2) <> (bz1 >= bz2) Then Stop If (x1 = x2) <> (bz1 = bz2) Then Stop If (x1 <> x2) <> (bz1 <> bz2) Then Stop If (x1 < x2) <> (bz1 < bz2) Then Stop If (x1 <= x2) <> (bz1 <= bz2) Then Stop ' nächster Testdurchlauf Next i MsgBox("Demonstration der Klasse 'cBruchzahl' beendet") End Sub
Public Function RandomLong( _ Optional ByVal Null_OK As Boolean = False) As Long ' Hilfsfunktion: berechnet vorzeichenbehaftete ' Ganzzahlen im Bereich -10000 bis 10000 Dim r As Long ' zufällige Ganzzahl r = CLng(Rnd() * 10000) ' ggf. Null vermeiden If Not Null_OK Then If r = 0 Then r = 1 End If ' zufälliges Vorzeichen If Rnd() > 0.5 Then Return -r Else Return r End If End Function
Public Sub BruchZahlArray_Demo() ' Beispiel für ein dynamisches, zweidimensionales ' BruchZahl-Array Dim i, k As Integer ' Loop Dim bz_arr(,) As cBruchzahl Dim bz_sum As New cBruchzahl Dim dec_sum As System.Decimal ReDim bz_arr(10, 20) ' Wert-Zuweisung unter gleichzeitiger ' Konstruktion der Arrayfeld-Objektinstanzen For i = 0 To UBound(bz_arr, 1) For k = 0 To UBound(bz_arr, 2) bz_arr(i, k) = New cBruchzahl(i, k + 1) Next k Next i ' Überprüfung des Array-Inhaltes bz_sum = New cBruchzahl For i = 0 To UBound(bz_arr, 1) For k = 0 To UBound(bz_arr, 2) ' Kontrolle des Feldinhaltes If bz_arr(i, k).ToDecimal <> _ CDec(i) / CDec(k + 1) Then Stop ' Summation aller Brüche in einer Bruchzahl bz_sum += bz_arr(i, k) ' Summation aller Brüche in einem Decimal-Wert dec_sum += bz_arr(i, k).ToDecimal Next k Next i ' Vergleich der beiden Summationsergebnisse ' Man beachte die Stringabfrage bei bz_sum ' hier ist wegen der Funktionsüberladung zunächst ' die DECIMAL-Transformation notwendig If dec_sum.ToString("g15") <> _ bz_sum.ToDecimal.ToString("g15") Then MsgBox("OOOPS!°") End If MsgBox("Summe aller Array-Elemente: " + bz_sum.ToString) End Sub