Präzision mit Floats

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
wp_xyz
Beiträge: 5129
Registriert: Fr 8. Apr 2011, 09:01

Re: Präzision mit Floats

Beitrag von wp_xyz »

Niesi hat geschrieben: Sa 6. Jul 2024, 16:34 Da aber delta = 0.0200000000000001 und delta_up = 0.0199999999999999
als Ergebnis geliefert werden, ist beim Vergleich delta_up kleiner als delta.
Da liegt der Hase (Hund?) begraben. Du darfst normalerweise zwei Floats nicht direkt vergleichen, sondern musst in der Regel einen Toleranzwert berücksichtigen. In der Math-Unit gibt es ein SameValue(x, y, Eps). Statt "if x = y" würde ich schreiben "if SameValue(x, y, 1e-9)" - das gibt immer true zurück, solange x und y sich um nicht mehr als 1E-9 unterscheiden. Der Toleranzwert von 1E-9 ist relativ groß im Vergleich zur Grundgenauigkeit von Double und Extended, aber die Rundungsfehler können sich je nach Rechnung und Größenordnung des Ergebnisses leicht vergrößern; in der Regel komme ich damit gut zurecht.

Für die anderen Vergleiche würde ich mir Funktionen schreiben:

Code: Alles auswählen

function LessThan(x, y, Eps: float): Boolean;
begin
  if SameValue(x, y, Eps) then
    Result := false
  else
    Result := x < y;
end;

function LessEqual(x, y, Eps: float): Boolean;
begin
  if SameValue(x, y, Eps) then
    Result := true
  else
    Result := x < y;
end;

// analog: GreaterThan und GreaterEqual
P.S. Und lass die Finger von RoundTo. Denn wenn die gerundete Zahl nicht exakt als Binärzahl (Summe von Zweier-Potenzen) dargestellt werden kann, hast du dasselbe Problem wieder.

Benutzeravatar
Niesi
Lazarusforum e. V.
Beiträge: 581
Registriert: So 26. Jun 2016, 19:44
OS, Lazarus, FPC: Linux Mint Cinnamon, Laz 3.9 Fpc 3.2.3 und allerlei mit FpcUpDeLuxe
Kontaktdaten:

Re: Präzision mit Floats

Beitrag von Niesi »

wp_xyz hat geschrieben: Sa 6. Jul 2024, 16:55
Niesi hat geschrieben: Sa 6. Jul 2024, 16:34 Da aber delta = 0.0200000000000001 und delta_up = 0.0199999999999999
als Ergebnis geliefert werden, ist beim Vergleich delta_up kleiner als delta.
Da liegt der Hase (Hund?) begraben. Du darfst normalerweise zwei Floats nicht direkt vergleichen, sondern musst in der Regel einen Toleranzwert berücksichtigen. In der Math-Unit gibt es ein SameValue(x, y, Eps). Statt "if x = y" würde ich schreiben "if SameValue(x, y, 1e-9)" - das gibt immer true zurück, solange x und y sich um nicht mehr als 1E-9 unterscheiden. Der Toleranzwert von 1E-9 ist relativ groß im Vergleich zur Grundgenauigkeit von Double und Extended, aber die Rundungsfehler können sich je nach Rechnung und Größenordnung des Ergebnisses leicht vergrößern; in der Regel komme ich damit gut zurecht.

Für die anderen Vergleiche würde ich mir Funktionen schreiben:

Code: Alles auswählen

function LessThan(x, y, Eps: float): Boolean;
begin
  if SameValue(x, y, Eps) then
    Result := false
  else
    Result := x < y;
end;

function LessEqual(x, y, Eps: float): Boolean;
begin
  if SameValue(x, y, Eps) then
    Result := true
  else
    Result := x < y;
end;

// analog: GreaterThan und GreaterEqual
P.S. Und lass die Finger von RoundTo. Denn wenn die gerundete Zahl nicht exakt als Binärzahl (Summe von Zweier-Potenzen) dargestellt werden kann, hast du dasselbe Problem wieder.

Herzliichen Dank, dies bringt mich weiter. Zumal SameValue() auch mit PlusMinus richtig umgeht - ist genial einfach und damit wirklich gut!

Ich werde mich dann auch mal mit der Darstellung von Gleitkommazahlen in binären Systemen befassen müssen ...
Wissen ist das einzige Gut, das sich vermehrt, wenn es geteilt wird ...

icho2099
Beiträge: 47
Registriert: Fr 21. Feb 2020, 19:17
OS, Lazarus, FPC: Win10/64
CPU-Target: 64 Bit
Wohnort: Osterholz-Scharmbeck

Re: Präzision mit Floats

Beitrag von icho2099 »

Hier https://de.wikipedia.org/wiki/Gleitkommazahl
ist eigentlich alles gut erklärt.

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

Re: Präzision mit Floats

Beitrag von wp_xyz »

Oder durchsuche das Forum. Warf hat vor einiger Zeit einen sehr guten Beitrag über Gleitkommazahlen geschrieben.

Dein Ergebnis 0.02 = 1/50 = 1/(2*2*5) z.B. kann nie exakt als Gleitkommazahl dargestellt werden, denn es ist wegen der enthaltenen 5 keine Kombination von Zweierpotenzen; 0.25 = 1/4 = 1/(2*2) = 2^-2 dagegen wäre es schon. Selbst wenn die FloatToStr-Ausgabe exakt 0.02 anzeigt - da läuft viel Code zur Verschönerung der Anzeige.

Vbxler
Beiträge: 129
Registriert: Sa 25. Mai 2013, 07:43
OS, Lazarus, FPC: Win7_x64 (FPC:4.7.1)
CPU-Target: 32Bit

Re: Präzision mit Floats

Beitrag von Vbxler »

Das Problem mit den Gleitpunktzahlen ist manchmal ärgerlich
aber das ist Systemimmanent, mit dem müssen wir leben.
Dafür wurde ja der Datentyp Currency gemacht um das Problem zu umgehen.
Dann schaut dein Ergebniss so aus:

https://wiki.freepascal.org/Currency
Hilft dir das weiter?
Dateianhänge
Bildschirmfoto_2024-07-07_19-02-13.png
Bildschirmfoto_2024-07-07_19-02-13.png (67.79 KiB) 641 mal betrachtet

Benutzeravatar
Niesi
Lazarusforum e. V.
Beiträge: 581
Registriert: So 26. Jun 2016, 19:44
OS, Lazarus, FPC: Linux Mint Cinnamon, Laz 3.9 Fpc 3.2.3 und allerlei mit FpcUpDeLuxe
Kontaktdaten:

Re: Präzision mit Floats

Beitrag von Niesi »

Vbxler hat geschrieben: So 7. Jul 2024, 19:05 Das Problem mit den Gleitpunktzahlen ist manchmal ärgerlich
aber das ist Systemimmanent, mit dem müssen wir leben.
Dafür wurde ja der Datentyp Currency gemacht um das Problem zu umgehen.
Dann schaut dein Ergebniss so aus:

https://wiki.freepascal.org/Currency
Hilft dir das weiter?

Danke für Deine Antwort - aber an currency müchte ich nicht rangehen, da kann ich auch RoundTo oder Round verwenden. Curreny ist für mich sogar noch "geheimnisvoller", weil ich nicht weiß, was da zusätzlich gemacht wird. Ist für Banker und rundet auch so ...


Bildschirmfoto vom 2024-07-08 16-41-00.png
Bildschirmfoto vom 2024-07-08 16-41-00.png (24.21 KiB) 619 mal betrachtet


Die Lösung mit SameValue finde ich gut, ich bilde mir ein, sie zu verstehen und da habe ich dann Vertrauen zu ... :mrgreen:

Code: Alles auswählen

Const
  EZeroResolution = Extended(1E-16); 


function SameValue(const A, B: Extended; Epsilon: Extended): Boolean;

begin
  if (Epsilon=0) then
    Epsilon:=Max(Min(Abs(A),Abs(B))*EZeroResolution,EZeroResolution);
  if (A>B) then
    Result:=((A-B)<=Epsilon)
  else
    Result:=((B-A)<=Epsilon);
end;
PrecisionCurrency.7z
(147.83 KiB) 31-mal heruntergeladen
Wissen ist das einzige Gut, das sich vermehrt, wenn es geteilt wird ...

Warf
Beiträge: 2118
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Präzision mit Floats

Beitrag von Warf »

Was du hier siehst ist zu erwarten, Fließkommazahlen im PC sind im binär system, und können damit nur eine begrenzte Menge an zahlen genau darstellen.

Als Beispiel wir rechnen im dezimal system, also basis 10, die primfaktorzerlegung von 10 ist 2 und 5. Damit können ausschließlich Summen und Produkte von fraktionen von 2 und 5 dargestellt werden:
1/2=0.5
1/5=0.2
3/10=(1/2*1/5)*3=0.3
Zahlen die allerdings nicht damit dargestellt werden können sind nicht (endlich) darstellbar:
1/3=0.3333...
1/7=0.14...
Und müssen somit zwangsläufig irgendwann angeschnitten oder gerundet werden.

Genauso ist's beim binär system, nur das man hier nicht 2 und 5 hat sondern nur 2.

Daher zu deinem Problem, 0.2=1/5 und 0.6=3/5 und damit zwar darstellbar im dezimal system aber nicht binär.

Warum klappt es manchmal doch? Naja das ist ein trick vom Computer, es gibt im 2 wege zu runden. Entweder 0.20...1 oder 0.199...9 (bzw. Entsprechende binär zahlen, ich benutz dezimal nur zur Veranschaulichung). Und das system hat einfach gehardcoded das die beiden zahlen ist 0.2. intern ist's aber trotzdem nicht genau, es tut nur so. Und je nach dem wie du bei einem Ergebnis ankommst landest du halt auf dem anderen Rundungsfehler und damit sieht es falsch aus:

Code: Alles auswählen

var a, b, c: Double;
begin
  a:=0.1;
  b:=0.3;
  c:=a*3;
  writeln(a, ' ', b, ' ',c, 0.3);
end.
Ergebnis

Code: Alles auswählen

 1.0000000000000001E-001  2.9999999999999999E-001  3.0000000000000004E-001 3.00000000000000000011E-0001
Es ist also nicht so das er manchmal korrekt rechnet und manchmal falsch, er rechnet immer falsch und gaukelt dir aber manchmal vor richtig zu rechnen

Antworten