vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#
DTA-Dateien erstellen inkl. BLZ-/Kontonummernpr?fung  
 vb@rchiv Quick-Search: Suche startenErweiterte Suche starten   RSS-Feeds  | Newsletter  | Impressum  | vb@rchiv CD Vol.6  | Shop Copyright ©2000-2014
 
zurück
Rubrik: Variablen/Strings · Algorithmen/Mathematik   |   VB-Versionen: VB2005, VB200817.02.09
Midpoint-Rounding bei IEEE-Variablen

Die Math.Round-Funktion beachtet beim MidpointRounding die begrenzte Darstellungsgenauigkeit von IEEE-Werten nicht angemessen.

Autor:   Manfred BohnBewertung:     [ Jetzt bewerten ]Views:  4.842 
ohne HomepageSystem:  Win2k, WinXP, Vista, Win7, Win8kein Beispielprojekt 

Der Begriff 'MidPointRounding' bezeichnet eine Rundung, bei der der zu rundende Wert genau zwischen zwei benachbarten gerundeten Werten liegt (z.B. liegt der Wert 1,25 bei Rundung auf eine Dezimalstelle genau zwischen 1,2 und 1,3).

Bedeutsam ist die Arbeitsweise des MidpointRounding-Verfahren, falls in einer Datenreihe zahlreiche solcher 'MidPoints' vorkommen. Das ist u.a. oft der Fall, wenn bereits gerundete Werte noch einmal auf eine geringere Stellenzahl gerundet werden. (Gerundete Werte erhält man z.B. bei Daten von Meßsensoren, die die Genauigkeit berücksichtigen, bei der Abfrage von Datenbanktabellen oder bei der Sekundärauswertung von veröffentlichten Datenreihen.)

Über das Standardverfahren, das die MATH.ROUND-Methode verwendet, berichtet die VB-DOKU:
Diese Art der Rundung wird zuweilen als Rundung auf den nächsten Wert bzw. unverzerrte Rundung (Banker's Rounding) bezeichnet. So werden Rundungsfehler reduziert, die sich beim einheitlichen Runden von in der Mitte liegenden Werten in nur eine Richtung ergeben. (Im gerundeten Wert ist die letzte Ziffer immer gerade.)

Bei der Berechnung statistischer Kennwerte aus gerundeten Daten kann diese Ausgleichswirkung der 'unverzerrten Rundung' wichtig sein.

Gleitkomma-Operationen werden in VB-Programmen meist mit Variablen des IEEE-Datentyps 'DOUBLE' ausgeführt, der eine große Werte-Spannweite bietet und besonders effizient ist. Double-Werte liegen im Speicher allerdings nicht exakt, sondern in 'komprimierter' Form vor, d.h. nach IEEE-Norm zerlegt in Mantisse und Exponent. Näheres dazu in der VB-Doku im Abschnitt: 'Problembehandlung bei Datentypen' (Die VB-Intellisense zeigt bei Double-Variablen häufig NICHT die intern tatsächlich vorliegenden Werte an !!).

Bei IEEE-Variablen ist ein zugewiesener Wert, der beim Runden dem 'Midpoint' entspräche, in manchen Fällen nicht exakt darstellbar. Das hat u.a. Auswirkungen auf die Ergebnisse des MidpointRounding-Verfahrens.

Ein Beispiel:

Dim dec5, dec4 As Decimal
Dim dbl5, dbl4 As Double
 
dec5 = 0.5115D
dbl5 = 0.5115#
 
If dbl5 <> dec5 Then Stop
 
dec4 = Math.Round(dec5, 3)
dbl4 = Math.Round(dbl5, 3)
 
If dbl4 <> dec4 Then Stop
 
Dim str As String = dbl5.ToString("G18")

Die erste STOP-Bedingung wird nicht ausgelöst. Der verwendete Vergleichsoperator ermittelt keinen Unterschied zwischen dem intern exakt dargestellten DECIMAL-Wert und dem komprimiert gespeicherten DOUBLE-Wert.

Bei der Rundung auf vier Stellen wird der DECIMAL-Wert auf 0,512 AUFGERUNDET, damit sich beim MidPoint eine gerade Ziffer am Ende des gerundeten Wertes ergibt (2).

Der DOUBLE-Wert wird auf 0,511 ABGERUNDET. Entgegen dem Anschein wird nicht das MidPointRounding-Verfahren durchgeführt. Der Grund findet sich in der Variable 'str'. Der durch die ToString-Methode gelieferte Zahlenstring lautet "0,51149999999999995". Diese Ziffernfolge entspricht der 'zurückgerechneten' internen Darstellung von 0,5115. Numerisch liegt sie geringfügig unterhalb des MidPoint.

Die Math.Round-Funktion berücksichtigt offenbar NICHT die begrenzte Darstellungsgenauigkeit von Double-Werten (15 Stellen), sondern beurteilt das Vorliegen des 'Midpoint' über alle (max. 18) Stellen. Dies gilt auch für das andere der angebotenen MidPointRounding-Verfahren ('AwayFromZero').

Beim Runden von Datenreihen, bei denen der 'MidPoint' häufig auftritt, sollte man den Datentyp DECIMAL bevorzugen. Wenn man beim Runden DOUBLE-Werte mit der Konvertierungsfunktion CDec in DECIMAL umwandelt, wird die DECIMAL-Überladung von Math.Round verwendet. Auch dadurch läßt sich das MidPoint-Problem beheben. Die CDec-Funktion beachtet bei der Konvertierung von Double-Werten nicht alle Stellen.

Um den Sachverhalt zu verdeutlichen, hier ein (speziell konstruiertes) Beispiel:
Es wird eine Datenreihe aus 12 Werten simuliert. Jeder Wert wird als DECIMAL und DOUBLE gerundet und daraus jeweils die Summe berechnet. Die beiden Ergebnisse unterscheiden sich bereits in der zweiten Nachkommastelle.

Dim dec5, sum_dec4 As Decimal
Dim dbl5, sum_dbl4, sum_dbl4b As Double
 
sum_dec4 = 0D : sum_dbl4 = 0.0# : sum_dbl4b = 0.0#
 
For i As Integer = 501 To 512
  dec5 = CDec(i / 1000 + 0.0005)
  dbl5 = CDbl(dec5)
  If dec5 <> dbl5 Then Stop
 
  sum_dec4 += Math.Round(dec5, 3)
  sum_dbl4 += Math.Round(dbl5, 3)
 
  ' Verwendung der DECIMAL-Überladung von Math.Round
  sum_dbl4b += Math.Round(CDec(dbl5), 3)
Next i

Aufgrund des eingesetzten 'unverzerrten' MidPoint-Rundungsverfahrens entspricht die Summe der gerundeten DECIMAL-Werte bei diesem Beispiel exakt der Summe der ungerundeten DECIMAL-Werte. Bei den DOUBLE-Werten tritt das MidpointRounding nicht ein, so dass stets abgerundet wird (---> Akkumulation der Rundungsfehler). Der Effekt tritt auch auf, wenn die Werte aus einer Datei oder Datenbank gelesen und Variablen des Typs DOUBLE zugewiesen werden.

Das Phänomen ist tückisch. Es tritt nämlich nur in eng umgrenzten Wertebereichen auf und kann beim Testen von Programmen leicht übersehen werden.

Untersucht man z.B. Werte mit 4 Nachkommastellen, die im Bereich 0 < x < 10 liegen, findet man beim Runden auf drei Stellen diese für die MidPoint-Ermittlung kritischen Intervalle:

0,501 <--> 0,512
2,001 <--> 2,048
4,001 <--> 4,094
8,001 <--> 8,188

Hinweis für VB6-Anwender bzw Umsteiger:
Auch in VB6 arbeitet die Math.ROUND-Funktion wie oben dargestellt.
Die erste STOP-Bedingung wird nicht ausgelöst, nur die zweite.

Dim dec5, dec4 As Variant
Dim dbl5, dbl4 As Double
 
dec5 = CDec(0.5115)
dbl5 = 0.5115
 
If dbl5 <> dec5 Then Stop
 
dec4 = Math.Round(dec5, 3)
dbl4 = Math.Round(dbl5, 3)
 
If dbl4 <> dec4 Then Stop

Dieser Tipp wurde bereits 4.842 mal aufgerufen.

Voriger Tipp   |   Zufälliger Tipp   |   Nächster Tipp

Über diesen Tipp im Forum diskutieren
Haben Sie Fragen oder Anregungen zu diesem Tipp, können Sie gerne mit anderen darüber in unserem Forum diskutieren.

Neue Diskussion eröffnen

nach obenzurück


Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Tipps & Tricks finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6

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.
 
   

Druckansicht Druckansicht Copyright ©2000-2014 vb@rchiv Dieter Otter
Alle Rechte vorbehalten.

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.

Diese Seiten wurden optimiert für eine Bildschirmauflösung von mind. 1280x1024 Pixel