Live-Forum - Die aktuellen Beiträge
Anzeige
Archiv - Navigation
328to332
Aktuelles Verzeichnis
Verzeichnis Index
Übersicht Verzeichnisse
Vorheriger Thread
Rückwärts Blättern
Nächster Thread
Vorwärts blättern
Anzeige
HERBERS
Excel-Forum (Archiv)
20+ Jahre Excel-Kompetenz: Von Anwendern, für Anwender
328to332
328to332
Aktuelles Verzeichnis
Verzeichnis Index
Verzeichnis Index
Übersicht Verzeichnisse
Inhaltsverzeichnis

Rechenfehler in VBA

Rechenfehler in VBA
30.10.2003 08:56:32
Andreas Eckmann
Hallo zusammen,

ich hatte vor ca. 2 Wochen eine Anfrage zu obigem Thema gestellt. Leider konntet Ihr mir nicht abschließend helfen. Ich hatte am Schluß versprochen, meine Lösung zu präsentieren, sobald ich sie habe. Hier ist sie nun.

Ziel war es, eine bis zu 19stellige Zahl in einem Feld mit 10 Zeichen unterzubringen. Das Feld kann alle darstellbaren ASCII-Zeichen aufnehmen. Durch die Formel a^b=x^c (a=Anzahl vorkommenden Zeichen (hier 10), b=ursprüngliche Länge (hier 19), c=Länge des Feldes (hier 10), x=gesucht) ergibt sich x=79,4, rund 80. Also muß ich die max. 19stellige Zahl in Vielfache von 80^i zerlegen. Dieser Wert wird dann um 33 erhöht in ASCII umgewandelt (33 ist das erste darstellbare ASCII-Zeichen nach dem Leerzeuchen).

Leider läßt sich sowas weder in EXCEL noch VBA ohne ein paar ein Tricks berechnen. Bei der BErechnung des Rests einer Division verschlucken beide die letzten 2 bis 4 Ziffern.

In Excel habe ich dann die Berechnung des Rest ohne die letzten 6 Ziffern gemacht (weil für 80^i mit i>=6 sind die letzten 6 Ziffern = "000000") und habe anschließend die 6 Ziffern des vorherigen Rests mit verketten() wieder angefügt.

In VBA habe ich es wie unten aufgeführt gelöst. Die Lösung zur Behandlung großer Zahlen heißt hier z=CDec(...).

Genug der Vorrede. "9999999999999999999" ergibt "kIF-UHpppp".


Sub BerechneCPIu()
Dim iAusgangswert As Variant
Dim iRest As Variant
Dim iDivisor As Integer
Dim iVersatz As Integer
iDivisor = 80
iVersatz = 33
iAusgangswert = 1
iAusgangswert = "9999999999999999999"
iRest = iAusgangswert
For i = 9 To 0 Step -1
iExponent = iDivisor ^ i
iTeiler = Int(iRest / iExponent)
sZeichen = Chr(iTeiler + iVersatz)
sErgebnis = sErgebnis + sZeichen
If iTeiler > 0 Then
z = CDec(iTeiler * iExponent)
iRest = iRest - z
End If
Next i
'--- Ausgabe CPIu
Debug.Print sErgebnis
End Sub



Und hier die Umkehrung.


Sub BerechneCgPA2()
Dim sAusgangswert As String
Dim iVersatz As Variant
Dim iEndwert As Variant
Dim sEinzelnesZeichen As String
Dim y As String
iDivisor = 80
iVersatz = 33
sAusgangswert = "kIF-UHpppp"
For i = 0 To 9
sEinzelnesZeichen = Mid(sAusgangswert, 10 - i, 1)
y = Asc(sEinzelnesZeichen) - iVersatz
iExponent = (iDivisor ^ i)
iEndwert = CDec(iEndwert + iExponent * y)
Next i
'--- Ausgabe CPIu
Debug.Print Str(iEndwert)
End Sub

14
Beiträge zum Forumthread
Beiträge zu diesem Forumthread

Betreff
Datum
Anwender
Anzeige
AW: Rechenfehler in VBA
30.10.2003 09:23:39
Michael Scheffler
Hallo,

eine Frage: wozu braucht man das?

Gruß

Micha
AW: Rechenfehler in VBA
30.10.2003 10:58:41
Hans W. Hofmann
Hallo Andreas, ich sag mal ganz direkt:
Dein Algorithmus ist für die Tonne, weil es den Wert 80^9 = 1,3*10^17 nicht gibt. Die Rechengenauigkeit ist nun mal auf 15 Stellen limitiert. Damit ist Dein CPIu nicht eindeutig weil Deine ersten beiden Divisoren (auf 15 Stellen) gerundet werden.
Du mußt einen anderen Weg gehen, der NICHT auf arithmetischer Berechnung basiert - hatte ich das nicht schon einmal vorgeschlagen?
Z.B. Du hast 19 Ziffern 0....9.
Wenn man ein Byte in ein High und Low Teil (4 Bit Wert) zerlegt, dann kann ich im High-Teil 16 und im Low Teil 16 speichern. Du arbeitest Deinen Zahlen-String (der besser 20 Zeichen lang ist) paarweise ab und machst aus 2 Byte 1 Byte z.B. "38" wird
3 = 0011
8 = 1000
daraus wird zusammengesetzt
0011 1000 = 56 = "8"
Alles modulo 16 - Klaro?

Gruß HW
Anzeige
AW: Rechenfehler in VBA
30.10.2003 14:33:37
Andreas Eckmann
Hallo Hans,

danke für die offenen Worte (.. für die Tonne). Wo siehst Du das Problem? Ich habe beide Makros mit Werten getestet wie 80^i und 80^i + 1 und 80^i - 1 und 4930386 und 496956606 und eben 9999999999999999999.

Modulo 16 hatte ich auch schon versucht. Leider benötige ich dann mehr als 80 ASCII Zeichen, weil es Lücken in der ASCII Abfolge gibt. Fällt also aus.

Übrigens weiß ich vorher nicht, wieviel Stellen die Ausgangszahl hat. Kann auch 7 sein. Wegen x=a^(b/c) und darstellbare ASCII Zeichen gleich 33...126 (126-33=94) geht es nur bis b=19. Bei b=20 bin ich schon bei x=100 (100 > 94).

http://www.asciitable.com/

Gruß
Andreas
Anzeige
AW: Rechenfehler in VBA
30.10.2003 15:02:56
Michael Scheffler
Hallo Andreas,

reine Neugierde: wozu braucht man das? Da ich den Ursprungs-Therad nicht mehr finde, hatte ich das gefragt.

Gruß
Micha
AW: Rechenfehler in VBA
30.10.2003 15:35:54
Andreas Eckmann
Wozu braucht man das. Ziel ist es, eine bis zu 19stellige Zahl in einem Feld mit 10 Zeichen unterzubringen. Das Feld kann alle darstellbaren ASCII-Zeichen aufnehmen.

Der Hintergrund ist der, daß hier mehrere Computer miteinander kommunizieren. Ein PC generiert einen String, wobei der 10stellige ASCII-Code Teil des String ist. Ein 2. Rechner (UNIX) nimmt diesen String und speichert ihn in einer Datenbank. Ein 3. Rechner (UNIX) fragt die Datenbank ab und bekommt dabei diesen ASCII-Code, den er dann jedoch als ursprüngliche Zahl darstellen soll.

Das eigentliche Problem ist halt die Beschränkung in der Feldgröße. Daher kommt dieser Aufwand. Es ist einfacher und schneller, eine Regel zu finden als das Feld der Datenbank zu vergrößern.
Anzeige
AW: Rechenfehler in VBA
30.10.2003 15:36:53
Hans W. Hofmann
Tja, wat mut dat mut ;-). Ich weiß ja nicht, ob Du Dir Fehler leisten kannst?

Was machst Du?
Also Du definierts ein Zahlensystem mit 80 Symbolen, wegen Teiler 80,
Du arbeitest sozusagen modulo 80!
Wenn Dir jetzt bei den "hohen" Stellen 8 (80^8) und 9 (80^9) die Divisoren/Multiplikatoren zusammengerundet werden, dann verlierst Du Stellen und damit können nicht alle Zahlensymbole abgebildet werden. Verschiedene CPIu können den gleichen 10er Code erhalten! Du mußt nur danach suchen - es ist halt ein sehr großer Zahlenbereich und das fällt evtl. nur bei (wenigen?) kranken Codes auf. Ganz zu schweigen, daß das Problem praktisch schon früher auftreten kann, weil Du bei 15 Stellen Genauigkeit nicht davon ausgehen kannst, dass auch _immer_ alle 15 Stellen gültig sind.
Alte Maschinenzahlregel!

Was Deinen Weg eine Zeitlang am Leben erhält ist die Tatsache, dass vermutlich die Potenzen von 80 "hinten" auf 0en enden, was dann beim Runden nicht auffällt - Runde 00 zu 00 :-). Du bewegst Dich auf sehr dünnem Eis - es kann ne Zeitlang tragen - muss aber nicht. Dein Algorithmus ist nicht stabil..

Gruß HW
Anzeige
AW: Rechenfehler in VBA
30.10.2003 15:49:56
Andreas Eckmann
Danke für den Kommentar. Ich werd dann mal alle Werte testen, wo Du Bedenken hast.
AW: Rechenfehler in VBA
30.10.2003 17:49:30
Hans W. Hofmann
Ajee, ich tue Buße und stelle mal eine Umsetzung meines Algorithmus daneben.

Gruß HW


'(C)oded by HW
'Compact 20 Stellige Zahl in 10 Stellen
'High/Low transmission modulo 16
Function CPIu(iAusgangswert As String)
Dim i As Integer, high As Integer, low As Integer
If Len(iAusgangswert) > 20 Then CPIu = "#Wert!": Exit Function
While Len(iAusgangswert) < 20
iAusgangswert = "0" & iAusgangswert
Wend
For i = Len(iAusgangswert) To 1 Step -2
low = Val(Mid(iAusgangswert, i, 1))
high = Val(Mid(iAusgangswert, i - 1, 1))
CPIu = Chr(Mask0(high) * 16 + Mask0(low)) & CPIu
Next
End Function
  Function reverseCPIu(iAusgangswert As String) Dim i As Integer, high As Integer, low As Integer For i = Len(iAusgangswert) To 1 Step -1 high = Int(Asc(Mid(iAusgangswert, i, 1)) / 16) low = Asc(Mid(iAusgangswert, i, 1)) Mod 16 reverseCPIu = DeMask0(high) & DeMask0(low) & reverseCPIu Next While Asc(reverseCPIu) = 48 reverseCPIu = Mid(reverseCPIu, 2) Wend reverseCPIu = " " & reverseCPIu End Function
  Function Mask0(s As Integer) Mask0 = IIf(s = 0, 15, s) End Function
Function DeMask0(s As Integer) DeMask0 = IIf(s = 15, 0, s) End Function

Anzeige
AW: Rechenfehler in VBA
30.10.2003 23:06:22
Andreas Eckmann
Hallo Hans,

hab Dein Makro ausprobiert. Funktioniert in beide Richtungen einwandfrei. Nur verlasse ich den Bereich der standard ASCII Zeichen (32 bis 127). Und nu?

Noch ein Hinweis. Der komplette Datensatz wird von einem PC generiert und als String in einer Datei abgelegt. Diese Datei wird mittels FTP an einen UNIX Rechner übertragen und dort als Datensatz in die Datenbank aufgenommen. Dieses besagte 10stellige Feld kann dann jedoch auch von einem anderen PC über eine Eingabemaske (java basiert) editiert werden. Und da ist halt die Regel drauf, daß ASCII bis 127 geht. Zum Beispiel: <126> ergibt "~". Das manuelle Editieren wird jedoch nur dann benötigt, wenn der Datensatz zerstört wurde.

Deinen Hinweis mit dem Runden von Zahlen mit mehr als 15 Ziffern kann ich bestätigen. Jedoch nur im Tabellenblatt. Mit meinem Makro in VBA gibts an dieser Stelle keine Probleme.

Gruß Andreas
Anzeige
AW: Rechenfehler in VBA
31.10.2003 09:15:58
Hans W. Hofmann
Hm, Du bist auch im ASCII-Bereich beschränkt ;-).
Dann würde ich die Zahlenoperationen aufteilen um von der Genauigkeitsgrenze weg zu kommen. Teile die Potenzen von 80 in reinen Zahlenwerte und Nullen auf:
z.B. 80^9= 134217728000000000 -> x/134217728/1000000000
dammit bist Du immer im sicheren Bereich. Die paar Konstanten werden als Array gespeichert, machen den Bock nicht fett, oder...
Dim Teiler = Array(8,....134217728)
Dim Zehner = Array(10,...,1000000000)

Gruß HW
AW: Rechenfehler in VBA
31.10.2003 09:27:58
Andreas Eckmann
Das verstehe ich nicht. Kannst Du die Routine beifügen?

Gruß Andreas
AW: Rechenfehler in VBA
02.11.2003 20:30:19
Hans W. Hofmann
Ich hab noch mal nachgedacht und da ist mir der Decimal-Typ eingefallen, der den Zahlenbereich erweitert. Mit dem und einem Weg ohne große Zahlen - aufarbeiten der Basis von unten nach oben - würde Dein Weg jetzt so aussehen.


Function BerechneCPIu(iAusgangswert As String)
Const iVersatz = 33
Dim iDivisor, iTeiler As Variant
iDivisor = CDec(80)
For i = 1 To 9
iTeiler = Split(CDec(iAusgangswert) / iDivisor, ",")
iTeiler(1) = CDec("0," & iTeiler(1)) * 80
BerechneCPIu = Chr(iTeiler(1) + iVersatz) & BerechneCPIu
iAusgangswert = CDec(iTeiler(0))
Next
End Function


Der müsste stabil sein. Den Rückweg findest Du sicher selber.

Gruß HW
Anzeige
AW: Rechenfehler in VBA
03.11.2003 13:43:30
Andreas Eckmann
Hallo Hans,

vielen Dank dass Du Dir so viel Mühe hiermit gibst. Zu Deiner o.g. Funktion bekommen ich bei
iTeiler = Split(...)
die Fehlermeldung "Fehler beim Kompilieren -

Sub oder Funktion nicht definiert."
Ich habe es wie folgt gelöst. Alle Tests waren bisher erfolgreich.

Function CPI(iAusgangswert As Variant)
Dim iRest As Variant
Dim iDivisor As Integer
Dim iVersatz As Integer
iDivisor = 80
iVersatz = 33
iRest = iAusgangswert
For i = 9 To 0 Step -1
iExponent = CDec(iDivisor ^ i)
iTeiler = Int(iRest / iExponent)
sZeichen = Chr(iTeiler + iVersatz)
sErgebnis = sErgebnis + sZeichen
If iTeiler > 0 Then
z = CDec(iTeiler * iExponent)
iRest = CDec(iRest - z)
End If
Next i
CPI = sErgebnis
End Function


Ich habe viel mehr das Problem, die Umkehrung als Funktion hinzubekommen. Ein Debug.Print am Ende der Funktion gibt den richtigen Wert aus. Im Excel steht jedoch #WERT!.


Function CgPA(sAusgangswert As String)
Dim iVersatz As Variant
Dim sEndwert As String
Dim iEndwert As Variant
Dim sEinzelnesZeichen As String
Dim y As String
iDivisor = 80
iVersatz = 33
For i = 0 To 9
sEinzelnesZeichen = Mid(sAusgangswert, 10 - i, 1)
y = Asc(sEinzelnesZeichen) - iVersatz
iExponent = (iDivisor ^ i)
CgPA = CDec(iEndwert + iExponent * y)
Next i
Debug.Print "sAusgangswert=" & sAusgangswert & " CgPA=" & CgPA
End Function


MfG
Andreas Eckmann
Anzeige
AW: Rechenfehler in VBA
03.11.2003 20:36:12
Hans W. Hofmann
Ach bist Du wohl noch mit XL97 gesegnet?
Split dient nur der Aufteilung in Ganzzahl und Dezimalanteil das kann mal umschreiben...
Ich leg mal meine vollständige Version bei!
Zu Deiner Version: Du musst String zurückliefern, weil es so große Zahlen im Tabellenblatt net gibt...

Gruß HW


Function BerechneCPIu(iAusgangswert As String)
Const iVersatz = 33
Dim p As Integer, iDivisor, iTeiler As Variant
iDivisor = CDec(80)
 
While CDec(iAusgangswert) > 0
iAusgangswert = CDec(iAusgangswert) / iDivisor
p = InStr(CStr(iAusgangswert), ",")
If p > 0 Then
iTeiler = CDec(Mid(CStr(iAusgangswert), p))
iAusgangswert = Left(CStr(iAusgangswert), p)
Else
iTeiler = 0
End If
iTeiler = CDec(iTeiler * iDivisor)
BerechneCPIu = Chr(iTeiler + iVersatz) & BerechneCPIu
Wend
 
End Function
    Function BerechneCgPA2(sAusgangswert As String) Const iVersatz = 33 Const iBasis = 80 Dim iDigit As Variant iDigit = 1   For i = Len(sAusgangswert) To 1 Step -1 iZeichen = Asc(Mid(sAusgangswert, i, 1)) - iVersatz BerechneCgPA2 = CDec(BerechneCgPA2 + CDec(iZeichen * iDigit)) iDigit = CDec(iDigit * iBasis) Next BerechneCgPA2 = " " & BerechneCgPA2   End Function
     

Anzeige

Beliebteste Forumthreads (12 Monate)

Anzeige

Beliebteste Forumthreads (12 Monate)

Anzeige
Anzeige
Anzeige