Entwickler-Ecke
Datenbanken - Round bringt kein Ergebnis
HelgeLange - So 10.06.12 20:12
Titel: Round bringt kein Ergebnis
Hallo,
ich habe ein seltsames Problem zu lösen :
Unsere Kunden müssen Finanz-Berichte machen und alles auf 2 Kommastellen maximal gerundet. Nun soll ich die vorhandene Tabelle dazu konvertieren, da es zu Unstimmingkeiten kommt, wenn man 10 kommastellen hat. Dazu gibt es ja in Firebird die Funktion Round, wo man angeben kann, auf wieviele Kommastellen man runden möchte.
Ich hab es auf 2 verschiedenen Wegen probiert :
1. eine Stored procedure, welche durch alle Einträge läuft, rundet und dann ein update macht
2. Trigger, welche auf INSERT und UPDATE reagiert und die neuen Werte rundet.
Keins von beiden bringt ein Ergebnis scheinbar. 5.61 zeigt immernoch sachen wie 5.609999997 zum Bsp. an.
Hier der Code für die Stored Procedure
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:
| create or alter procedure FIX_GL_DECIMALS as declare variable E integer; declare variable S integer; declare variable TIPO char(2); declare variable NUMERO integer; declare variable CONTEO integer; declare variable DEBIT numeric(15,2); declare variable CREDIT numeric(15,2); declare variable BASE numeric(15,2); declare variable AJUSTE_PESO integer; declare variable ID_N bigint; declare variable RDEBIT numeric(15,2); declare variable RCREDIT numeric(15,2); declare variable RBASE numeric(15,2); begin FOR SELECT E, S, Tipo, BATCH, Conteo, Debit, Credit, Base FROM GL INTO :E, :S, :Tipo, :Numero, :Conteo, :Debit, :Credit, :Base DO begin RDebit = Round(:Debit * 100, 2) / 100.0; RCredit = Round(:Credit * 100, 2) / 100.0; RBase = Round(:Base * 100, 2) / 100.0; UPDATE GL SET Debit=:RDebit, Credit=:RCredit, Base=:RBase WHERE Conteo=:Conteo AND E=:E AND S=:S AND Tipo=:Tipo AND Batch=:Numero; END SELECT ACCT_AjustePeso, ID_N FROM SYS WHERE E=1 INTO :ajuste_peso, :ID_N; DELETE FROM GL WHERE ACCT=:ajuste_peso; FOR SELECT DISTINCT E, S, tipo, Batch FROM GL INTO :E, :S, :Tipo, :Numero DO BEGIN SELECT Sum(Debit), Sum(Credit) FROM GL WHERE E=:E AND S=:S AND Tipo=:Tipo AND Batch=:Numero INTO :Debit, :Credit; IF ((:debit - :Credit <> 0) AND ((:Debit - :Credit) < 0.5)) THEN BEGIN if (:Debit > :Credit) then BEGIN INSERT INTO GL (ID_N, ACCT, E, S, Tipo, Batch, Base, Debit, Credit, Descripcion) VALUES (:ID_N, :Ajuste_Peso, :E, :S, :Tipo, :Numero, 0, 0, (:Debit-:Credit), 'Ajuste al peso'); END ELSE BEGIN INSERT INTO GL (ID_N, ACCT, E, S, Tipo, Batch, Base, Debit, Credit, Descripcion) VALUES (:ID_N, :Ajuste_Peso, :E, :S, :Tipo, :Numero, 0, (:Debit-:Credit), 0, 'Ajuste al peso'); END END END end^ |
im Trigger versuchte ich es (wie im Internet als Idee gefunden) mit Integer-Runden, hier der code :
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| CREATE OR ALTER TRIGGER GL_BIU0 FOR GL ACTIVE BEFORE INSERT OR UPDATE POSITION 0 AS begin New.Credit = Round(New.Credit * 100) / 100.0; New.Debit = Round(New.Debit * 100) / 100.0; New.Base = Round(New.Base * 100) / 100.0; end ^ |
Hat einer eine Idee, warum er mir das nicht korrekt rundet ?
Danke schonmal,
Helge
Lemmy - So 10.06.12 21:43
hi,
welche Firebirdversion und welcher Datenbankdialekt?
Grüße
HelgeLange - So 10.06.12 22:30
Server Version: WI-V2.5.0.26074 Firebird 2.5
Dialect 3
Danke :)
Tranx - Mo 11.06.12 04:01
Das Round betrifft ja nur den ersten Teil der Rechnung (Round(100 * x)). Dann teilst Du wieder durch eine Realzahl (100.0) somit ist das Ergebnis eine Realzahl. Und die von Dir genannten "Abweichungen" sind typische "Fehler" von endlichen Realzahlen. Weclchen Typ von Realzahlen verwendest Du zur Darstellung der Datenbankinhalte: Real, Single, Double oder Extended. Je größer die Speichergröße eines Realtypen ist, um so genauer ist das Ergebnis. Letztlich ist das, was Du darstellst, kein Fehler, sondern es geht z.B. bei Single nicht besser. Denn Realzahlen sind nun mal Zahlen des Typs: x.xxxx E yy. Bei der Verwendung von Integer-Zahlentypen würde dieser Fehler nicht auftreten. Dann allerdings müsstest Du die Werte mit 100 multiplizieren und abspeichern oder den Realtypen Currency nehmen.
Lemmy - Mo 11.06.12 07:27
Tranx hat folgendes geschrieben : |
| Das Round betrifft ja nur den ersten Teil der Rechnung (Round(100 * x)). Dann teilst Du wieder durch eine Realzahl (100.0) somit ist das Ergebnis eine Realzahl. |
nicht unbedingt - jetzt kommts drauf an wie die Tabellenfelder definiert sind - steht leider nicht da. Wenn es aber wie bei der SP sich um numeric(15,2) handelt, dann werden die Daten intern (IMHO - finde die Tabelle wo das steht gerade nicht) als Int64 gespeichert.
Tranx - Mo 11.06.12 08:25
Die Speicherung erfolgt möglicherweise so, aber vielleicht auch nicht. Wo die ungenaue Anzeige auftritt, ist auch nicht ganz ersichtlich (Im Programm oder in der Datenbank). In der Datenbank kann es m.E. nicht sein, wenn die Speicherung als Integer erfolgt, da diese Art der Ungenauigkeit auf einen Realtyp (möglicherwiese Single) schließen lässt. Sollte es doch in der Datenbank sein, so ist die Definition des Datenbankfeldes möglichweise ein Realtyp.
Was ich allerdings nicht verstehe ist, wieso eine derartige Ungenauigkeit Probleme bereitet. Die Genauigkeit ist doch völlig ausßreichend für eine 2-Stellige Nachkommaanzeige. Oder sehe ich das verkehrt?
Lemmy - Mo 11.06.12 08:42
Tranx hat folgendes geschrieben : |
Die Speicherung erfolgt möglicherweise so, aber vielleicht auch nicht. Wo die ungenaue Anzeige auftritt, ist auch nicht ganz ersichtlich (Im Programm oder in der Datenbank). In der Datenbank kann es m.E. nicht sein, wenn die Speicherung als Integer erfolgt, da diese Art der Ungenauigkeit auf einen Realtyp (möglicherwiese Single) schließen lässt. Sollte es doch in der Datenbank sein, so ist die Definition des Datenbankfeldes möglichweise ein Realtyp.
|
völlig richtig.. nur ist das alles gerade ein raten und Glaskugel schauen so lange gewisse Grundlagen nicht geklärt sind :-)
Tranx hat folgendes geschrieben : |
Was ich allerdings nicht verstehe ist, wieso eine derartige Ungenauigkeit Probleme bereitet. Die Genauigkeit ist doch völlig ausßreichend für eine 2-Stellige Nachkommaanzeige. Oder sehe ich das verkehrt? |
z.B: Berechnungen in einer StoredProcedure (Summierungsfehler aufgrund Float-Ungenauigkeiten). Bei einer Rechnung ist schon eine Differenz von einem Cent ein Problem.
Grüße
HelgeLange - Mo 11.06.12 13:39
tschuldigung für die späte Antwort, aber die Zeitverschiebung von 7 Stunden macht doch schon was her. :D
Also alle Felder sind als Numeric(15,2) definiert. Einen Currency Datentypen gibt es nicht in Firebird (IMHO).
Um Euch etwas den Hintergrund zu erklären :
Die Tabelle GL stellt eine Schnittstelle der Buchhaltung für die Transaktionen der einzelnen Module dar. Das Heisst, alle Dokumententypen (Rechnung, Wareneingang, Geldeingäng, Rechnung an Dritte, Rechnung von Dritten etc.) schreiben ihre einzelnen Posten dort rein und ausserdem die verschiedenen Steuern, die abgeführt werden müssen. Zu jedem Eintrag gibt es ein Konto (Feld ACCT), einen Wert Base (die Berechnungsgrundlage) und Debit/Credit (was sagt, ob es einkommendes oder ausgehendes Geld ist).
Ok, warum muss es genau sein : Das Finanzamt hier akzeptiert keine Buchhaltung, wo es zu Unterschieden zwischen Debit und Credit kommt. Bei einem Document muss sich das ganze auf 0 rechnen (also Debit-Credit=0). Falls es durch Rundungsfehler auf einen Unterschied kommt, dann wird der Unterschied auf ein besonderes Konto gerechnet (Ajuste al Peso), allerdings sollte es kein grosser Unterschied sein, normalerweise beträgt der 0.01 Peso max, wir akzeptieren bis 0.49 pesos. Zur Erklärung, 1800 Pesos = 1 US$, 2300 Pesos = 1 euro.
Wenn es keine andere Möglichkeit gibt in Firebird, das auf genau 2 Kommastellen zu runden (was seltsam ist, ich kann das ja in Delphi runden und perfekt reinschreiben, warum ist FB nicht in der Lage, das zu rechnen und korrekt zu schreiben), dann muss ich eben alle Daten rauslesen und neu reinschreiben, nur hätte ich halt gern eine Lösung, die nicht vom nutzer 1xmal im Monat angestossen werden muss (wir haben rund 1000 Formulare und es wäre eine Aufgabe von Wochen, alle Stellen zu finden, die in diese Tabelle über ihre proxy-Tabelle schreiben und dann dort zu runden).
Helge
Lemmy - Mo 11.06.12 15:11
HelgeLange hat folgendes geschrieben : |
Wenn es keine andere Möglichkeit gibt in Firebird, das auf genau 2 Kommastellen zu runden (was seltsam ist, ich kann das ja in Delphi runden und perfekt reinschreiben, warum ist FB nicht in der Lage, das zu rechnen und korrekt zu schreiben) |
Wie rundest Du in Delphi die Daten? Wie schreibst Du die Daten in die DB zurück? Verwendest Du da konsequent den Datentyp Currency bzw. entsprechende Currency-Funktionen? Bitte beachte, dass z.B. ein
den Funktionsparameter als Float transportiert, d.h. du verlierst beim Eintritt in die Funktion den Currency-Vorteil!! Und damit kann ggf. auch ein fehlerhaftes Ergebnis zurück kommen!
Dann teste bitte was passiert, wenn Du mit einer DB-Konsole (FlameRobin, IBExpert, ISQL,....) mit der Tabelle arbeitest und dort in den Numeric(15,2) fehlerhafte Werte (d.h. mit mehr Nachkommastellen) rein schreibst. Werden die bei einem Select dann korrekt (d.h. mit 2 Nachkommastellen) zurück geliefert? Wenn ich das mache (Feldtyp Numeric(9,2)) dann wird korrekt nach der 2. Stelle gerundet.
Hast Du die DB neu erstellt oder ist das ggf. ein Altprojekt (Firebird 1.0 oder sogar noch Interbase)? Wenn ja, wurde dann der Versionsübergang immer korrekt gemacht, d.h. mind. ein Backup und Restore durchgeführt?
Grüße
Tranx - Mo 11.06.12 15:19
Eine Anregung hätte ich auch.
Wenn es denn keine Möglichkeit gibt, wirklich 2-Stellige Nachkomma-Werte in die Datenbank zu speichern, und es den Typ Currency nicht gibt, der im Übrigen auch vierstellig nach dem Komma ist, warum nicht dann konsequenterweise als Longint. Dann rechnest Du eben in Centavos oder wie die Teile eines Pesos heißen. Dann hast Du keine Rundungsfehler. Musst nur bei der Ausgabe auf dem Bildschirm den Datenbankwert / 100 teilen (die Ausgabe dann mit FormatFloat ausgeben) bzw. beim Eintrag in die Datenbank den Peso-Eintrag im Programm * 100 nehmen.
Zugegeben, toll sieht das nicht aus, besonders wenn Du die Datenbank direkt und nicht über ein Eingabeprogramm bearbeitest. Aber es ist genau genug.
Wäre eine Anregung. Nichts anderes macht - hier automatisch - der Variablentyp Currency - soweeit ich das in der Hilfe lese.
HelgeLange - Mo 11.06.12 15:53
Hallo wieder und danke für die Ideen.
Erstmal zur Integer (oder LargeInt)-Behandlung der Currency-Werte :
1.) Zu kompliziert, weil man muss ja dran denken, dass man daraus Rechnungen druckt und auch auf dem Bildschirm Werte anzeigen muss. Das wäre eine ständige Umrechnung entweder im Programm oder im SQL-Befehl.
2.) Haben wir so rund 1000 Formulare im Programm, da müsste man sehr viel ändern, damit das funktioniert.
Die Stored Procedure sowie auch der Trigger versuchen ja, den Wert
korrekt zu runden [
http://www.firebirdsql.org/refdocs/langrefupd25-intfunc-round.html] und dann den Wert mittels UPDATE (im Fall der SP) oder durch Zuweisung des gerundeten Ergebnisses (im Fall des Triggers) in die DB zu schreiben.
Da alle Werte als Numeric(15,2) erstellt wurden, zeigt IBExpert zum Bsp. auch entsprechend gerundet dies an, aber wenn man dann das Feld editieren will, dann erscheinen wieder die ganzen Nachkommastellen. Ich finde es mehr als seltsam, dass Firebird es nicht schafft, den gerundeten Wert in die DB zu schreiben, denn es ist definitiv ein Unterschied, ob ich 5.6999999 oder 5.7 als Wert habe.
Falls keiner mehr eine Idee hat, werde ich das ganze wohl in Delphi auslesenm runden und gerundet wieder in die DB schreiben, da das zumindest zu funktionieren scheint.
Lemmy - Mo 11.06.12 16:33
HelgeLange hat folgendes geschrieben : |
Da alle Werte als Numeric(15,2) erstellt wurden, zeigt IBExpert zum Bsp. auch entsprechend gerundet dies an, aber wenn man dann das Feld editieren will, dann erscheinen wieder die ganzen Nachkommastellen. Ich finde es mehr als seltsam, dass Firebird es nicht schafft, den gerundeten Wert in die DB zu schreiben, denn es ist definitiv ein Unterschied, ob ich 5.6999999 oder 5.7 als Wert habe.
|
schon mal dran gedacht, dass das ein IBExpert "Problem" sein könnte weil die Werte als Float (Extended) in IBExpert behandelt werden und nicht als Currency? Ist bei mir übrigens genauso: Wenn ich von einer SP mehrere Numeric zurück bekomme werden die ebenfalls mit Nachkommastellen beim editieren dargestellt. Der große Unterschied zu einem Float ist der: WEnn ich das Ergebnis mit 10.000 oder 100.000 multipliziere, passen die Zahlen immer noch, d.h. da wird korrekt gerechnet, bei einem Float schwappt irgend wann der Darstellungsfehler ins Ergebnis rein.
SQL-Anweisung
1:
| Select cast(100/1.19 AS Numeric(7,2))*10000 AS Result, cast(100/1.19 AS Float)*10000 AS Result2 from rdb$Database; |
Firebird 2.1
Oliver Maas - Mo 11.06.12 16:41
Hm, irgendwie wundert mich dieser Unterschied nun weniger, denn es ist ja so, dass eine Fliesskommazahl (ob nun Single, Double oder Extended) immer letztendlich mit 2er Potenzen arbeiten muss.
Extended ist natürlich "genauer" als Single, aber auch da gibt es diese berühmten "Abweichungen", die einfach darauf beruhen, dass der PC ja letztendlich jede Zahl als Folge von 0 und 1 darstellen muss (die jeweils unterschiedliche 2 hoch x repräsentieren). Und die "Rundung" der stored procedure ist strenggenommen eine Fliesskommadivision durch 100.0, eben mit den üblichen Abweichungen, die an oben genanntem Grund liegen.
Daher kann man 0,5 und 0,25 exakt darstellen, z.B. mit Double (das sind ja 2 hoch -1 und 2 hoch -2), aber die meisten "Dezimalzahlen" kann man nicht ganz exakt darstellen,
da nur die wenigsten 2 hoch x genau gehorchen. Daher auch 5,6999... usw.
Was nun die Delphi Prozedur angeht: die wird es wohl anders machen als die stored procedure, aber ich weiß ja nun nicht genau, wie das jetzt im Code realisiert wurde.
Ich hatte bisher noch keine Anforderung (zum Glück) mit Währungsrechnungen, aber es ist schon klar, dass da keine Abweichungen auftreten dürfen, daher gibt es ja bei vielen Datenbanken Currency als Typ. Könnte man mit Währungen genauso rechnen wie mit einem physikalischen Messwert, bräuchte man den Typ Currency gar nicht. Es ist schon schade, dass Firebird das nicht kann.
Vermutlich würde das das Problem am elegantesten lösen.
Olli
Lemmy - Mo 11.06.12 16:57
Oliver Maas hat folgendes geschrieben : |
Es ist schon schade, dass Firebird das nicht kann.
|
bitte bitte erzählt doch keinen Blödsinn. Firebird stellt mit Numeric(x,y) einen dem Currency entsprechenden Datentyp zur Verfügung. Aber mir wird das hier jetzt langsam echt zu viel mit den Windmühlen...
Grüße und weg....
HelgeLange - Mo 11.06.12 17:07
@Lemmy: Immer langsam, es ist eine Diskussion, keine Grund, so zu reagieren.
Also ich habe langsam wirklich die Vermutung, dass es vielleicht doch ein Fehler von IBExpert ist, da ich jetzt mal einen Wert wir 5.21 in die Datenbank geschrieben habe und IBExpert zeigt im grid den Wert an, aber im Moment des Editierens zeigt er wieder 5.2099999998 an.
Ich glaube nicht, dass Firebird sich so einen Fehler erlaubt oder Erlauben kann, ich sehe auch im Internet keine Diskussionen, die zweifelsohne da sein würde, wäre es das was Firebird macht. Ich schau jetzt mal mit ISQL nach, dem Kommandozeilen tool von firebird.
HelgeLange - Mo 11.06.12 17:17
Es Ist ein Fehler von IBExpert, der zeigt die Daten wirklich falsch an. IN ISQL.exe der Wert wird korrekt dargestellt, wenn auch mit vielen Nullen am Ende :D
Dann sollte Firebird damit jetzt auch korrekt rechnen können.
Danke an alle.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!