Float Variable - Entfernen von Nachkommastellen

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Weizenbaum
Beiträge: 28
Registriert: Mi 24. Aug 2016, 09:00

Float Variable - Entfernen von Nachkommastellen

Beitrag von Weizenbaum »

Hallo,
ich suche nach einer Funktion, welche überflüssige Nachkommastellen entfernt. Leider bin ich mit dem Wiki nicht weitergekommen -> https://www.freepascal.org/docs-html/rt ... /frac.html

Bei folgenden Quelltext wird statt 2,70 EUR -> 2.7000000000000002 EUR eingelesen und wenn ich später einen Abgleich mache bei einer Änderung eines anderen Feldes vom selben Datensatz, wird derzeit von meiner Software ein Änderungsflag gesetzt wegen der 2 ganz hinten, obwohl ja im Grunde da keine Änderung statt fand.

Code: Alles auswählen

        // Frac (123.456):0:3);
        rBetrag:= SQLQueryKonto.FieldByName('BETRAG').AsCurrency;
        // 2.7000000000000002
        //rBetrag:= Frac(rBetrag):0:2);
Das mit dem :0:2 war mein verzweifelter Versuch, den passenden Wert doch noch zu bekommen, aber das bezog sich auf den Parameter WriteLn und nicht auf Frac, sonst hätte ich einfach die Vorkommastelle und Nachkommastelle getrennt extrahiert und dann wieder richtig zusammengefügt.
Wichtig ist mir auch wegen der Masse von Daten, keine zu große Algorithmen hier einzusetzen, wegen der Laufzeit.

Benutzeravatar
gladio
Beiträge: 217
Registriert: Sa 21. Jun 2014, 06:15
OS, Lazarus, FPC: Win10-64 - aktuelle Lazarus/FPC Standard-Edition
CPU-Target: 64Bit
Wohnort: Rügen

Re: Float Variable - Entfernen von Nachkommastellen

Beitrag von gladio »

Nicht die eleganteste Art:

Code: Alles auswählen

rBetrag:=StrToFloat(FormatFloat('0.00', rBetrag));
oder so ohne über Strings:

Code: Alles auswählen

rBetrag:=trunc(rBetrag*100+0.5)/100;

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6200
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: Float Variable - Entfernen von Nachkommastellen

Beitrag von af0815 »

Das hinrunden beim Einlesen ist IMHO immer noch ein Schuss ins Knie.

Ein Float kann eine Zahl nur mit einer Ungenauigkeit speichern, wie bereits das Einlesen gezeigt hat. Deswegen muss man sich das Design der App gut überlegen. Für genaue kaufmänniche Rechnungen gibt es den Datentyp Currency, mit allen seinen Besonderheiten.

Ist eine gewisseS Rundungsproblem ok, so geht Float und man muss nur an den Schnittstellen aufpassen und dort entsprechend vorsorgen und nur dort die Ausgabe runden. Ansonsten sich mit currency befassen oder Currency selbst nachbauen - ist ja genaugenommen ein Int64 mit Kommaverschiebung.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Float Variable - Entfernen von Nachkommastellen

Beitrag von wp_xyz »

Wenn Float-Zahlen nicht wie erwartet sondern nur mit vielen Nachkommastellen angezeigt werden, so ist das im Prinzip unvermeidbar. In der Mathematik liegen reelle Zahlen unendlich dicht, das heißt, man findet bei zwei Zahlen immer eine die dazwischen liegt, egal wie stark man "hineinzoomt". Im Computer geht das nicht, weil für eine Floatzahl nur eine endliche Zahl von Bits zur Verfügung stehen, das heißt man kann nur bis zu einer gewissen Grenze "hineinzoomen", und dann sieht man, dass die Zahlen auf einem Gitter verteilt angeordnet sind. Wenn du also die Zahl 2.7 erwartest, dann ist in diesem Fall eben 2.7000000000002 die Zahl auf demjenigen Gitterpunkt, der der "Wunsch-Zahl" 2.7 am nächsten liegt.

Da kannst du runden wie du willst, denn das Ergebnis der mathematischen Rundung (2.7) wird ja wieder nicht auf einem Gitterpunkt liegen.

Man kann dem Problem oft ausweichen, wenn man zu einem anderen Datentyp geht (double statt single oder extended, oder auch umkehrt), oder eine andere Funktion zur Stringumwandlung wählt.

Bei deinem konkreten Beispiel wundert mich allerdings, dass das Problem überhaupt auftritt. Denn wenn du einen Feldwert als "AsCurrency" abfragst, ist das Ergebnis vom Typ Currency, das ist eigentlich ein mit 10000 multiplizierter Integer. Die Frage wäre in diesem Zusammenhang: Welchen Typ hat rBetrag? Ist das ein Double/Single/Extended? Dann kann das Problem wie beschrieben auftreten. Oder ist es ein Currency? Dann sollte es nicht auftreten.

Mit dem folgenden Code kann ich das nachstellen (Formular mit einem Button und einem TMemDataset):

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  v: Currency;    // ---> korrekt 2.7
//  v: Double;      // ---> "falsch"  2.7000000000000002
//  v: extended;      // ---> korrekt 2.7
begin
  MemDataset1.First;
  v := MemDataset1.Fields[0].AsCurrency;
  ShowMessage(Format('%g', [v]));
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  with MemDataset1 do
  begin
    FieldDefs.Add('Betrag', ftCurrency);
    CreateTable;
    Open;
    AppendRecord([2.7]);
  end;
end; 
Um zwei Float-Zahlen auf Gleichheit zu prüfen, solltest du nie den '=' Operator verwenden, sondern die Funktion SameValue aus der Unit Math, die einen Toleranz-Bereich um jede der beiden Zahlen zulässt. Je nachdem wieviel gerechnet wurde, um zu den zu vergleichenden Zahlen zu gelanden, musst du sogar einen eigenen Toleranz-Bereich angeben, den die Ungenauigkeit können sich während der Rechnung aufgeschaukelt haben:

Code: Alles auswählen

// nicht so: if rBetrag = 2.7 then
// sondern so:
if SameValue(rBetrag, 2.7) then
// oder
if SameValue(rBetrag, 2.7, 1E-12) then  // 1E-12 ist der zulässige Abstand zwischen den beiden Zahlen, bei dem trotzdem noch Gleichheit herrschen soll.

Weizenbaum
Beiträge: 28
Registriert: Mi 24. Aug 2016, 09:00

Re: Float Variable - Entfernen von Nachkommastellen

Beitrag von Weizenbaum »

Ok, ich hab jetzt mal die vorgeschlagenen und weitere Methoden wegen dieser Nachkommaproblematik ausprobiert. Die 2 ganz hinten hält sich jedoch hartnäckig, ich beobachte dies bei jeder einzelnen Zeile im Debugerfenster. Habe rBetrag auf der Liste der Beobachtung.

Code: Alles auswählen

        rBetrag:= SQLQueryKonto.FieldByName('BETRAG').AsCurrency;
        rBetrag:= rBetrag * 100;
        rBetrag:= Int(rBetrag);
        rBetrag:= rBetrag / 100;
        rBetrag:=trunc(rBetrag*100+0.5)/100;
        rBetrag:=StrToFloat(FormatFloat('0.00', rBetrag));
        // 2.7000000000000002
Es ist zum verzweifeln.

Soner
Beiträge: 623
Registriert: Do 27. Sep 2012, 00:07
OS, Lazarus, FPC: Win10Pro-64Bit, Immer letzte Lazarus Release mit SVN-Fixes
CPU-Target: x86_64-win64
Wohnort: Hamburg

Re: Float Variable - Entfernen von Nachkommastellen

Beitrag von Soner »

An erste Stelle verwende bei Datenbank-System keine Float-Zahlen. Sondern ein Format wo man Nachkommastellen festlegen kann, z.B. bei Firebird ist es Numeric. Stell es bei Geldwerten immer 4 Nachkommastellen. Das ist üblich und jeder rechnet mit 4-Nachkommastellen und rundet es bei der endgültigen Wertanzeige auf 2-Stellen.

Dann kannst die Ausgabe deines Feldes bei Datenbankkomponenten mit TField.DisplayFormat beinflussen:

Code: Alles auswählen

TNumericField(SQLQueryKonto.FieldByName('BETRAG')).DisplayFormat:='0.00'; // 2-Nachkommastellen
Alle DB-Controls verden jetzt, dein Feld-Wert korrekt anzeigen. Wenn du jetzt selber irgendwo den Wert anzeigen willst, dann verwende SQLQueryKonto.FieldByName('BETRAG').DisplayText anstatt SQLQueryKonto.FieldByName('BETRAG')).AsString

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Float Variable - Entfernen von Nachkommastellen

Beitrag von wp_xyz »

Weizenbaum hat geschrieben:
So 29. Aug 2021, 10:01
Ok, ich hab jetzt mal die vorgeschlagenen und weitere Methoden wegen dieser Nachkommaproblematik ausprobiert. Die 2 ganz hinten hält sich jedoch hartnäckig, ich beobachte dies bei jeder einzelnen Zeile im Debugerfenster. Habe rBetrag auf der Liste der Beobachtung.

Code: Alles auswählen

        rBetrag:= SQLQueryKonto.FieldByName('BETRAG').AsCurrency;
        rBetrag:= rBetrag * 100;
        rBetrag:= Int(rBetrag);
        rBetrag:= rBetrag / 100;
        rBetrag:=trunc(rBetrag*100+0.5)/100;
        rBetrag:=StrToFloat(FormatFloat('0.00', rBetrag));
        // 2.7000000000000002
Es ist zum verzweifeln.
Nochmals: was ist der Typ von rBetrag? Mit Currency sollte eigentlich die oberste Zeile schon funktionieren, die folgende Rundung ist unnötig und wird bei Gleitkommazahlen nicht funktionieren - siehe meine Erklärung oben.

Weizenbaum
Beiträge: 28
Registriert: Mi 24. Aug 2016, 09:00

Re: Float Variable - Entfernen von Nachkommastellen

Beitrag von Weizenbaum »

Vom Typ real, Float hab ich oben falsch angegeben, weil ich auch mit anderen Sprachen schreibe wo das vorkommt anstatt real.
Das mit Displaytext probier ich jetzt auch mal aus.

Ansonsten bleibt nur noch das ganze bei der Prüfung anzusetzen, was dann so aussehen wird.

Vorher

Code: Alles auswählen

  if StrToFloat(sParamBetrag) <> rBetrag then
  begin
    bBetragChanged:= true;
    Inherited SetBetrag(StrToFloat(sParamBetrag));  // Übergabe an die DB-Schicht.
  end
  else
  begin
    bBetragChanged:= false;
  end;
Nachher

Code: Alles auswählen

  rBetrag:= rBetrag * 100;
  rBetrag:= Int(rBetrag);
  if (StrToFloat(sParamBetrag)*100) <> rBetrag then
  begin
    bBetragChanged:= true;
    Inherited SetBetrag(StrToFloat(sParamBetrag)); // Übergabe an die DB-Schicht.
  end
  else
  begin
    bBetragChanged:= false;
  end;
  rBetrag:= rBetrag / 100;

Weizenbaum
Beiträge: 28
Registriert: Mi 24. Aug 2016, 09:00

Re: Float Variable - Entfernen von Nachkommastellen

Beitrag von Weizenbaum »

Problem gelöst, danke wp_xyz
Es lag an dem Datentyp real, hab jetzt Currency genommen für die Datenbankschicht und somit Logikschicht.

Code: Alles auswählen

    private
      // Felder
      iID: integer;
      dDatum: TDateTime;
      //rBetrag: real;
      rBetrag: currency;
      iArt: integer;

Antworten