VB.NET löst bei einem Überlauf der Gleitkomma-Arithmetik in DOUBLE-Variablen keinen Fehler aus, das betroffene Programm stürzt aber auch nicht ab. Bei der Umstellung von VB6-Programmen auf VB.NET ist zu beachten, dass die ON ERROR GOTO-Fehlerbehandlung beim Gleitkomma-Überlauf nicht mehr die gewohnten auffangbaren Fehler auslöst. Einführung VB.NET löst bei einem Überlauf der Gleitkomma-Arithmetik in DOUBLE-Variablen keinen Fehler aus, das betroffene Programm stürzt aber auch nicht ab. Bei der Umstellung von VB6-Programmen auf VB.NET ist zu beachten, dass die ON ERROR GOTO-Fehlerbehandlung beim Gleitkomma-Überlauf nicht mehr die gewohnten auffangbaren Fehler auslöst. Es stellt sich die Frage: Was geschieht eigentlich in einem Programm, das nach einem Gleitkomma-Fehler einfach weiterläuft? Die im Folgenden mitgeteilten (unvollständigen!) Befunde sind durch Herumprobieren mit VB.NET 2005 EE ermittelt worden. Der Datentyp DOUBLE enthält für Reaktionen auf den Überlauf der Gleitkomma-Arithmetik drei Sonderwerte:
Den Begriff INFINITY verwende ich - wie in VB.NET üblich - als übergeordnete Bezeichnung für Positive- und Negative-Infinity-Werte. Genau genommen gibt es noch weitere "specials", die in VB.NET manchmal auftreten (z.B. QNAN statt NAN), auf die ich aber nicht näher eingehen will. Diese ganzen Sonderwerte korrespondieren mit Konventionen des IEEE-Formats. Dieser Workshop ist nicht als ein Regelwerk für den Umgang mit DOUBLE-Sonderbedingungen zu verstehen, sondern als eine Warnung für Umsteiger, die Programmiersprachen gewohnt sind, die solche Zustände und die daraus resultierenden Operationen nicht kennen. 1. Die Sonderwerte "PositiveInfinity" und "NegativeInfinity" Der Wert dieser Konstanten ist das Ergebnis einer Division einer positiven Zahl durch null. Diese Konstante wird zurückgegeben, wenn das Ergebnis einer Operation größer als die Konstante "Double.MaxValue" ist. Verwenden Sie "IsPositiveInfinity" um festzustellen, ob ein Wert "+unendlich" ist. Durch einen Vergleich mit einem PositiveInfinity-Wert ist nicht feststellbar, ob ein Wert "+unendlich" ist. Klingt mysteriös! Versuchen wir, es aufzuklären ... Es werden hier zwei Fehlerbedingungen miteinander vermischt: Der DivisionByZero-Error und der Overflow-Error. (Nicht jeder wird die Verwendung des Begriffs "Fehler" oder "Ausnahme" in diesem Zusammenhang akzeptieren, sondern stattdessen vielleicht eher "Sonderzustand" favorisieren. Es ist aber gute VB-Tradition, das so zu bezeichnen.) Beide Fehlerbedingungen in der Gleitkomma-Arithmetik liefern zwar den "gleichen" Wert "+unendlich", aber VB-intern werden sie offenbar unterschieden. Dim x, y As Double x = 1.0E+308 : y = 1.0E+308 If Double.IsPositiveInfinity(x + y) Then Stop If Double.IsPositiveInfinity(5.0 / 0.0) Then Stop Beide Stopp-Bedingungen werden ausgelöst. (x+y) erzeugt den Überlauf-Infinity, weil das Ergebnis die Konstante Double.MaxValue überschreitet; (5.0 / 0.0) erzeugt den DivisionByZero-Infinity. Dass diese beiden Infinity-Varianten tatsächlich unterschieden werden, zeigt sich an folgenden Beispielen (x, y wie oben): If 5.0 / 0.0 = 6.0 / 0.0 Then Stop If (x + y) = (x * 2) Then Stop If (5.0 / 0.0) = (x + y) Then Stop Die ersten beiden Stopp-Bedingungen werden ausgelöst, weil hier jeweils die gleichen Fehlerarten verglichen werden. Die dritte Stopp-Bedingung wird nicht ausgelöst, obwohl auch hier beide Terme eigentlich "PositiveInfinity" liefern - aber ihnen eben zwei unterschiedliche Fehler-Typen zugrunde liegen. If 5.0 / 0.0 = Double.PositiveInfinity Then Stop If CDbl(x + y) = Double.PositiveInfinity Then Stop If x + y = Double.PositiveInfinity Then Stop Die ersten beiden Stopp-Bedingungen werden ausgelöst, die dritte nicht. Der Overflow-Error ist erst nach expliziter Umwandlung in einen Double-Wert tatsächlich durch den Gleichheitsoperator als "PositiveInfinity" zu identifizieren. Wie verhält sich INFINITY nach der Zuweisung auf eine Double-Variable? Dim x, y, dbz, ofl As Double x = 1.0E+308 : y = 1.0E+308 dbz = 5.0 / 0.0 : ofl = x + y If dbz = ofl Then Stop If dbz = Double.PositiveInfinity Then Stop If ofl = Double.PositiveInfinity Then Stop Alle drei Stopp-Bedingungen werden ausgelöst. Das Gleiche gilt entsprechend für den Sonderwert "NegativeInfinity". Falls ein Ausdruck einen Fehler in der Gleitkomma-Arithmetik auslöst, enthält die Ergebnis-Variable vom Typ Double den Wert "PositiveInfinity" bzw. "NegativeInfinity", unabhängig von der Art des Fehlers. Es gibt allerdings eine wichtige Ausnahme: 0.0 / 0.0 --> Sonderwert: NAN Die VB-Dokumentation (2005) hält übrigens (5.0 / 0.0) = "PositiveInfinity" für mathematisch völlig korrekt: "In the previous procedure, 'Infinity' might not be what you expected, but it is mathematically correct - 0 goes into 5 an infinite number of times." In meinem Handbuch der Mathematik steht ROT UNTERLEGT für reelle Zahlen: Eigentlich ist die Bezeichnung "mathematisch korrekt" hier überhaupt nicht anwendbar. Der Datentyp DOUBLE bildet (weitgehend) die IEEE-Konventionen ab und dient nicht der möglichst präzisen Umsetzung der reellen Zahlen in ein digitales System. Er unterliegt deshalb auch nicht der Beurteilung aus der Perspektive einer mathematischen Axiomatik. 2. Was geschieht bei der Ausführung der mathematischen Methoden? Beispiele: Dim erg As Double erg = Math.Log(-1) ' ungültiges Argument (->NAN) erg = Math.Sqrt(-1) ' ungültiges Argument (->NAN) erg = Math.Exp(900) ' Überlauf des Datentyps (->INFINITY) erg = Math.Tan(2.0# * Math.Atan(1.0#)) ' nun ja Allgemein: Bei einem ungültigen Argument wird die Ergebnisvariable auf den Wert NAN gesetzt, bei einem Überlauf wird das Ergebnis positiv oder negativ INFINITY. Das Verhalten der mathematischen Methoden, wenn INFINITY-Werte als Funktions-Argumente auftreten, besitzt eine gewisse innere Schlüssigkeit:Es gilt: die Wurzel eines Wertes kleiner 0.0 ist undefiniert, die Wurzel eines unendlich großen Wertes ist unendlich. Dim erg, dbz As Double dbz = (5.0 / 0.0) erg = Math.Sqrt(dbz) If Double.IsPositiveInfinity(erg) Then Stop dbz = (-5.0 / 0.0) erg = Math.Sqrt(dbz) If Double.IsNaN(erg) Then Stop Beide Stopp-Bedingungen werden ausgelöst. Falls ein INFINITY-Argument bei mathematischen Funktionen auftritt, kann es zum "Umschlagen" in einen gültigen Wert kommen: exp(-unendlich) = 0.0. Dieses Funktionsergebnis ist - für sich betrachtet - durchaus begründbar. Innerhalb umfangreicher Terme auftretend, verschleiert es aber eventuell wirkungsvoll den Umstand, dass ein Gleitkomma-Überlauf aufgetreten ist. Zur ATAN-Methode berichtet die VB-Dokumentation, dass der Wert (-PI / 2) bzw. (+PI / 2) zurückgegeben wird, wenn ein INFINITY-Argument übergeben worden ist. Weitere Beispiele: log(+unendlich) = +unendlich (Details zum Verhalten von Math.Log: vgl. unbedingt VB-Dokumentation) abs(-unendlich) = +unendlich sign(-unendlich) = -1.0 Zum Verhalten der POW-Funktion in Bezug auf die Sonderwerte gibt die VB-Dokumentation detailliert Auskunft. Es werden 16 (!) Fälle unterschieden; in neun dieser Fälle wird in der Ergebnis-Variable einer der Sonderwerte eingetragen. Es empfiehlt sich aus den genannten Gründen, die jeweils benötigten mathematischen VB-Funktionen in eigenen Routinen zu kapseln und vor der Ausführung auf Gültigkeit bzw. Ausprägung des Arguments abzufragen - dabei ggf. den Fehler "ungültiges Argument" (Fehlernummer 5) bzw. "Überlauf" (Fehlernummer 6) per Code auszulösen. Sehr viel weniger effizient ist es, wenn zuerst die Funktion ausgeführt, das Ergebnis auf die Double-Variable "erg" zugewiesen, und folgende Kontroll-Abfrage angehängt wird: If Double.IsInfinity(erg) Or Double.IsNaN(erg) Then Err.Raise(5) Durch diese Abfrage kann der Rechenzeitbedarf einer Funktion um ca. 30-100 % anwachsen. Insbesondere bei Funktionen, die sehr häufig aufgerufen werden, ist das beachtlich. 3. Wie wird mit diesen Sonderwerten in arithmetischen Rechenoperationen verfahren? Im Einzelnen: Für Infinity-Werte gelten dabei die allgemeinen Rechenregeln für Vorzeichen: +unendlich * -unendlich = -unendlich -unendlich * -unendlich = +unendlich Dieses Vorzeichenverhalten gilt auch für die Multiplikation eines gültigen Double-Wertes mit einem Infinity-Wert. Addition oder Subtraktion einer beliebigen Menge gültiger Werte zu/von INFINITY-Werten ändert die unendlich-Werte nicht. Das gilt auch für Addition bzw. Subtraktion des Wertes 0.0. Bedingte Schleifen (z.B. DO ... WHILE), bei denen auf Differenzen abgefragt wird, sprechen deshalb eventuell nicht mehr an und werden zu Endlos-Schleifen. Eine Variable, die auf "+unendlich" gesetzt worden ist, wird bei Vergleichsabfragen - wie zu erwarten - größer als die Konstante "Double.MaxValue" erkannt. Der Wert "-unendlich" ist dementsprechend kleiner als "Double.MinValue". Falls in einem Programmabschnitt vorausgesetzt wird, dass die Werte der verarbeiteten DOUBLE-Variablen innerhalb der gültigen Grenzen liegen, kann das zu unerwarteten Operationen führen. Das Aufeinandertreffen von zwei INFINITY-Werten führt MANCHMAL zum Entstehen eines NAN-Wertes: +unendlich - +unendlich = NAN (jedoch: +unendlich + +unendlich = +unendlich !!) +unendlich / +unendlich = NAN (jedoch: +unendlich * +unendlich = +unendlich !!) Man darf bei den Infinity-Werten nicht mit einem Verhalten rechnen, das dem gültiger Werte entspricht. Stattdessen gilt für sie eine spezifische Operations-Logik. Ein Infinity-Wert, der innerhalb einer Berechnung auftritt, kann im Ergebnis durchaus zu einem gültigen Double-Wert führen: GültigerWert / +unendlich = 0.0 GültigerWert / -unendlich = 0.0 Das erscheint - nach der Einführung von INFINITY - zunächst logisch als durchaus folgerichtig. Die unendliche Aufteilung eines Wertes führt zu Null. Diese Eigenart ist aber besonders "gefährlich". Wenn in einer komplexen Gleichung unerkannt ein Infinity-Wert auftritt, der sofort im Rahmen einer Division "genullt" wird, kann das Gesamtresultat einen gültigen (aber häufig sinnlosen) Wert ergeben. Bei Beteiligung an Ganzzahl- oder Modulo-Divisionen lösen INFINITY-Werte einen Überlauf-Fehler aus, weil die implizite Typumwandlung in eine Ganzzahl scheitert. Das gilt auch für das Verhalten expliziter Typumwandlungsroutinen für Ganzzahlen (z.B. CINT). Die Überwachung der Ganzzahl-Arithmetik reagiert auf die IEEE-Sonderwerte mit dem Auslösen der entsprechenden "Ausnahme". Das Phänomen besteht darin, dass INFINITY-Werte nicht einfach "undefinierte" Werte sind, sondern eine eigenständige numerische Logik besitzen. Dem muss man entweder sorgsam aus dem Weg gehen - also alle irgendwo möglichen Gleitkomma-Überläufe und unzulässigen Funktionsaufrufe abfangen - oder: man muss lernen, mit diesen Sonderwerten sinnvoll umzugehen. Hierfür muss man deren Verhalten ganz genau kennen .... 4. Der Sonderwert NAN Die mathematischen Funktionen akzeptieren den Sonderwert NAN als Argument und liefern dann NAN auch im Ergebnis zurück. Bei Anwendung mathematischer Funktionen auf INFINITY-Werte entstehen ebenfalls manchmal NAN-Werte, z. B. bei den Winkelfunktionen SIN, COS, TAN (vgl. aber ATAN). Die Sign-Funktion löst bei einem NAN-Wert als Argument einen auffangbaren Laufzeitfehler aus: sign(NAN) ---> Fehler Der Sonderwert NAN kann nicht durch direkten Vergleich mit der entsprechenden VB-Konstante abgefragt werden, stattdessen muss zwingend die Funktion Double. IsNAN verwendet werden. (obwohl beide Werte in der IDE identisch als -1#IND angezeigt werden - also Vorsicht !!) Dim nan As Double nan = 0.0 / 0.0 If nan = Double.NaN Then Stop If Double.IsNaN(nan) Then Stop Nur die zweite Stopp-Bedingung wird ausgelöst !! Ist das unerkannte Auftreten von NAN-Werten in einer Funktion deshalb relativ harmlos, weil Funktions-Rückgaben nahezu zwangsläufig ebenfalls NAN-Werte annehmen? Leider nicht. Umgewandelt in eine boolsche Variable (z.B. VB-Funktion: "CBOOL") liefert eine Double-Variable, die auf NAN gesetzt worden ist, stets den Wert "TRUE". Beim Vergleich von NAN mit einem gültigen numerischen Wert oder mit einem zweiten NAN-Wert ergibt sich immer "FALSE". Unerkannte NAN-Werte können deshalb die Abfragelogik in einer Routine aushebeln. Dim z As Double z = Math.Log(-1) '--> NAN If CBool(z) Then Stop If z > 100 Then Stop If z = 100 Then Stop If z < 100 Then Stop Nur die erste STOPP-Bedingung wird ausgelöst. Alle drei Vergleiche mit dem gültigen Wert 100 werden als "FALSE" weiterverarbeitet. Der Vergleich einer nicht-numerischen Kennung mit einem gültigen numerischen Wert müsste eigentlich zwingend eine "Ausnahme" auslösen. Der Vergleich einer STRING-Variable, die einen nicht-numerischen Inhalt hat, mit einem zulässigen Double-Wert wird nicht durchgeführt, sondern führt zu einem Fehler ("Conversion is not valid"). Das Verhalten von NAN ist in dieser Hinsicht also absolut ungewöhnlich. Im Code führt diese Eigentümlichkeit zu Problemen wie dem Folgenden: If z > 100 Then ' erwartet: z > 100 'inclusive: z = PositiveInfinity ElseIf z < 100 Then ' erwartet: z < 100 'inclusive: z = NegativeInfinity Else ' erwartet: z = 100 ' inclusive: z = NAN End If Folgender Code ist stattdessen erforderlich, um den Sonderwert NAN zu identifizieren: If z > 100 Then ' erwartet: z > 100 ' inclusive: z = PositiveInfinity ElseIf z < 100 Then ' erwartet: z < 100 ' inclusive: z = NegativeInfinity ElseIf z = 100 then ' erwartet: z = 100 Else ' z = NAN End If Aus Performancegründen sollte auf eine Abfrage durch die Methode Double.IsNan möglichst verzichtet werden. 5. Was sonst noch wichtig sein könnte .... Ein Infinity-Wert in einer Variable des Typs SINGLE entspricht - bei wertebezogenen Operationen und Vergleichen - dem Infinity-Wert in einer Double-Variable (sng = dbl). Auf Grund impliziter Typumwandlung können Operationen / Funktionen, deren Eingabevariable nicht vom Typ DOUBLE sind, im Ergebnis DOUBLE-Werte liefern, die dann auch ggf. die beschriebenen Sonderwerte enthalten. Ein Beispiel: Dim i, k As Short i = 100 : k = 0 If i / k > 40000 Then Stop Auf den ersten Blick erwartet man nicht, dass diese STOPP-Bedingung jemals ausgelöst werden könnte. Der Datentyp SHORT erlaubt bekanntlich keine Werte in der Größe 40000. Zudem vermutet man wahrscheinlich, dass die Division von ganzzahligen Werten mit einem Divisor 0 zu einem Ganzzahl-Arithmetik-Alarm führt. Die Division liefert aber im Ergebnis den Datentyp DOUBLE, löst keinen Alarm aus und weil die Variable k auf 0 steht, erhält dieser DOUBLE-Ergebniswert den Sonderwert "PositiveInfinity" zugewiesen. Und INFINITY ist nun mal größer als 40000 definiert. Die obige STOPP-Bedingung wird deshalb ausgelöst. If CINT(i / k) > 40000 Then Stop In diesem Fall kommt es zum Overflow-Error, weil der entstehende INFINITY-Wert nicht durch die Funktion "CINT" umgewandelt werden kann. Dim i, k As Short i = 0 : k = 0 If i / k > 40000 Then Stop If i / k <= 40000 Then Stop Diese beiden Stopp-Bedingungen werden nicht ausgelöst, weil im Ergebnis-DOUBLE der Sonderwert NAN auftritt und dieser Wert liefert beim Vergleich mit gültigen Werten immer FALSE. 6. Wie kann man sein VB-Programm vor den Kapriolen der IEEE-Sonderwerte schützen?
Die Mehrzahl der veröffentlichten numerischen Verfahren bezieht sich auf DOUBLE-(ähnliche) Variable und ist entsprechend abgestimmt und optimiert. Insbesondere die hohe Rechengenauigkeit von DECIMAL kann im Einzelfall bei der Übertragung Probleme verursachen:
Die Verwendung des Datentyps DECIMAL ist deshalb nur bedingt empfehlenswert. Man kann - um sicherzugehen - den unvorteilhaften Weg einschlagen und statt DOUBLE-Variablen Instanzen einer Klasse verwenden, die eine Variable des Datentyps DOUBLE kapselt und alle Wert-Zuweisungen auf diese Variable überwacht. Diese Strategie führt allerdings zu einem erheblichen Anwachsen des Rechenzeitbedarfs bei Ausführung umfangreicher Gleitkomma-Operationen. Zudem sind Anpassungen des rufenden Quellcodes erforderlich. Der Anpassungs-Aufwand lässt sich ab VB.NET 2005 durch die Überladung von arithmetischen und logischen Operatoren erheblich vermindern. Vielleicht (Ich behaupte: mit an Sicherheit grenzender Wahrscheinlichkeit) werden kommende Versionen von VB.NET die Überwachung der Gleitkomma-Operationen wieder beinhalten. Man kann also auch abwarten und Tee trinken ..... 7. Überwachung der Gleitkomma-Arithmetik durch die Klasse "cDouble" Sollen in einer Routine statt DOUBLE-Variablen Instanzen dieser Klasse verwendet werden, sind im wesentlichen folgende Anpassungen des Codes der Routine erforderlich: (Eine Klasse, die weniger Anpassung im Code erfordert, ist mir leider nicht eingefallen. Meine Kenntnisse in VB.NET sind aber ziemlich bescheiden. Insbesondere kann man in VB.NET offenbar keine parameterfreie DEFAULT PROPERTY programmieren.) Dim X, Y, Z as Double --> Dim X, Y, Z as New cDouble X = 502.123 --> X.DoubleX = 502.123 X = Y --> X.Value = Y (Warnung: X=Y setzt eine Referenz !!) Z = (X+Y) --> Z = (X+Y) X = Sqrt(Z) --> X = Z.Sqrt oder X = Sqrt(Z.DoubleX) Z = X ^ Y --> Z = X.Pow(Y) Z = 1 / X --> Dim eins as new cdouble: eins.DoubleX = 1.0 : Z = eins / X Z = Abs((X-Y)/(X+Y)) --> Z = ((X-Y)/(X+Y)).Abs oder Z = Abs(((X-Y)/(X+Y)).DoubleX) Z = Exp(x) --> Z = Exp(X.DoubleX) If Z + X > Y Then … --> If Z + X > Y Then … If Abs(X-Y) < eps Then …. --> If X = Y Then … Dieser Workshop wurde bereits 14.738 mal aufgerufen.
Anzeige
Diesen und auch alle anderen Workshops finden Sie auch auf unserer aktuellen vb@rchiv Vol.6 (einschl. Beispielprojekt!) Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv! - nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten - Symbol-Galerie mit mehr als 3.200 Icons im modernen Look Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m. |
Neu! sevEingabe 3.0 Einfach stark! Ein einziges Eingabe-Control für alle benötigten Eingabetypen und -formate, inkl. Kalender-, Taschenrechner und Floskelfunktion, mehrspaltige ComboBox mit DB-Anbindung, ImageComboBox u.v.m. Tipp des Monats Januar 2025 Dieter Otter Zeilen einer MultiLine-TextBox ermitteln (VB.NET) Dieser Zipp zeigt, wie man die Zeilen einer MultiLine-TextBox exakt so ermitteln kann, wie diese auch in der TextBox dargestellt werden. Access-Tools Vol.1 Über 400 MByte Inhalt Mehr als 250 Access-Beispiele, 25 Add-Ins und ActiveX-Komponenten, 16 VB-Projekt inkl. Source, mehr als 320 Tipps & Tricks für Access und VB |
|||||||||||||
Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein. |