Nächstgrößere, nächstkleinere Fließkommazahl

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
PascalDragon
Beiträge: 963
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von PascalDragon »

Mathias hat geschrieben: Mi 4. Sep 2024, 17:21 Das die DLL anders heisst kann ich noch verstehen, aber das Unterline geht mir gegen jede Logik.
Auf diese Idee muss man erst mal kommen.
Weil es eben gewisse Standards für das Mangling von den Namen von C-Funktionen gibt und hier unterscheiden sich Linux und Windows eben (und manchmal auch je nach Bitness).
Mathias hat geschrieben: Mi 4. Sep 2024, 17:21 Auch da bin ich kürzlich über dieses komische Unterline gestossen.
https://learn.microsoft.com/de-de/cpp/c ... w=msvc-170
Das sind hier zwei verschiedene Arten von Unterstrichen. Der hier ist für den tatsächlichen C-Identifier. Der obige ist für das Assemblysymbol. _fileno würde in (Object) Pascal also als __fileno importiert werden.
FPC Compiler Entwickler

Ekkehard
Beiträge: 67
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Ekkehard »

Mathias hat geschrieben: Mi 4. Sep 2024, 17:21 Das die DLL anders heisst kann ich noch verstehen, aber das Unterline geht mir gegen jede Logik.
Auf diese Idee muss man erst mal kommen.

Auch da bin ich kürzlich über dieses komische Unterline gestossen.
https://learn.microsoft.com/de-de/cpp/c ... w=msvc-170
Mit dem "DLL Export Viewer" findet man solche Schätzchen einigermaßen schnell.
https://www.nirsoft.net/utils/dll_export_viewer.html
Alterternativ hat vielleicht der ein oder andere noch ein altes Delphi installiert, dort gibt es das Kommandozeilen-Programm tdump.exe, welches ebenfalls die Namen liefert.

Ekkehard
Beiträge: 67
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Ekkehard »

Warf hat geschrieben: Mi 4. Sep 2024, 19:04 Die LibC variante wird zwar vermutlich deutlich effizienter implementiert sein, man kann das ganze aber auch recht trivial selbst in Pascal bauen:

Code: Alles auswählen

function nextAfter(const AValue: Single): Single;
var
  Sign, Exp, Mantissa: LongWord;
begin
  // Single precision float: 8 bit exponent 23 bit mantissa
  Sign := LongWord(aValue) and (1 shl 31);
  Exp := LongWord(aValue) and (255 shl 23);
  Mantissa := LongWord(aValue) and ((1 shl 23) - 1);
  if Mantissa + 1 < 1 shl 23 then
    PLongWord(@Result)^ := Sign Or Exp Or (Mantissa + 1)
  else if Exp + 1 < 256 then
    PLongWord(@Result)^ := Sign Or (Exp+1)
  else if Sign = 0 then
    Result := Infinity
  else
    Result := NegInfinity;
end;

function nextAfter(const AValue: Double): Double;
var
  Sign, Exp, Mantissa: QWord;
begin
  // Double precision float: 11 bit exponent 52 bit mantissa
  Sign := QWord(aValue) and (1 shl 63);
  Exp := QWord(aValue) and (2047 shl 52);
  Mantissa := QWord(aValue) and ((1 shl 52) - 1);
  if Mantissa + 1 < 1 shl 52 then
    PQWord(@Result)^ := Sign Or Exp Or (Mantissa + 1)
  else if Exp + 1 < 2048 then
    PQWord(@Result)^ := Sign Or (Exp+1)
  else if Sign = 0 then
    Result := Infinity
  else
    Result := NegInfinity;
end;
Vielen Dank für dieses Beispiel, welches grob getestet das gleiche Ergebnis liefert. Was bei dem "Original" _nextafter schön ist, dass man auch die Richtung angeben kann, also nicht nur die nächstgrößere, sondern auch die nächstkleinere Zahl ermitteln kann.

Bei der ganzen Aktion ist mir gerade aufgefallen, dass die in der Unit Math angegebene
MinDouble = 2.2250738585072014e-308;
tatsächlich gar nicht die kleinste mögliche Zahl ist. Im dort angegebenen Wikipedia-Artikel https://en.wikipedia.org/wiki/Double-pr ... n_examples ist diese Zahl zwar auch angegeben, zusätzlich aber auch die in unseren Versuchen ermittelte:
4.9406564584124654E−324 (Min. subnormal positive double)
Im Text darüber steht: "By compromising precision, the subnormal representation allows even smaller values up to about 5 × 10−324".

Ich hätte eigentlich erwartet, das MinDouble die kleinstmögliche Double-Zahl ist.
Bei Delphi ist die MinDouble auch so definiert.

Ich könnte mir vorstellen, dass Programme die die Konstante verwenden, deutlich unterschiedliche Ergebnisse liefern, wenn sie mit Delphi oder FPC/Lazarus compiliert werden.
Ist das ein FPC/Lazarus Bug?

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

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Warf »

Um in die andere richtung zu gehen muss man alles einfach nur einmal umdrehen, d.h. -1 statt +1 überall und in den if's statt < MAX (also 256 für exponent) einfach >= 0 schreiben. Dann müsste es auch in die andere richtung funktionieren.

Was ein bisschen tricky ist, das geht aktuell immer weg von 0, d.h. eine negative zahl wird nicht größer sondern noch kleiner, also könnte man eine dispatching funktion bauen die die Zahl und die richtung entgegen nimmt und dann entweder den decrement oder increment aufruft je nach richtung und ob die Zahl > oder < 0 ist

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1659
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von fliegermichl »

Wo ist den Infinity and NegInfinity definiert?

Benutzeravatar
Zvoni
Beiträge: 402
Registriert: Fr 5. Jul 2024, 08:26
OS, Lazarus, FPC: Windoof 10 Pro (Laz 2.2.2 FPC 3.2.2)
CPU-Target: 32Bit
Wohnort: BW

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Zvoni »

fliegermichl hat geschrieben: Do 5. Sep 2024, 09:40 Wo ist den Infinity and NegInfinity definiert?
https://www.freepascal.org/docs-html/rt ... dex-2.html
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.

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1659
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von fliegermichl »

Ah Danke, Ich hatte die Fehlermeldung nicht richtig gelesen. Der Compiler hat nicht gemeckert, dass Infinity unbekannt ist, sondern das er einem qword nicht zugewiesen werden kann.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6855
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: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von af0815 »

Wenn die Pascalumsetzung gestestet ist, wäre das nicht ein Featurerequest zumn Einfügen in die Unit Math wert ? Eventuell gleich mit Testcases.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Benutzeravatar
Zvoni
Beiträge: 402
Registriert: Fr 5. Jul 2024, 08:26
OS, Lazarus, FPC: Windoof 10 Pro (Laz 2.2.2 FPC 3.2.2)
CPU-Target: 32Bit
Wohnort: BW

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Zvoni »

af0815 hat geschrieben: Do 5. Sep 2024, 10:33 Wenn die Pascalumsetzung gestestet ist, wäre das nicht ein Featurerequest zumn Einfügen in die Unit Math wert ? Eventuell gleich mit Testcases.
Hab das hier gefunden: https://www.mail-archive.com/fpc-pascal ... 55419.html
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.

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

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Warf »

af0815 hat geschrieben: Do 5. Sep 2024, 10:33 Wenn die Pascalumsetzung gestestet ist, wäre das nicht ein Featurerequest zumn Einfügen in die Unit Math wert ? Eventuell gleich mit Testcases.
Die Umsetzung von mir ist vergleichsweise ineffizient, und ich hab schon einen Fehler gefunden (vergessen beim vergleich den Exponenten zu shiften).
z.B. kann man den Increment von Mantisse und den Overflow in Exp in eine Operation gießen, und statt dem vielen If-Then-Else blöcken sollte man Arithmetisch arbeiten (siehe Implementierung von Sign in math unit)
Wenn dann sollte man es nochmal besser bauen.

Edit: Hier mal ein kleiner Aufschlag:

Code: Alles auswählen

function floatNext(const AValue: Single): Single;
type
  SizedWord = LongWord;
  PSizedWord = ^SizedWord;
const
  ValueBits = SizeOf(SizedWord)*8-1;
var
  Sign, Value: SizedWord;
begin
  Sign := SizedWord(aValue) and (1 shl ValueBits);
  Value := SizedWord(aValue) and ((1 shl ValueBits) - 1);
  PSizedWord(@Result)^ :=
    // If Value < (1 shl ValueBits) - 1 => (Sign Or (Value + 1))
    Ord(Value < (1 shl ValueBits) - 1) * (Sign Or (Value + 1)) +
    // If Value = (1 shl ValueBits) - 1 (overflow condition) => +/- Infinity
    Ord(Value = (1 shl ValueBits) - 1) *
      // If Sign bit is 1 => 1 * Infinity, else -1 * Infinity
      ((Sign shr (ValueBits-1)) - 1) * LongWord(Infinity);
end;

function floatPred(const AValue: Single): Single;
type
  SizedWord = LongWord;
  PSizedWord = ^SizedWord;
const
  ValueBits = SizeOf(SizedWord)*8-1;
var
  Sign, Value: SizedWord;
begin
  Sign := SizedWord(aValue) and (1 shl ValueBits);
  Value := SizedWord(aValue) and ((1 shl ValueBits) - 1);
  PSizedWord(@Result)^ :=
    // If Value > 0 => (Sign Or (Value - 1))
    Ord(Value > 0) * (Sign Or (Value - 1)) +
    // If Value = 0, flip sign
    Ord(Value = 0) * (Sign XOr (1 shl ValueBits));
end;
Für Double kann man einfach den typen SizedWord anpassen zu QWord und es sollte auch funktionieren.

Braucht aber definitiv noch tests.

Und die Frage ist auch obs gewollt ist das der Pred(0) dann -0 ist und anders rum. Das ist vermutlich etwas unerwartet

Ekkehard
Beiträge: 67
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Ekkehard »

Mathias hat geschrieben: Mi 4. Sep 2024, 17:21
Jetzt geht es auf beiden Plattformen.

Code: Alles auswählen

 function nextafter(x, y: cdouble): cdouble; cdecl; external {$ifdef linux} 'm' {$else} 'msvcrt.dll' Name '_nextafter' {$endif};    
Ich habe mal eine Import DLL gebastelt, die das Ganze unter Windows dynamisch zur Laufzeit laden kann. So vermeidet man einen Crash, wenn Funktion und oder DLL nicht vorhanden, falsch etc ist und kann ggf. reagieren.
Das wird unter Linux ganz anders funktionieren. Wer mag, kann sich daran versuchen.

Gruß Ekkehard
Dateianhänge
umsvcrtdll.pas
(2.5 KiB) 69-mal heruntergeladen

Ekkehard
Beiträge: 67
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Ekkehard »

Warf hat geschrieben: Do 5. Sep 2024, 11:57
af0815 hat geschrieben: Do 5. Sep 2024, 10:33 Wenn die Pascalumsetzung gestestet ist, wäre das nicht ein Featurerequest zumn Einfügen in die Unit Math wert ? Eventuell gleich mit Testcases.
Die Umsetzung von mir ist vergleichsweise ineffizient, und ich hab schon einen Fehler gefunden (vergessen beim vergleich den Exponenten zu shiften).
z.B. kann man den Increment von Mantisse und den Overflow in Exp in eine Operation gießen, und statt dem vielen If-Then-Else blöcken sollte man Arithmetisch arbeiten (siehe Implementierung von Sign in math unit)
Wenn dann sollte man es nochmal besser bauen.

Edit: Hier mal ein kleiner Aufschlag:

Code: Alles auswählen

function floatNext(const AValue: Single): Single;
type
  SizedWord = LongWord;
  PSizedWord = ^SizedWord;
const
  ValueBits = SizeOf(SizedWord)*8-1;
var
  Sign, Value: SizedWord;
begin
  Sign := SizedWord(aValue) and (1 shl ValueBits);
  Value := SizedWord(aValue) and ((1 shl ValueBits) - 1);
  PSizedWord(@Result)^ :=
    // If Value < (1 shl ValueBits) - 1 => (Sign Or (Value + 1))
    Ord(Value < (1 shl ValueBits) - 1) * (Sign Or (Value + 1)) +
    // If Value = (1 shl ValueBits) - 1 (overflow condition) => +/- Infinity
    Ord(Value = (1 shl ValueBits) - 1) *
      // If Sign bit is 1 => 1 * Infinity, else -1 * Infinity
      ((Sign shr (ValueBits-1)) - 1) * LongWord(Infinity);
end;

function floatPred(const AValue: Single): Single;
type
  SizedWord = LongWord;
  PSizedWord = ^SizedWord;
const
  ValueBits = SizeOf(SizedWord)*8-1;
var
  Sign, Value: SizedWord;
begin
  Sign := SizedWord(aValue) and (1 shl ValueBits);
  Value := SizedWord(aValue) and ((1 shl ValueBits) - 1);
  PSizedWord(@Result)^ :=
    // If Value > 0 => (Sign Or (Value - 1))
    Ord(Value > 0) * (Sign Or (Value - 1)) +
    // If Value = 0, flip sign
    Ord(Value = 0) * (Sign XOr (1 shl ValueBits));
end;
Für Double kann man einfach den typen SizedWord anpassen zu QWord und es sollte auch funktionieren.

Braucht aber definitiv noch tests.

Und die Frage ist auch obs gewollt ist das der Pred(0) dann -0 ist und anders rum. Das ist vermutlich etwas unerwartet
Die Idee scheint mir schon richtig, aber diese Funktionen versagen fast schon beim ersten Test.
Ruft man
floatNext(-1.0);
auf, so ist das Ergebnis kleiner (negativer) als -1.0 geworden und nicht größer also näher an -0.99.
predNext(-1.0) liefert in diesem Fall dann das richtige Ergebnis.
Da stimmt was mit der Verarbeitung der Vorzeichen nicht.

Anpasssen an Double funktioniert auch nicht wie vermutet, weil da die Maske 11 Bits sein müssen und nicht 7 ((1 shl ValueBits) - 1) schief geht, es muss $0EFF raus kommen.
Gruß Ekkehard

Gruß Ekkehard

Benutzeravatar
Zvoni
Beiträge: 402
Registriert: Fr 5. Jul 2024, 08:26
OS, Lazarus, FPC: Windoof 10 Pro (Laz 2.2.2 FPC 3.2.2)
CPU-Target: 32Bit
Wohnort: BW

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Zvoni »

Ekkehard hat geschrieben: Do 5. Sep 2024, 15:26 Die Idee scheint mir schon richtig, aber diese Funktionen versagen fast schon beim ersten Test.
Ruft man
floatNext(-1.0);
auf, so ist das Ergebnis kleiner (negativer) als -1.0 geworden und nicht größer also näher an -0.99.
predNext(-1.0) liefert in diesem Fall dann das richtige Ergebnis.
Da stimmt was mit der Verarbeitung der Vorzeichen nicht.

Anpasssen an Double funktioniert auch nicht wie vermutet, weil da die Maske 11 Bits sein müssen und nicht 7 ((1 shl ValueBits) - 1) schief geht, es muss $0EFF raus kommen.
Gruß Ekkehard

Gruß Ekkehard
UNd funktionierts mit meinem Fund (Benötigt Unit Math)?
Zvoni hat geschrieben: Do 5. Sep 2024, 10:53
af0815 hat geschrieben: Do 5. Sep 2024, 10:33 Wenn die Pascalumsetzung gestestet ist, wäre das nicht ein Featurerequest zumn Einfügen in die Unit Math wert ? Eventuell gleich mit Testcases.
Hab das hier gefunden: https://www.mail-archive.com/fpc-pascal ... 55419.html
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.

Sieben
Beiträge: 292
Registriert: Mo 24. Aug 2020, 14:16
OS, Lazarus, FPC: Ubuntu Xenial 32, Lazarus 2.2.0, FPC 3.2.2
CPU-Target: i386

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Sieben »

Was ist mit der Pascal-Implementation, die Zvoni hier verlinkt hat?

Edith sacht: Zvoni war schneller...

Mathias
Beiträge: 6964
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Nächstgrößere, nächstkleinere Fließkommazahl

Beitrag von Mathias »

Ich habe mal eine Import DLL gebastelt, die das Ganze unter Windows dynamisch zur Laufzeit laden kann. So vermeidet man einen Crash, wenn Funktion und oder DLL nicht vorhanden, falsch etc ist und kann ggf. reagieren.
Das wird unter Linux ganz anders funktionieren. Wer mag, kann sich daran versuchen.

Code: Alles auswählen

    Pointer(nextafter) := GetProcAddress(HDll, '_nextafter');
Muss da nicht auch folgendes rein ?

Code: Alles auswählen

  nextafter_NAME = {$ifdef linux} 'nextafter' {$else} '_nextafter' {$endif};
...
    Pointer(nextafter) := GetProcAddress(HDll, nextafter_NAME);
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten