Kaufmännisches Runden unter VB 6.0

  • VB6

Es gibt 18 Antworten in diesem Thema. Der letzte Beitrag () ist von Marcus Gräfe.

    Kaufmännisches Runden unter VB 6.0

    Die Round-Funktion von VB6 rundet leider nicht kaufmännisch. Daher benutze ich seit Jahren diese Funktion von aboutvb.de/khw/artikel/khwround.htm :

    Visual Basic-Quellcode

    1. Public Function Round(ByVal Number As Double, ByVal Digits As Integer) As Double
    2. Round = Int(Number * 10 ^ Digits + 0.5) / 10 ^ Digits
    3. End Function

    Ich dachte eigentlich, dass diese funktioniert, heute stelle ich aber fest, dass dem nicht so ist (oder verlassen mich gerade meine mathematischen Kenntnisse?).

    MsgBox Round(34.55, 1) ergibt nämlich 34,5 anstatt 34,6. Es wird aber definitiv meine eigene Round-Funktion aufgerufen.

    Was ist falsch?
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Ein (hässlicher) Workaround für Double wäre:

    Visual Basic-Quellcode

    1. Public Function Round(ByVal Number As Double, ByVal Digits As Integer) As Double
    2. Round = Int(Number * 10 ^ Digits + 0.50000000000001) / 10 ^ Digits
    3. End Function

    Du kannst auch (ein klein wenig eleganter) die Anzahl der notwendigen Korrekturstellen aus Digits berechnen.

    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --

    petaod schrieb:

    Der innere Ausdruck ergibt wohl aufgrund Double-Rundungsfehlern minimal weniger als 346

    Achso. Ich dachte, diese Funktion würde alle VB-Eigenarten berücksichtigen und wirklich immer korrekt kaufmännisch runden. Derartige Rundungsfehler hatte ich schon öfters. Beispielsweise war sowas wie 4/2 nicht exakt 2. Ich habe mein Programm vor Jahren übrigens von alles Single auf alles Double umgestellt. War das vielleicht ein Fehler?
    Edit: Habe dazu auch soeben einen Artikel gefunden: msdn.microsoft.com/de-de/library/bb978923.aspx

    Artentus schrieb:

    Verwende nicht double wenn es um Geld geht

    Es geht um mathematische Berechnungen von Lichtreflexionen.

    Artentus schrieb:

    Decimal-Datentyp

    Sagt mir spontan leider nichts.

    Artentus schrieb:

    bleibt leider nur long

    Aber Long ist ein ganzzahliger Typ, ich benötige aber Nachkommastellen.
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Ich kenn mich mit der Berechnungen von Lichtreflexionen nicht aus, deswegen kann ich nur spekulieren.
    Aber dein Problem scheint von der selben Natur zu sein wie Rechnen mit Geld. Und da kann long ohne Probleme genutzt werden, man betrachte den long einfach als Festkommazahl. Addition und Subtraktion funktionieren sofort, für Multiplikation und Division sind nur noch zusätzliche Bitshifts nötig. Dadurch entstehen (innerhalb der x festgelegten Nachkommastellen) keine Rundungsfehler.
    In .Net löst der Decimal-Datentyp das Problem besser, indem intern eine 96Bit-Ganzzahl verwendet wird sowie die Anzahl der verwendeten Nachkommastellen zwischen 0 und 28 variieren kann, das Prinzip ist aber das selbe.
    @petaod Ich habe nun deinen Workaround eingebaut. Zumindest bei 34,55 rechnet er nun richtig. ;)

    Danke!

    Allerdings musste ich eine "0" entfernen, also "0.5000000000001".

    @Artentus Das Programm enthält tausende Variablen und Berechnungen. Eine Umstellung ist daher nicht möglich.

    Vorerst habe ich ja nun eine Lösung. Falls jemand noch eine bessere kennt, immer her damit. ;)
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    @Marcus Gräfe Gibt es eigentlich einen guten Grund, warum du nicht einfach mal zu .NET wechselst? Insgesamt, nicht bei bestehenden Projekten.

    Grüße
    "Life isn't about winning the race. Life is about finishing the race and how many people we can help finish the race." ~Marc Mero

    Nun bin ich also auch soweit: Keine VB-Fragen per PM! Es gibt hier ein Forum, verdammt!
    Ich habe quasi nur bestehende Projekte. Habe schon ewig nichts neues mehr angefangen.

    Übrigens hat VB6 auch den Decimal-Datentyp, aber indirekt:

    MSDN schrieb:

    Variablen des Datentyps
    Decimal werden als 96-Bit-Ganzzahlen (12 Bytes) ohne Vorzeichen mit
    einer variablen Potenz zur Basis 10 gespeichert. Die Potenz zur Basis 10 wird
    als Skalierungsfaktor verwendet und bestimmt die Anzahl der Nachkommastellen,
    die in einem Bereich von 0 bis 28 liegen kann. Beim Skalierungsfaktor von 0
    (keine Nachkommastellen) liegt der größtmögliche Wert bei
    +/-79.228.162.514.264.337.593.543.950.335. Bei 28 Nachkommastellen liegt der
    größte Wert bei +/-7,9228162514264337593543950335 und der kleinste Wert, der
    ungleich Null ist, bei +/-0,0000000000000000000000000001.

    Anmerkung Der Datentyp Decimal kann nur
    mit einem Wert vom Typ
    Variant benutzt werden, d.h., Sie können
    keine Variable als Decimal deklarieren. Mit der CDec-Funktion
    können Sie jedoch einen Wert vom Typ Variant erstellen, dessen Untertyp
    Decimal ist.

    Wusste ich bisher nicht.
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Das wäre vermutlich korrekt, da ab 0.5 aufgerundet wird, und bei negativen Zahlen aufrunden funktioniert andersrum. Will mich da aber nicht festlegen.
    "Life isn't about winning the race. Life is about finishing the race and how many people we can help finish the race." ~Marc Mero

    Nun bin ich also auch soweit: Keine VB-Fragen per PM! Es gibt hier ein Forum, verdammt!
    Laut Wikipedia allerdings nicht:
    de.wikipedia.org/wiki/Rundung#Kaufm.C3.A4nnisches_Runden

    Negative Zahlen werden nach ihrem Betrag gerundet

    Betrag=Absolutwert
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Hallo Marcus,
    dein zuerst geposteter Code funktioniert übrigens auch nicht
    für negative Zahlen, weil die Int-Funktion negative Zahlen
    mit Nachkommaanteil grundsätzlich abrundet. Z.B. wird
    Int(-1.1) zu -2. Das ist kein Fehler der Programmiersprache,
    sondern gewollt, um der Definition der Integer-Funktion
    gerecht zu werden. Das der Code bei einigen negativen Zahlen
    funktioniert ist dann eher Zufall. Das Verhalten der Int-
    Funktion wird durch die Addition von 0.5 zum Teil wieder
    kompensiert. Um bei negativen Zahlen den Nachkommanteil
    abzuschneiden, gibt es die Fix-Funktion. Wie dem auch sei,
    die Lösung mit Abs und Sgn ist schon der richtige Weg. Nur
    das mit der Addition von 0.5000000000001 ist ein bißchen
    fragwürdig. Wenn eine Rundung mit einer Zahl nicht funktioniert
    und man passt dann die mathematisch korrekte Konstante (0,5) so
    an, daß es geht, kann es sein, daß danach eine andere Zahl nicht
    mehr richtig gerundet wird. Mein Vorschlag sieht daher so aus:

    Visual Basic-Quellcode

    1. Public Function Round(ByVal Number As Double, ByVal Digits As Integer) As Double
    2. Dim TenPower As Double
    3. TenPower = 10 ^ Digits
    4. Round = Sgn(Number) * Int(CDec(0.5) + Abs(Number) * TenPower) / TenPower
    5. End Function

    Erklärung: Durch die Umwandlung von 0,5 in den Dezimaltyp wird innerhalb
    der Klammer eine Rechnung mit höherer Genauigkeit erzwungen. Dadurch
    werden Rechenungenauigkeiten beseitigt.
    Gruss,

    Neptun

    Neptun schrieb:

    passt dann die mathematisch korrekte Konstante (0,5) so
    an, daß es geht, kann es sein, daß danach eine andere Zahl nicht
    mehr richtig gerundet wird
    Die Zahl wird höchstens an der kritischen Stelle des Double-Problems minimal positiver.
    Das wird durch das Int-Abschneiden auf jeden Fall ausgeglichen.
    Fehler kann es nur dann geben, wenn die Anzahl der Digits in den kritischen Bereich von mehr als 13 geht.

    VB6 und VBA unterstützen den Datentyp Decimal nicht direkt.
    CDec funktioniert zwar, aber nur für den Datentyp Variant.
    Dein Verfahren würde also (ausser in VBS) ebenfalls funktionieren.

    Aber kannst du mir erklären, weshalb du die Ganzzahl TenPower als Double deklarierst.
    Da handelst du dir doch schon wieder potenzielle Double-Problematik ein.
    Wenn, dann würde ich hier Long nehmen.
    Oder allenfalls Decimal über den Umweg Variant/CDec.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --
    @petaod Siehst du bei der von @Neptun geposteten Funktion gravierende Vorteile gegenüber deinem Code, so dass ich seine Version nehmen sollte?
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Meine Routine hat den Nachteil, dass sie ab der 13.Nachkommastelle nicht mehr stimmt, Wenn du mit dieser Einschränkung leben kannst, dann kannst du sie lassen.

    Wenn du bis zur Grenze von Double gehen willst, kannst du die andere nehmen, aber dann würde ich sie abändern.

    Visual Basic-Quellcode

    1. ​Public Function Round(ByVal Number As Double, ByVal Digits As Integer) As Double
    2. Dim TenPower As Variant
    3. TenPower = CDec(10 ^ Digits)
    4. Round = CDbl(Sgn(Number) * Int(CDec(0.5) + Abs(Number) * TenPower) / TenPower)
    5. End Function
    Müsste meiner Meinung nach funktionieren (nicht getestet).
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --