Category: Datentypen

Datentypen und Berechnungen

By , 25. Juli 2015

Hallo,
heute möchte ich euch ein paar Fallstricke zeigen in die man bei Berechnungen innerhalb VBA tappen kann.
Doch Zuvor unsere kleine Testprozedur:

Sub berechnen()
    Dim i As Long, k As Long
    i = 1 * 64000
    k = 2 * 32000
End Sub

Wir setzen auf die Prozedur einen Haltepunkt und starten sie.
Ihr werdet sehen dass i korrekt berechnet wird, aber bei k ein Überlauf-Fehler kommt.
Der Grund liegt in der Größe der Zahlen.

Macht einfach mal folgendes:
Haltepunkt setzen und Prozedur starten.
Markiert 1 * 64000, rechte Maustaste klicken und auf „Überwachung hinzufügen“ klicken.
Im Fenster für Überwachungsausdrücke seht ihr jetzt als Datentyp „Long“ stehen.
Dann macht das gleiche für 2 * 32000. Jetzt sehr ihr als Datentyp „Integer“.

Das bedeutet, dass obwohl ihr die Variablen als Long deklariert habt, die Berechnung selbst unter einem anderen Datentyp laufen kann. Access nimmt sich hier einfach die größte Zahl aus der Berechnung und ermittelt anhand deren den Datentyp. Und 32000 passt nunmal ziemlich gut in den signed Integer Wertebereich hinein.

Hier ein paar gebräuchliche Datentypen mit ihren Wertebereichen:

Byte:     -128 bis 127 bzw. unsigned von 0 bis 255
Integer:  -32768 bis 32767 bzw. unsigned von 0 bis 65535
Long:     −2.147.483.648 bis 2.147.483.647 bzw. unsignet von 0 bis 4.294.967.295

Um jetzt das ganze noch verwirrender zu machen folgende Multiplikationen:

i = 2.1 * 32000
k = 2 = 32000

Wie Wunder gibt es bei i keinen Überlauf-Fehler, bei k aber schon. Wieder als Überwachungsausdruck eingefügt zeigt der Debugger nun für 2.1 * 32000 jetzt als Datentyp Double an.
Das liegt an der Nachkommastelle, die VBA dazu bewegt hat den Double-Datentyp zu verwenden.

Um nun aber 2 * 32000 berechnen zu können gibts einfach einen Trick.
entweder man schreibt hinter die 2 das Formarzeichen „#“ oder man schreibt einfach 2.0, welches automatisch auch in ein 2# umgewandelt wird.
Werden nun aber Variablen multipliziert, so ist der deklarierte Datentyp der Variablen maßgebend.

Ihr seht, eine kleine Berechnung kann ganz schön Kopfzerbrechen verursachen, aber nun wisst ihr wie ihr das umschiffen könnt.

Bis dahin
© 2015 Andreas Vogt

Modulo großer Zahlen berechnen

By , 23. Juli 2015

Hallo,
bei einer Berechnung von Modulo-Werten, also dem ganzzahligen Rest einer Division, bin ich auf ein Problem gestoßen, dessen Lösung ich euch nicht vorenthalten möchte.
Angenommen ihr möchtet den Modulo-Wert einer sehr großen Zahl berechnen. Dann stößt Access schnell an die Grenze des Longinteger-Wertebereichs, der bekanntlich bis 2147483647 reicht.
Beispiel:
2147483647 Mod 97 Ergebnis 65
2147483648 Mod 97 Ergebnis Überlauf.

Erinnert man sich wie der Modulo-Wert (ganzzahliger Rest einer Division) berechnet wird:
2147483648 geteilt durch 97 ergibt 22139006,86
Multipliziert man 22139006 mit 97, und zieht das Ergebnis von 2147483648 ab, so erhält man als Modulo-Wert 66

Und genau so bauen wir eine kleine Hilfsfunktion auf, die uns den Modulo-Wert berechnet:

Public Function Modulo(ByVal Dividend As Double, ByVal Devisor As Double) As Long
    Dim GanzzahlErgebnis As Long
    If Devisor = 0 Then Exit Function
    
    GanzzahlErgebnis = Fix(Dividend / Devisor)
    Modulo = Dividend - GanzzahlErgebnis * Devisor
End Function

Der Wert von GanzzahlErgebnis muss wirklich die abgeschnittene ganze Zahl der Division sein, deswegen muss hier die Funktion fix() verwendet werden.
Da wir immer den ganzzahligen Rest als Ergebnis möchten, genügt es die Prozedur als Long zu deklarieren.

Kopiert die Funktion in ein Modul und ruft sie z.B. wie folgt auf:

MsgBox Modulo(2200000000, 97)

Als Antwort wird die Zahl 36 ausgegeben.

Bis dahin
© 2015 Andreas Vogt

Umgang mit Optionswerten

By , 15. Mai 2015

Hallo,
heute befasse ich mich mit dem Thema „Umgang mit Optionswerten“. Es gibt kaum eine Anwendung die ohne Optionen auskommt. Sei es ein bestimmtes Verzeichnis oder bestimmte Werte oder Zustände.
Per Zufall bin ich heute auf eine Sache gestoßen in Verbindung mit Arrays: Ich wollte die in der gespeicherten Tabelle in ein Array laden um auf sie zugreifen zu können. Aber ein Element eines Arrays lässt sich nur über den Index abrufen, nicht über einen aussagekräftigen Namen.
Also hab ich mal herumgetüftelt, und schließlich doch eine sehr brauchbare Lösung gefunden.
Meine Tabelle hat 6 Optionsfelder. Ich erstelle also zuerst einen Öffentlichen Enum mit den Feldnamen:

Public Enum ltOption
    sportjahr = 0
    old_sportjahr = 1
    vereinsname = 2
    vereinsNummer = 3
    adminpassword = 4
    usestartgeld = 5
End Enum

Als nächstes benötigen wird 2 Variablen:

Public myOptions(5) As Variant
Private FieldNameArr As Variant

Zum Setzen der Optionswerte und zum Auslesen benötigen wird Get/Let Properties:

Public Property Get Settings(cOption As ltOption) As Variant
    Settings = myOptions(cOption)
End Property

Public Property Let Settings(cOption As ltOption, vValue As Variant)
    myOptions(cOption) = vValue
    CurrentDb.Execute "Update tabOptionen set " & FieldNameArr(cOption) & " = " & OptionDataType(vValue, cOption)
End Property

In der Get-Property wird als Optionswert die Nummer aus dem Enum übergeben. Daher kann man nun den entsprechenden Optionswert aus dem Array myOptions entnehmen.
In der Let-Property kommt jetzt eine Besonderheit zum Zuge, die ich vorher gar nicht kannte.
Definiert man eine Let-Property mit 2 Parametern, so kann man die Property wie folgt aufrufen:

PropertyName(ErsterParameter) = ZweiterParameter

Außerdem wird die Parameteränderung auch gleich in die Datenbank geschrieben damit sie permanent ist. Der Feldname erhalten wir aus dem Array FieldNameArr() mit dem Enum-Wert als Index.
Da wir hier an dieser Stelle nicht wissen, welcher Datentyp das Feld hat, muss man dies anhand des Enum-Wertes cOption überprüfen. Dies geschieht in einer kleinen Prozedur:

Private Function OptionDataType(vValue As Variant, cOption As Long) As Variant
    OptionDataType = vValue
    If cOption = 2 Or cOption = 4 Then
        OptionDataType = "'" & OptionDataType & "'"
    End If
End Function

D.H. Die Optionswerte 2 und 4, also die Felder vereinsname und adminpassword werden als Strings behandelt und in einfache Hochkommatas eingeschlossen.

Was noch fehlt ist die Initialisierung wo erstmals die Optionen ausgelesen und in das Array geschrieben werden. Dazu habe ich eine Public Sub Main() erstellt, die von einem Autoexec-Makro aufgerufen wird:

Public Sub Main()
    Dim i As Long
    FieldNameArr = Split("sportjahr,old_sportjahr,vereinsname,vereinsnummer,adminpassword,useStartgeld", ",")
    For i = 0 To 5
        myOptions(i) = DLookup(CStr(FieldNameArr (i)), "tabOptionen")
    Next i
    DoCmd.OpenForm "frmStart"
End Function

Im Array FieldNameArr stehen jetzt die tatsächlichen Feldnamen der Tabelle tabOptionen drin, wogegen man es oben beim Enum nicht so genau nehmen muss.
Anstelle der DLookup-Funktion kann man auch dessen Ersatzfunktionen verwenden die schneller arbeiten.

So unser Modul ist somit fertig und kann eingesetzt werden. Ich habe dazu mal ein Beispiel erstellt, das sich leicht nachbauen lässt. Man benötigt dazu:
1 Textfeld Text0, 3 CommandButtons cmdSet, cmdReset und cmdView.
Der Code sieht dann wie folgt aus:

Private Sub cmdReset_Click()
    Settings(sportjahr) = Settings(old_sportjahr)
End Sub

Private Sub cmdView_Click()
    Me.Text0 = Settings(sportjahr)
End Sub

Private Sub cmdSet_Click()
    Settings(old_sportjahr) = Settings(sportjahr)
    Settings(sportjahr) = 2017
End Sub

Private Sub Form_Load()
    Me.Text0 = Settings(sportjahr)
End Sub

Beim Formular laden wird das Sportjahr angezeigt. Bei cmdSet wird zuerst das aktuelle Sportjahr gesichert indem man dessen wert der Option old_sportjahr zuweist. danach wird das sportjahr mit 2017 überschrieben. Beim Reset wird nun der Wert aus old_sportjahr genommen und mit diesem der die Option sportjahr überschrieben.

Jetzt erahnt man auch warum das Enum im Modul als Public deklariert wurde, den wenn man jetzt schreibt „Settings(“ erscheinen die Werte aus dem Enum als Intellisense.
Um das speichern der Werte in der Tabelle kümmert sich auch das Modul, egal um was für ein Datentyp es sich handelt.
Probiert es aus, setzt Breakpoints um den Codeablauf zu verstehen, es ist ziemlich cool.

Bis dahin
©2015 Andreas Vogt

OfficeFolders theme by Themocracy