Floatingpoint in Komponenten

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
siro
Beiträge: 732
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Floatingpoint in Komponenten

Beitrag von siro »

Guten Morgen,
ich habe schon jahrzehntelang Floatingpoints in einigen Komponenten und bisher auch noch nie Probleme gehabt.

Nachdem ich aber grade etwas in Lazarus gestöbert habe, kamen mir Zweifel und so dachte ich mir,
da frage ich mal in die Runde.

Es gibt ja keine unendliche Genauigkeit bei Floats und man sollte Floats "eigentlich" auch nie auf "Gleichheit" prüfen.

Die Genauigkeit, was ich bis eben auch noch garnicht wusste, ist in "Epsilon" festgelegt.
Epsilon befindet sich in TDoubleHelper.
https://www.freepascal.org/docs-html/rt ... elper.html

Jetzt die eigentliche Frage: Wie macht man es richig um auf Gleichheit bzw. auf Null zu prüfen ?
Dazu ein Minitest Code:

Code: Alles auswählen

Type TMyTest = class(TComponent)
  private
    FMin    : Double;
    procedure SetMin(value:Double);
  published
    property Min : Double read FMin write SetMin;
end;


procedure TMyTest.SetMin(value:Double);
begin
  if value = Min          then Exit;    { entweder so }
  if SameValue(Min,value) then exit;    { oder so ??? }

  if value = 0     then value:=1;       { entweder so }
  if isZero(value) then value:=1;       { oder so }

  FMin:=value;
end;

Macht das einen Unterschied ? sollte ich doch lieber meinen Code anpassen ?

für Infos bzw. Anregungen wäre ich euch dankbar.
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: Floatingpoint in Komponenten

Beitrag von Winni »

Hi!

Für math.IsZero ist anzumerken:

Es gibt eine überladene Version, bei der Du das Epsilon selbst bestimmen kannst:

Code: Alles auswählen

function IsZero(const A: Single; Epsilon: Single): Boolean;  
Zweitens gibt es konstante Werte für die verschiedenen Float-Datentypen:

Code: Alles auswählen

Const
  EZeroResolution = 1E-16;
  DZeroResolution = 1E-12;
  SZeroResolution = 1E-4;    
Bevor ich das entdeckt habe, hab ich schon mal lange nach einem Fehler in meinem Programm gesucht. Antwort: Die ZeroResolution für Single ist mit 1E-4 doch sehr groß. Dort hatte IsZero Null gemeldet wo es das nicht sollte.

Man muss also immer eine Abschätzung der Größenordung machen, bevor man IsZero tätigt und evtl. das Epsilon anpassen.

Wenn man mit TPointF für Bildschirm-Koordinaten rechnet, ist alles ab der zweiten Stelle hinterm Komma egal. Wenn man allerdings mit Längen- und Breitengraden rechnet, so ist 1E-4 viel zu groß. Und zack landest Du in der Ostsee. Oder sonstwo. Als Beispiele.

Winni

siro
Beiträge: 732
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Floatingpoint in Komponenten

Beitrag von siro »

Hallo Winni,
danke Dir für deine Info.

isZero scheint mir auch etwas zu gefährlich zu sein.... :wink:
Null kann ein Wert doch eigentlich nur sein, wenn ALLE Bytes des Wertes aufoderiert 0 ergeben, oder besser gesagt wenn ALLE BIts des Wertes 0 sind.
Diese Aussage müsste (meiner Meinung nach) auch für Single, Double usw. zutreffen.
Ich hab mal ein kleines Progrämmchen gemacht zum Testen:
es bestätigt, dass bei 1E-12 isZero TRUE liefert.... :shock:

Code: Alles auswählen

var value : Double = 1E-12;    // ein beliebiger Testwert  (isZero) liefert TRUE

procedure TForm1.FormCreate(Sender: TObject);
var p:pByte = @value;  // Bytezeiger auf die Adresse von value setzen
    i:integer;         // Bytezaehler
    s:string;          // Ausgabe Text
begin
  // alle Hexadezimalen Bytes ausgeben
  s:='Hexbytes: ';
  for i:=1 to sizeof(value) do begin
    s:=s+IntToHex(p^,2);
    inc(p);
  end;

  // den Floatwert
  s:=s+' Float:'+floattostr(value);

  // prüfen ob Null
  if isZero(value) then s:=s+' !!! isZero !!! ';

  caption:=s;  // Ausgabe
end;                        
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: Floatingpoint in Komponenten

Beitrag von Winni »

Hi!

Du kannst aber nicht auf Gleichheit bei jeder Art von Float sicher prüfen.

Es gibt Zahlen, die lassen sich nicht exakt 4 oder 8 byte (single oder double) darstellen. Das heißt: Irgendeine Konstante in Deinem Source wird schon beim Kompilieren nicht genau in den Binärwert umgewandelt, weil es nicht möglich ist.

Grausames Beispiel aus den Anfängen der Digitaltechnik:

Die ersten Taschenrechner mit Wurzel-Funktion ergaben:

√4 = 1.99999999

Winni

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

Re: Floatingpoint in Komponenten

Beitrag von wp_xyz »

Ein kleines Beispiel, warum man die Null mit Hilfe von IsZero prüfen muss: Angenommen du musst ein Diagramm der Funktion y = 1/(x - 3.9) erstellen, wobei x aus einem Laufindex durch Multiplikation mit der Wert 1.3 berechnet wird. Du siehst, da wird durch x - 3.9 dividiert. Um einen Laufzeitfehler auszuschließen, fügst du in deinem Code die Abfrage "if x - 3.9 <> 0" ein und berechnest den Funktionswert nur in diesem Fall. Dennoch sieht das Diagramm völlig unverständlich aus. Grund ist, dass das Produkt i*1.3 für i = 3 nicht EXAKT gleich 3.9 ist, sondern um einen winzigen Wert davon abweicht. Daher wird die Abfrage "if x - 3.9 <> 0" nicht berücksichtigt, und die 1 wird durch eine sehr kleine Zahl dividiert, was zu einem (betragsmäßig) unvernünftig großem Ergebnis führt.

Wird dagegen die Null mit Hilfe der Funktion IsZero geprüft, so wird an diesem kritischen Wert die "De-fakto-Null" erkannt, und die Division überhaupt nicht ausgeführt.
Dateianhänge
GleichNull.png
GleichNull.png (16.01 KiB) 1631 mal betrachtet
IsZero.png
IsZero.png (16.35 KiB) 1631 mal betrachtet
IsZero_Demo.zip
(2.32 KiB) 67-mal heruntergeladen

siro
Beiträge: 732
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Floatingpoint in Komponenten

Beitrag von siro »

Das ist ein sehr schönes und anschauliches Beispiel.
Vielen Dank wp_xyz, wieder mal viel Mühe reingesteckt.

Das habe ich jetzt auch verstanden.
Die Genauigkeit beim Double liegt ja bei 15 Stellen.
Es ist nicht möglich alle Zahlen exakt darzustellen.
Durch eine Berechnung wie in deinem Beispiel, bleibt da halt ein bischen übrig...
Und dieses "ungefär 0" erledigt dann die Funktion isZero, welche speziell auf den entsprechenden Datentyp zugeschnitten ist,
wie Winni es schon schrieb.

Neue Versuche:

Code: Alles auswählen

var x:Double;
procedure TForm1.FormCreate(Sender: TObject);
begin
 x:=3.9;      // x - 3.9 ist dann tatsächlich 0  (vermutlich macht hier der Compiler schon eine Vorausberechnung bzw. Optimierung)
 x:=3 * 1.3;  // hier aber nicht, sondern das ergibt bei x-3.9 = 4.44089209850063E-16
 if (x-3.9) = 0 then caption:='Jo, is null'
                else caption:=FloatToStr(x-3.9);   // nicht null, sondern 4.44089209850063E-16
end;         
hat sich alles bestätigt.

Ist es dann aber sinnvoll für meine Setter Mothode der Komponente, SameValue zu verwenden ?

Ich setze im Objektinspektor einen Double Wert, der wird dann schon unter Umständen "ungenau" abgelegt.
Wenn ich nun in SetValue(newValue:Double) auf Gleichheit prüfe, müsste doch die gleiche Ungenauigkeit geprüpft werden..?
if newValue = oldValue then exit;

oder mein Ihr, ich sollte doch lieber mit SameValue arbeiten.
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

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

Re: Floatingpoint in Komponenten

Beitrag von wp_xyz »

Die Gleichheitsprüfung im Setter einer Komponenten-Eigenschaft soll eigentlich nur sicherstellen, dass der eigentliche Setter-Code nicht unnötigerweise ausgeführt wird, wenn sich nichts geändert hat. Nur wenn der Eigenschaftswert sehr, sehr oft verändert wird oder der vom Setter ausgeführte Code sehr aufwändig ist, spielt es überhaupt eine Rolle, den vorigen Property-Wert zu prüfen, geschweige davon, die Gleichheit mit dem '='-Operator oder den IsZero/SameValue-Funktionen zu vergleichen.

Falls du Bedenken hast, dass ein zu großzügig gewähltes Epsilon zu Fehlinterpretationen der übergebenen Werte führt, dann hast du wahrscheinlich generell schon ein Problem damit auch an anderer Stelle. Wenn Floating-Point-Rechnungen in irgendeiner Weise mit Vergleichen zu tun haben (und das ist nahezu immer die Regel), müssen die zu verknüpfenden Zahlen in "vernünftiger" Größenordnung liegen. Wenn man z.B. astronomische Rechnungen in Metern ausführt, wird man mit diesen Epsilon-Werten nicht auskommen, sondern man muss entweder Epsilon drastisch vergrößern, oder zu Einheiten übergehen, die zu handlicheren Zahlen führen (Lichtjahre, Parsec, o.ä.).

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: Floatingpoint in Komponenten

Beitrag von Winni »

wp_xyz hat geschrieben:
Mi 16. Feb 2022, 15:27
Wenn man z.B. astronomische Rechnungen in Metern ausführt, ...
Hi !

Gute Idee!

Dann ist Sirius 8,1468671E+16 Meter entfernt.

Das sind 81 Petameter.

Und Sirius ist uns sehr nahe - "nur" 8.6 Lichtjahre ....

Winni

siro
Beiträge: 732
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Floatingpoint in Komponenten

Beitrag von siro »

Die Abfrage auf Gleichheit hat mir schon Rekursionen erspart, bei Parametern die voneinander abhängig sind.

Es ging mir darum, mein Vorgehen nochmal zu überdenken.
Die Komponenten sind schon über 20 Jahre alt und ich hatte glücklicherweise noch keine Probleme damit.

ByTheWay:
wenn 1E-12 als Null erkannt wird, dann wird mein Pikofarad Kondensator wegoptimiert... :wink:
Aber es lässt sich ja das Epsilon anpassen und so hat man also alle Zügel in der Hand.

Ich danke euch beiden für die Informationen,
so habe ich auch wieder dazuglernt.
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: Floatingpoint in Komponenten

Beitrag von Winni »

Hi!

Ja, dann wird der Picofarad-Kondensator wegoptimiert.

Was immer Du nimmst als Konstante für Epsilon - es ist eine Hilfskrücke.

Sieh Dir in der Unit math den möglichen Wertebereich eines Single an:

Code: Alles auswählen

MinSingle    =  1.1754943508e-38;
MaxSingle    =  3.4028234664e+38;  
Mit dem default Epsilon von 10E-4 aus der unit math sind alle Berechnungen kurz über MinSingle gleich Null. Und wenn man im Bereich kurz unterhalb von MaxSingle rechnet, dann fällt 10E-4 überhaupt raus, also wird isZero immer false zurückgeben.

Ich sehe nur die Möglicheit das Epsilon dynamisch anzupassen, z.B. als ein Promille des aktuellen Wertebereichs.

Winni

Antworten