Rubrik: Variablen/Strings · Sonstiges | VB-Versionen: VB6 | 26.05.05 |
Der Gebrauch des Datentyps DECIMAL Besonderheiten und Einschränkungen des Datentyps DECIMAL | ||
Autor: Manfred Bohn | Bewertung: | Views: 23.594 |
ohne Homepage | System: Win9x, WinNT, Win2k, WinXP, Win7, Win8, Win10, Win11 | Beispielprojekt auf CD |
Der VB6-Datentyp DECIMAL scheint überwiegend ein Schattendasein zu führen, obwohl er die Möglichkeit bietet, Berechnungen mit ganz besonders hoher Genauigkeit durchzuführen. Das hängt vermutlich auch damit zusammen, dass bei seiner Anwendung eine ganze Reihe von Besonderheiten und Einschränkungen zu beachten sind.
Zunächst die Definition des Typs, wie sie die VB6-Dokumentation liefert:
Variablen des Datentyps Decimal werden als 96-Bit-Ganzzahlen (12 Bytes) ohne Vorzeichen mit einer variablen Potenz zur Basis 10 gespeichert.
Die Potenz zur Basis 10 wird als Skalierungsfaktor verwendet und bestimmt die Anzahl der Nachkommastellen, die in einem Bereich von 0 bis 28 liegen kann.
Beim Skalierungsfaktor von 0 (keine Nachkommastellen) liegt der größtmögliche Wert bei +/-79.228.162.514.264.337.593.543.950.335.
Bei 28 Nachkommastellen liegt der größte Wert bei +/- 7,9228162514264337593543950335 und der kleinste Wert, der ungleich Null ist, bei +/-0,0000000000000000000000000001.
Der Datentyp Decimal kann nur mit einem Wert vom Typ Variant benutzt werden, d.h., Sie können keine Variable als Decimal deklarieren. Mit der CDec-Funktion können Sie jedoch einen Wert vom Typ Variant erstellen, dessen Untertyp Decimal ist.
Soweit die Dokumentation.
Ulkigerweise kennt VB6 das Schlüsselwort DECIMAL nicht.
Es ist also weder eine Variablen-Deklaration möglich, noch kann ein DECIMAL in einem Funktionskopf, in einer Ereignisprozedur oder als Bestandteil einer DECLARE-Anweisung auftauchen.
Das ist zwar ungewöhnlich, stellt aber keine echte Einschränkung dar. Überall wo eine Variable des Typs DECIMAL zu deklarieren wäre, ist statt dessen eine Variable des Typs VARIANT einzusetzen. Die Zuweisung eines numerischen Wertes auf die VARIANT-Variable durch die Umwandlungsfunktion 'CDec' deklariert sie als DECIMAL. Das kann durch Anwendung der VB6-Funktion VARTYPE leicht verifiziert werden. In der VB6-Dokumentation steht an einigen Stellen, der Datentyp DECIMAL würde nicht unterstützt. Davon darf man sich nicht irritieren lassen. Nur das Schlüsselwort ist nicht bekannt. Als Untertyp einer VARIANT-Variable wird DECIMAL korrekt an Funktionen übergeben. Das gilt auch für Arrays, deren Feldelemente vom Typ VARIANT, Untertyp DECIMAL sind.
Besonderes Augenmerk ist darauf zu richten, dass DECIMAL-Variable nur Werte im Bereich von ca. -7,9E+28 bis +7,9E+28 annehmen können. Das ist ein erheblich geringeres Intervall, als es z.B. der Datentyp SINGLE bietet. DECIMAL-Variablen kennen zwar Gleitkomma-, aber eben keine Exponential-Darstellung. Es ist darauf zu achten, dass bei Durchführung der mathematischen Operationen kein Überlauf entsteht bzw. eine Fehlerbehandlung implementiert ist.
Die vier Grundrechenarten können direkt auf VARIANT-Variable des Typs DECIMAL angewendet werden. Auch die Kombination mit anderen numerischen Datentypen in einem Ausdrucks ist möglich. Bei gemischten Datentypen innerhalb eines Ausdrucks sollte man durch Verwendung der Umwandlungsfunktionen CDec, CDbl, CLng usw. sicherstellen, dass alle Variablen bei der Berechnung des Ausdrucks den gleichen Datentyp besitzen. Nur dadurch ist die erzielte Genauigkeit des Ergebnisses abschätzbar.
Die in VB6 eingebauten mathematischen Funktionen akzeptieren zwar den Datentyp DECIMAL als Eingabeparameter, aber sie liefern stets als Ergebnis eine Variable des Typs DOUBLE.
Eine VARIANT-Variable des Untertyps DECIMAL geben lediglich folgende numerische Funktionen zurück: ABS, FIX, ROUND
Die Winkelfunktionen SIN, TAN, ATN, die Funktionen LOG, EXP, SQR und das Potenzieren (^) können deshalb in Berechnungen nicht verwendet werden, wenn die 28-stellige Genauigkeit des Datentyps DECIMAL auch im Ergebnis erhalten bleiben soll.
In den Demonstrationsbeispielen sind deshalb einige gebräuchliche einfache Algorithmen als Funktionen beigefügt, die die Genauigkeit des Datentyps DECIMAL verwenden (Quadratwurzel, ganzzahliges Potenzieren, natürlicher Logarithmus, Exponentialfunktion, Sinus). Sie sollen als Anregung zur Entwicklung schnellerer und noch genauerer Funktionen für diesen Datentyp dienen.
' Beginn des Quellcodes der Demonstrationsbeispiele für den Datentyp DECIMAL Option Explicit ' Konstante als Indikator für Fehlerbedingungen Const dcError As Variant = "ERROR"
Sub decimal_demo() ' Demonstration der Verwendung des Datentyps DECIMAL ' Der Variant-Datentyp muss bei der Variablen-Deklaration ' zwar nicht explizit angegeben werden, aber das wäre unübersichtlich Dim a As Variant, b As Variant, c As Variant ' 28-stellige Ganzzahl als String-Konstante definieren Const strGanzzahl As String = "1234567890123456789012345678" ' Wichtiger Hinweis: (Win2000 etc.) ' Als Komma in Strings, die mit der Funktion CDEC gewandelt werden, ' muss unbedingt das Zeichen verwendet werden, das in ' Systemsteuerung -> Ländereinstellungen -> Zahlen -> Dezimaltrennzeichen ' eingestellt ist (meist: ,) Const strGleitzahl As String = "123456789,1234567890123456789" ' Die Zuweisung der String-Konstanten auf eine ' Variable des Typs VARIANT per <CDec> erzeugt ' eine Variable des Untertyps <Decimal> ' -------------------------------------------- a = CDec(strGanzzahl) If Not VarType(a) = vbDecimal Then MsgBox "Nanu !", vbExclamation Exit Sub End If ' Die Variable vom Typ DECIMAL wird korrekt addiert ' Die Ergebnisvariable ist vom Typ DECIMAL c = a + a Debug.Print "a + a = "; c ' a + a = 2469135780246913578024691356 ' Die Multiplikation liefert das gleiche Ergebnis Debug.Print "a * 2 = "; a * 2 ' a * 2 = 2469135780246913578024691356 ' Durchführung von Multiplikation und Division ' -------------------------------------------- ' Bei der Multiplikation mit einer Zahl des Typs Double ' wird das Ergebnis als Gleitkommazahl gespeichert ' (max. so viele Nachkommastellen, wie hinter dem Ganzzahl- ' Anteil noch möglich sind) c = a * CDbl(5.2) Debug.Print "a * 5,2 = "; c ' a * 5,2 = 6419753028641975302864197525,6 ' Das Divisions-Ergebnis wird mit ' 28 Nachkommastellen gespeichert c = a / c Debug.Print "a / c = "; c ' a / c = 0,1923076923076923076923076923 ' Die Genauigkeit der Rechenoperationen ist für die volle ' Stellenzahl gewährleistet ' Rückrechnung c = 1 / c Debug.Print "1 / c = "; c ' 1 / c = 5,2000000000000000000000000002 ' Man erhält das gleiche Ergebnis, wenn der ' Multiplikatonsfaktor als Decimal verwendet wird c = a * CDec(5.2) c = a / c ' Ergebnis der Rückrechnung Debug.Print "1 / c = "; 1 / c ' 1 / c = 5,2000000000000000000000000002 ' Die Funktionen ROUND, INT und die Typumwandlungsfunktionen ' sind für den Datentyp DECIMAL verwendbar ' ---------------------------------------------------------- a = CDec(strGleitzahl) Debug.Print "a = "; Round(a, 17) ' a = 123456789,12345678901234568 Debug.Print "Int = "; Int(a) ' Int = 123456789 Debug.Print "Dbl = "; CDbl(strGleitzahl) ' Dbl = 123456789,123457 Debug.Print "Sng = "; CSng(strGleitzahl) ' Sng = 1,234568E+08 ' Beispiel für die Verwendung des Datentyps DECIMAL ' als Parameter einer Funktion ' -------------------------------------------------- b = CDec(strGanzzahl) c = Addition(a, b) ' Die nicht mehr darstellbaren Nachkommastellen ' sind abgeschnitten ! Debug.Print "a + b = "; c ' a + b = 1234567890123456789135802467,1 ' Verwendung der Funktionen ABS und FIX ' ------------------------------------- ' ABS-Funktion: Ergebnis bleibt DECIMAL (=14) c = Abs(-a) Debug.Print "Rückgabe-Typ ABS: " + CStr(VarType(c)) ' FIX-Funktion: Ergebnis bleibt DECIMAL (=14) c = Fix(CDec(strGleitzahl)) Debug.Print "Rückgabe-Typ FIX: " + CStr(VarType(c)) ' Ermittlung der Nachkommastellen eines DECIMAL c = CDec(strGleitzahl) - Fix(CDec(strGleitzahl)) Debug.Print "Nachkommastellen von " + strGleitzahl + " = "; c ' 0,1234567890123456789 ' Das Verhalten der Funktionen SQR und ^ beim Datentyp ' DECIMAL ist nicht unproblematisch: ' Das Ergebnis der Berechnung der Quadrat-Wurzel ' wird von VB6 nur als DOUBLE geliefert, obwohl ein ' DECIMAL übergeben und akzeptiert worden ist Debug.Print "sqr(a) = "; Sqr(a) ' sqr(a) = 11111,1110661111 ' Bei der Berechnung der Quadratwurzel mit der Genauigkeit ' DECIMAL muss man sich deshalb einer Hilfsfunktion bedienen Debug.Print "sqr(a) = "; Quadratwurzel(a) ' sqr(a) = 11111,111066111110969986110534 ' Auch das Potenzieren liefert im Ergebnis ' den Datentyp DOUBLE Debug.Print "-a^3 = "; (-a) ^ CDec(3) ' -a^3 = -1,88167637743418E+24 ' Das Potenzieren mit einem ganzzahligen Exponenten ' benötigt deshalb ebenfalls eine Hilfsfunktion, um ' mit DECIMAL-Genauigkeit durchgeführt werden zu können Debug.Print "-a^3 = "; Potenzieren(-a, 3) ' -a^3 = -1881676377434183982474065,6126 ' Das gleiche beim 'natürlichen' Logarithmus Debug.Print "log(a) = "; Log(a) ' Hilfsfunktion für Genauigkeit des Datentyps DECIMAL Debug.Print "log(a) = "; Logarithmus(a) ' bei der Exponentialfunktion Debug.Print "exp(30,3) = "; Exp(30.3) ' Hilfsfunktion für Genauigkeit des Datentyps DECIMAL Debug.Print "exp(30,3) = "; Exponential(CDec("30,3")) ' und beim SINUS ... a = CDec("-4,736789012345678901234567") Debug.Print "sin(a) = "; Sin(a) ' Hilfsfunktion für Genauigkeit des Datentyps DECIMAL Debug.Print "sin(a) = "; Sinus(a) ' Die Verwendung des Datentyps DECIMAL zur ' Steuerung von FOR_NEXT-Schleifen bereitet keine Probleme ' -------------------------------------------------------- Dim ug As Variant, og As Variant, stp As Variant Dim i As Variant ' Alle Schleifenvariablen mit DECIMALs belegen ug = CDec("-123456789,123456789123456789") og = Abs(ug) stp = CDec((og - ug) / CDec(10)) Debug.Print vbCrLf + "Decimal in FOR_NEXT-Schleife" For i = ug To og Step stp Debug.Print CStr(i); " --> "; CStr(Potenzieren(i, 3)) Next i ' Die Verwendung von Arrays, deren Felder vom Datentyp ' DECIMAL sind, ist ebenfalls möglich ' --------------------------------------------------- ' Es kann keine Variable des Typs VARIANT verwendet werden, ' die ein Array des Typs DECIMAL aufnimmt, sondern es ' ist ein Array mit Feldern des Datentyps VARIANT zu deklarieren Dim arr() As Variant, k& ReDim arr(1 To 20) a = CDec("100,12345678901234567890123") ' Felder des Array mit DECIMAL-Werten füllen For k = LBound(arr) To UBound(arr) arr(k) = a * CDec(k) Next k ' Übergabe des Array an eine Funktion ' zur Berechnung der Feld-Summe mit DECIMAL-Genauigkeit Debug.Print vbCrLf + "Array-Summe = "; CStr(Array_Summe(arr())) ' Array-Summe: 21025,9259256925925925692583 End Sub
Function Addition(ByVal a As Variant, ByVal b As Variant) As Variant ' Die Funktion addiert beliebige numerische Datentypen und ' gibt sie als VARIANT -> DECIMAL zurück On Error GoTo fehler Addition = dcError ' ungeeignete Eingabe abfangen If Not IsNumeric(a) Or Not IsNumeric(b) Then Exit Function ' Sicherstellung des Ergebnistyps durch die ' explizite Typumwandlungsfunktion CDec Addition = CDec(a) + CDec(b) Exit Function fehler: ' Überlauf des DECIMAL-Datentyps abfangen Addition = dcError End Function
Function Quadratwurzel(ByVal arg As Variant) As Variant ' Quadratwurzel aus arg ' Rückgabe: Variant -> Decimal ' Hilfsvariable Dim iarg As Variant Dim a As Variant, b As Variant ' Für Iterations-Abbruch Dim ok As Boolean Dim z& ' Hilfskonstanten Dim zwei As Variant Dim epsilon As Variant zwei = CDec(2) epsilon = CDec("0,0000000000000000000000001") Quadratwurzel = dcError ' unbrauchbare Eingabe abfangen If Not IsNumeric(arg) Then Exit Function If arg < 0 Then Exit Function ' Sonderfall If arg = CDec(0) Then Quadratwurzel = 0 Exit Function End If iarg = CDec(arg) a = CDec(1) b = CDec(iarg) Do Until ok z = z + 1 a = (a + b) / zwei b = iarg / a ' Abbruchbedingungen prüfen ok = (a - b < epsilon) Or z > 100 Loop a = (a + b) / zwei ' Rückgabe Quadratwurzel = a End Function
Function Potenzieren(ByVal arg As Variant, _ ByVal lngExponent As Long) As Variant ' Potenzieren = arg ^ lngExponent, wobei lngExponent >=0 Dim iarg As Variant Dim i As Long On Error GoTo fehler Potenzieren = dcError ' ungeeignete Eingabe abfangen If lngExponent < 0 Then Exit Function If Not IsNumeric(arg) Then Exit Function iarg = CDec(arg) Potenzieren = CDec(1) ' Sonderfall If lngExponent = 0 Then Exit Function For i = 1 To lngExponent Potenzieren = Potenzieren * iarg ext i Exit Function fehler: ' Überlauf des Datentyps verhindern Potenzieren = dcError End Function
Function Logarithmus(ByVal arg As Variant) As Variant ' natürlicher Logarithmus bei positivem Argument Dim p As Variant, q As Variant ' p = Produkt q = iterierte Quadratwurzel Dim i As Long Dim epsilon As Variant ' Abbruchkriterium On Error GoTo fehler Const n As Long = 2000 epsilon = CDec("0,0000000000000000000000000001") Logarithmus = dcError ' unbrauchbare Eingabe abfangen If Not IsNumeric(arg) Then Exit Function If arg < epsilon Then Exit Function p = CDec(1): q = CDec(arg) ' Initialisierungen For i = 1 To n q = Quadratwurzel(q) ' Abbruchkriterien If q = 0 Then Exit For If q = dcError Then Exit For If Abs(q - CDec(1)) < epsilon Then Exit For p = p * CDec(2) / (q + CDec(1) / q) Next i Logarithmus = p * (arg - CDec(1) / arg) / CDec(2) Exit Function fehler: Logarithmus = dcError End Function
Function Exponential(ByVal arg As Variant) As Variant ' Wert der Exponentialfunktion für <arg> Dim glied As Variant, e As Variant, i As Long Exponential = dcError If Not IsNumeric(arg) Then Exit Function ' Geltungsbereich der Funktion ' ausserhalb dieses Bereichs ist die Genauigkeit nicht gegeben ' statt dessen die VB6-Funktion EXP verwenden If arg > 40 Or arg < -10 Then Exit Function glied = CDec(1) e = glied For i = 1 To 300 glied = glied * (arg / CDec(i)) If Abs(glied) <= CDec(0) Then Exit For e = e + glied Next i Exponential = e End Function
Function Sinus(ByVal arg As Variant) As Variant ' Berechnung des Sinus für einen Winkel im Bogenmaß Dim sign As Boolean ' für Vorzeichen Dim px As Variant, x As Variant Dim erg As Variant ' für Summation Dim pglied As Variant ' für Summationsglied Dim fakultaet As Variant Dim i As Long Dim pi As Variant pi = CDec("3,141592653589793238462643383") If Not IsNumeric(arg) Then Exit Function x = arg ' x auf spitzen Winkel zurueckführen If x < CDec(0) Then x = -x sign = True End If While x > CDec(2) * pi x = x - CDec(2) * pi Wend If (CDec(1.5) * pi <= x And x <= CDec(2) * pi) Then x = CDec(2) * pi - x sign = Not sign ElseIf pi <= x And x < CDec(1.5) * pi Then x = x - pi sign = Not sign ElseIf CDec(0.5) * pi <= x And x < pi Then x = pi - x End If If sign Then x = -x ' Taylorformel (12 Glieder summieren) sign = False px = x * x fakultaet = 1 erg = x ' 1. Glied i = 3 While i <= 27 ' mehr geht bei Decimal nicht x = x * px ' erhoehe die Potenz von x um 2 fakultaet = fakultaet * CDec(i) * CDec(i - 1) ' errechne neue, aktuelle Fakultaet} pglied = x / fakultaet If Not sign Then pglied = -pglied ' wechselndes Vorzeichen erg = erg + pglied ' neues Glied aufaddieren sign = Not sign i = i + 2 Wend Sinus = erg End Function
Function Array_Summe(arr() As Variant) As Variant ' Berechnung der Summe der Felder eines Arrays, dessen ' Feld-Elemente aus numerischen Datentypen bestehen Dim i As Long On Error GoTo fehler Array_Summe = CDec(0) ' Rückgabe als DECIMAL einstellen For i = LBound(arr) To UBound(arr) If Not IsNumeric(arr(i)) Then ' Jedes Feld könnte einen nicht-numerischen ' Inhalt besitzen --> Abbruch der Funktion Array_Summe = dcError Exit Function End If Array_Summe = Array_Summe + CDec(arr(i)) Next i Exit Function fehler: ' Überlauf des Datentyps bzw. ' nicht-dimensioniertes Array abfangen Array_Summe = dcError End Function ' Ende des Quellcodes der Demonstrationsbeispiele für den Datentyp DECIMAL