|
Zur Umsetzung der Eingabe einer Zahl als Bruchschreibweise (zum Beispiel "3 1/8") in einen Dezimalbruch (3,125) ist zum einen der ganzzahlige Anteil der Eingabe und zum anderen der gebrochene Anteil zu isolieren und auszuwerten. Die Ergebnisse beider Teile ergeben addiert den gesuchten Dezimalwert.
Die Funktion FractionToNumDbl (bzw. FractionToNumSng und FractionToNumCur für Single- und Currency-Rückgabewerte) sind bezüglich der genauen Formatierung des zu übergebenden Ausdrucks relativ tolerant. Leerzeichen, vor allem vor und nach dem Teiler-Zeichen werden ignoriert. Bis auf eine Ausnahme: nachdem eventuell vorhandene Leerzeichen um das Teiler-Zeichen herum entfernt worden sind, wird der ganzzahlige Anteil vor dem ersten noch verbleibenden Leerzeichen erwartet. Ist dieser nicht vorhanden, wird der Ausdruck als Bruch ohne ganzzahligen Anteil interpretiert.
Schauen wir uns die Funktion FractionToNumDbl einmal näher an.
Public Const ftnErrInvalidParam = 30001
Public Function FractionToNumDbl(ByVal Value As String, _
Optional ByVal NormalFraction As Boolean = True) As Double
Dim nNumParts() As String
Dim nFracParts() As String
Dim nSgn As Long
Dim nNum As Long
Dim nNumerator As Long
Dim nDenominator As Long
Dim nFraction As Double
Zunächst putzen wir überflüssige Leerzeichen weg - vom Anfang und vom Ende und um das Teiler-Zeichen herum (in Visual Basic 5 gibt es die Replace-Funktion noch nicht - dieses Putzen wird daher in der VB5-Version des Moduls zu diesem Artikel in der gesonderten Hilfsfunktion zReplaceX erledigt, die Sie am Ende dieses Artikels finden).
Value = Trim$(Value)
Do While InStr(Value, " /")
Value = Replace(Value, " /", "/")
Loop
Do While InStr(Value, "/ ")
Value = Replace(Value, "/ ", "/")
Loop
On Error Resume Next
Dann teilen wir den Ausdruck am ersten noch innerhalb vorkommenden Leerzeichen in zwei Teile (die Split-Funktion gibt es auch erst ab VB 6 - für VB 5 finden Sie eine speziell für diesen Zweck angepasste Variante ebenfalls am Ende dieses Artikels).
nNumParts = Split(Trim$(Value), " ")
Select Case UBound(nNumParts)
Case 0
Besteht das zurückgegebene Array nur aus einem Element, war gar kein Leerzeichen mehr enthalten. Der Ausdruck bestand somit vermutlich entweder nur aus einem ganzzahligen Anteil oder aus einem Bruch. Daher versuchen wir nun, den Ausdruck anhand des Teiler-Zeichens zu zerlegen.
nFracParts = Split(nNumParts(0), "/")
Select Case UBound(nFracParts)
Case 0
Wenn auch diese Teilung nur ein Element liefert, haben wir es wohl mit einem ganzzahligen Ausdruck zu tun. Diesen wandeln wir mittels der Hilfsfunktion zCLngX in einen Long-Wert um. Das Innere dieser Funktion werden Sie später noch zu Gesicht bekommen - Sie werden dann erkennen, dass die VB-Funktion CLng nicht genügt hätte.
FractionToNumDbl = zCLngX(nFracParts(0))
Konnte der Ausdruck nicht in einen Long-Wert umgewandelt werden, wird ein Fehler ausgelöst.
If Err.Number Then
Err.Raise Err.Number
End If
Case 1
Konnte der Ausdruck erfolgreich in zwei Teile zerlegt werden, wandeln wir beide Teile in Long-Werte um und führen die Bruch-Division aus. Das Vorzeichen des Dezimalbruchs ergibt sich automatisch aus den Vorzeichen von Zähler und Nenner des Bruchs.
FractionToNumDbl = _
zCLngX(nFracParts(0)) / zCLngX(nFracParts(1))
Sollte einer der beiden Teile nicht erfolgreich in einen Long-Wert umgewandelt werden können, oder ergab die Umwandlung des zweiten den Wert 0, wird auch hier wieder ein Fehler ausgelöst.
If Err.Number Then
Err.Raise Err.Number
End If
Case Else
Ergab die Teilung mehr als 2 Teile oder auch gar kein Teil, wird ein Fehler ausgelöst.
Err.Raise ftnErrInvalidParam
End Select
Case 1
Ergab erste Teilung zwei Teile, haben wir es mit einem ganzzahligen Anteil und einem Bruch-Anteil zu tun. Letzeren teilen wir auch hier wieder am Teiler-Zeichen.
nFracParts = Split(nNumParts(1), "/")
Select Case UBound(nFracParts)
Hier ist es nun zwingend, dass der Bruch-Anteil aus zwei Elementen bestehen muss. Hinzu kommt, dass Vorzeichen beim Bruch-Anteil eines zusammengesetzten Ausdrucks keinen Sinn haben. Bei einem negativen Vorzeichen von Zähler (engl. "Numerator") und/oder Nenner (engl. "Denominator") wird daher wieder ein Laufzeitfehler ausgelöst.
Case 1
nNumerator = zCLngX(nFracParts(0))
If Sgn(nNumerator) = -1 Then
Err.Raise ftnErrInvalidParam
Else
nDenominator = zCLngX(nFracParts(1))
If Sgn(nDenominator) = -1 Then
Err.Raise ftnErrInvalidParam
Else
Das Vorzeichen des ganzzahligen Anteils bestimmt das Vorzeichen des Dezimalbruchs. Es wird zunächst ermittelt und festgehalten.
nNum = zCLngX(nNumParts(0))
nSgn = Sgn(nNum)
nFraction = nNumerator / nDenominator
If Err.Number Then
Err.Raise Err.Number
ElseIf NormalFraction Then
Konnte die Bruch-Division erfolgreich ausgeführt werden (kein Überlauf oder keine Division durch 0), prüfen wir, ob das Ergebnis größer oder gleich 1 ist. Da dies bei einem zusammengesetzen Ausdruck ebenfalls keinen Sinn hätte, lösen wir auch hier wieder einen Laufzeitfehler aus.
If nFraction >= 1 Then
Err.Raise Err.Number
End If
End If
Nun addieren wir den Absolutwert des ganzzahligen Ausdrucks und das zwangsläufig mit positivem Vorzeichen gegebene Ergebnis der Bruchdivision und multiplizieren die Summe mit dem zuvor ermittelten Vorzeichenwert des ganzzahligen Anteils. Das Ergebnis bildet den Rückgabewert der Funktion.
FractionToNumDbl = (Abs(nNum) + nFraction) * nSgn
End If
End If
Case Else
Hier wird ein Laufzeitfehler ausgelöst, wenn die Teilung des Bruch-Anteils mehr als zwei Teile ergeben sollte.
Err.Raise ftnErrInvalidParam
End Select
Case Else
Falls die Teilung des Gesamtausdrucks anhand des Leerzeichens mehr als 2 Teile ergeben haben sollte, wird auch für diesen Fall ein Laufzeitfehler ausgelöst.
Err.Raise ftnErrInvalidParam
End Select
End Function
Die Aufgabe der Hilfsfunktion zCLngX ist, den übergebenen Ausdruck nur dann in einen Long-Wert zu konvertieren, wenn dieser auch eindeutig einem solchen entspricht. Die VB-Funktion CLng alleine würde auch eine Dezimalzahl gerundet konvertieren. Diese Hilfestellung können wir jedoch nicht brauchen - wir müssen feststellen können, ob der Ausdruck eindeutig schon einem Long-Wert entspricht. Dazu isolieren wir ein gegebenenfalls vorhandenes Vorzeichen und prüfen mittels eines kleinen Formatierungstricks, ob der Ausdruck unverändert bleibt. Nur wenn dies der Fall ist (anderenfalls wird ein Laufzeitfehler ausgelöst, der in der übergeordneten Funktion ausgewertet wird), haben wir einen brauchbaren Ausdruck übergeben. Erst dieser wird ganz banal mittels CLng endgültig in einen Long-Wert konvertiert und mit dem zuvor ermittelten Vorzeichenwert multipliziert.
Private Function zCLngX(ByVal Value As String) As Long
Dim nSgn As Long
Value = Trim$(Value)
If Left$(Value, 1) = "-" Then
Value = Mid$(Value, 2)
nSgn = -1
Else
nSgn = 1
End If
Select Case True
Case CLng(Value) = 0
zCLngX = 0
Case Value = Format$(Value, "#")
zCLngX = CLng(Value) * nSgn
Case Else
Err.Raise ftnErrInvalidParam
End Select
End Function
Die folgende Funktion zReplaceX für Visual Basic 5 beseitigt überflüssige Leerzeichen aus einem Zahl/Bruch-Ausdruck.
Private Sub zReplaceX(Value As String)
Dim nPos As Long
Value = Trim$(Value)
Do
nPos = InStr(Value, " /")
If nPos Then
Value = Left$(Value, nPos - 1) & Mid$(Value, nPos + 1)
Else
Exit Do
End If
Loop
Do
nPos = InStr(Value, "/ ")
If nPos Then
Value = Left$(Value, nPos) & Mid$(Value, nPos + 2)
Else
Exit Do
End If
Loop
End Sub
Der folgende für VB 5 benötigte Ersatz der ebenfalls erst seit VB 6 verfügbaren Split-Funktion ist kein vollwertiger Ersatz, sondern enthält nur eine speziell auf die hier benötigten Bedürfnisse angepasste Version. Sie gibt nur im Erfolgsfall ein Array mit einem oder mit zwei Element(en) zurück, anderenfalls ein Array mit 2 Elementen (was als Fehler gewertet wird).
Private Function zSplit(Str As String, Separator As String) _
As Variant
Dim nPos As Long
Dim nParts() As String
nPos = InStr(Str, Separator)
If nPos Then
If InStr(Mid$(Str, nPos + 1), Separator) Then
ReDim nParts(0 To 2)
Else
ReDim nParts(0 To 1)
nParts(0) = Left$(Str, nPos - 1)
nParts(1) = Mid$(Str, nPos + 1)
End If
Else
ReDim nParts(0)
nParts(0) = Str
End If
zSplit = nParts
End Function
|