Moin zusammen,
ich programmiere seit ein paar Jahren ein kleines Lazarus-Tool mit einer SQLite-Datenbank, um meine Sammlung von 2-Euro-Gedenkmünzen zu verwalten (Land, Jahr, Zustand, Preis). Eigentlich nichts Kompliziertes, am Anfang einfach ein TSQLQuery, das mit einem TDBGrid verbunden ist, dazu ganz normale Felder.
Letztes Wochenende wollte ich eine Liste mit aktuellen Kursen importieren, um meine Einträge zu aktualisieren, und dann wurden die Preise im Grid nach der zweiten Nachkommastelle plötzlich völlig absurd angezeigt. Eine Münze für 12,50 wurde zum Beispiel als 12,4999999 angezeigt oder je nach Zeile irgendwie falsch gerundet.
Ich habe zwei Abende gebraucht, um herauszufinden, dass das Feld irgendwo in der Importprozedur als Double statt als Currency gecastet wurde und SQLite das Ganze seinerseits am Ende als Text gespeichert hat. Dadurch gab es bei jedem Auslesen eine doppelte und ziemlich unsaubere Konvertierung. Eigentlich ein Klassiker, aber es hat ewig gedauert, den Fehler einzugrenzen, weil er nur bei bestimmten Werten auftrat und nicht bei allen.
Franchement, eine Zeit lang dachte ich sogar, dass das Problem direkt vom Component kommt. Ausgerechnet bei einer seltenen 2-Euro-Münze aus Monaco ist mir der Rundungsfehler aufgefallen, als ich meine Werte mit dem **Wert seltener 2-Euro-Münzen** in einem Online-Katalog verglichen habe. Da ist mir die Abweichung aufgefallen und ich konnte den Fehler bis zu diesem fehlerhaften Cast zurückverfolgen.
Danach habe ich noch einen Vergleich Zeile für Zeile gemacht, um sicherzugehen, dass es wirklich am Import lag und nicht noch irgendwo anders etwas schiefgelaufen ist. Jetzt sind überall Currency-Felder drin und im Importcode versteckt sich kein Double mehr. Seitdem läuft alles problemlos.
Hatte jemand von euch schon mal so ein Problem mit SQLite und numerischen Datentypen? Vielleicht habe ich auf der Seite des Components noch irgendeine Einstellung übersehen. Kann natürlich auch einfach sein, dass ich das Ganze am Anfang falsch konfiguriert hatte.
Gruß
Lazarus/SQLite: Rundungsfehler bei einer Münze und Wert seltener 2-Euro-Münzen
- Zvoni
- Beiträge: 763
- Registriert: Fr 5. Jul 2024, 08:26
- OS, Lazarus, FPC: Windoof 10 Pro (Laz/FPC fixes)
- CPU-Target: 64Bit
- Wohnort: BW
Re: Lazarus/SQLite: Rundungsfehler bei einer Münze und Wert seltener 2-Euro-Münzen
Ich glaube mich daran zu erinnern, dass es ein Rundungsthema zwischen Single und Double gibt.
Desweiteren kommt es darauf an, welchen tatsächlichen Datentyp du für die Spalte in deiner Tabelle hast.
Unabhängig davon: Wenn, du, wie in deinem Fall, Preise/Geldwerte in der DB speichern willst, würde ich Integer nehmen, und Preise/Werte als "Cent" speichern.
Bsp: "12,47 €" werden in der Datenbank "1247" (cent).
Beim speichern mal 100 (inkl. runden), beim abholen geteilt durch 100
Generell würde ich solche Sachen (z.B. Runden) immer von der DB machen lassen
Stichwort: Computed/Generated columns
INSERT/UPDATE nur auf (Spalte 2 und) Spalte 3, hier den "rohen" Double-Wert liefern.
und Spalte 4 und 5 wird automagisch von der DB selbst berechnet, und zur Verfügung gestellt (Können ganz normal per SELECT abgeholt werden).
Bei DB-Gebundenen Controls resp. wenn du mit "Dataset.Insert/Append/Edit" arbeitest, muss besondere Sorgfalt für InsertSQL und UpdateSQL angewendet werden.
Die kann man definitiv nicht von der Komponente "erraten" lassen, sonst knallts
Desweiteren kommt es darauf an, welchen tatsächlichen Datentyp du für die Spalte in deiner Tabelle hast.
Unabhängig davon: Wenn, du, wie in deinem Fall, Preise/Geldwerte in der DB speichern willst, würde ich Integer nehmen, und Preise/Werte als "Cent" speichern.
Bsp: "12,47 €" werden in der Datenbank "1247" (cent).
Beim speichern mal 100 (inkl. runden), beim abholen geteilt durch 100
Generell würde ich solche Sachen (z.B. Runden) immer von der DB machen lassen
Stichwort: Computed/Generated columns
Code: Alles auswählen
CREATE TABLE "tbl_coins" (
"ID" INTEGER,
"Bezeichnung" TEXT,
"PreisRaw" REAL,
"PreisCent" INTEGER GENERATED ALWAYS AS (ROUND("PreisRaw"*100)) Stored,
"PreisReal" REAL GENERATED ALWAYS AS ("PreisCent"/100.0) virtual,
PRIMARY KEY("ID")
);und Spalte 4 und 5 wird automagisch von der DB selbst berechnet, und zur Verfügung gestellt (Können ganz normal per SELECT abgeholt werden).
Bei DB-Gebundenen Controls resp. wenn du mit "Dataset.Insert/Append/Edit" arbeitest, muss besondere Sorgfalt für InsertSQL und UpdateSQL angewendet werden.
Die kann man definitiv nicht von der Komponente "erraten" lassen, sonst knallts
Ein System sie alle zu knechten, ein Code sie alle zu finden,
Eine IDE sie ins Dunkel zu treiben, und an das Framework ewig zu binden,
Im Lande Redmond, wo die Windows drohn.
Eine IDE sie ins Dunkel zu treiben, und an das Framework ewig zu binden,
Im Lande Redmond, wo die Windows drohn.
- af0815
- Lazarusforum e. V.
- Beiträge: 7455
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Burgenland
- Kontaktdaten:
Re: Lazarus/SQLite: Rundungsfehler bei einer Münze und Wert seltener 2-Euro-Münzen
Grundlagen zur Ungenauigkeit (Klugscheissermodus):
Hier im Forum
viewtopic.php?t=9410
viewtopic.php?t=17088
Extern über C, Probleme aber gleich
https://learn.microsoft.com/de-de/cpp/b ... w=msvc-170
https://labex.io/de/tutorials/c-how-to- ... ems-419921
Hier im Forum
viewtopic.php?t=9410
viewtopic.php?t=17088
Extern über C, Probleme aber gleich
https://learn.microsoft.com/de-de/cpp/b ... w=msvc-170
https://labex.io/de/tutorials/c-how-to- ... ems-419921
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
Re: Lazarus/SQLite: Rundungsfehler bei einer Münze und Wert seltener 2-Euro-Münzen
Da ist aber auch nicht garantiert, dass z.B. 1249/100 exakt als 12.49 ausgegeben wird, denn 12.49 lässt sich nicht als Kombination von Zweierpotenzen darstellen:
Code: Alles auswählen
program Project1;
begin
WriteLn(1249/100);
ReadLn;
end.
// Ausgabe:
1.24899999999999999998E+0001
Code: Alles auswählen
for i := 0 to Dataset1.FieldCount-1 do
begin
if Dataset1.Fields[i] is TFloatField then
TFloatField(Dataset1.Fields[i]).DisplayFormat := '0.00'; // auf zwei Stellen nach dem Komma runden
end;