Entwickler-Ecke
Sonstiges (Delphi) - Hilfe beim Linken von C object files in in Delphi XE5?
Andreas_500 - Mo 10.05.21 11:27
Titel: Hilfe beim Linken von C object files in in Delphi XE5?
Hallo Delphi-Community,
am 5. Mai 2021 wurde ein interessantes Thema vorgestellt:
High-Precision Floating-Point Types for Delphi:
https://blog.grijjy.com/2021/05/05/high-precision/comment-page-1/#comment-29726.
Hier wird eine von Erik van Bilsen entwickelte Delphi-Schnittstelle repräsentiert
https://github.com/neslib/Neslib.MultiPrecision, die zwei neue Floating point-Typen zur Verfügung stellt:
- DoubleDouble (128-Bit: Doppelte Genauigkeit von Double)
- QuadDouble (256-Bit: Vierfache Genauigkeit von Double)
Erik’s Bibliothek basiert auf der C/C++ - Bibliothek von David H. Bailey
https://www.davidhbailey.com/dhbsoftware/
Leider macht Erik's Bibliothek auch von den neuesten Delphi-Features (Delphi 10.4.2.) Gebrauch. Wenn man die Inline-Deklarationen in den Routinen
function MultiPrecisionInit: UInt32; und
procedure MultiPrecisionReset(const AState: UInt32); in der Unit
Neslib.MultiPrecision.pas umschreibt, läßt sich das Projekt auch mit XE5 kompilieren.
Allerdings sind die Ergebnisse mit XE5 bedauerlicherweise nicht zu gebrauchen, weil sie falsch sind. Meine Kommentare und Korrespondenz mit Erik sind unter oberem Link
https://blog.grijjy.com/2021/05/05/high-precision/comment-page-1/#comment-29726 am Ende seines Beitrags zu finden.
Summa summarum schreibt mir Erik zum Schluß:
Zitat: |
I can reproduce the issues with XE5, but it seems to have to do with the way Delphi XE5 links C object files. Tried creating those object files in different ways, with different calling conventions but couldn’t get it to work. On Win32, it would generate invalid results, and on Win64 it would result in Access Violations. I don’t know in which Delphi version this was changed to the current (working) behavior. But I can’t spend too much time on trying to support an 8 year old Delphi version. |
Kennt sich jemand von Euch mit der von Erik vermuteten Problematik des seit XE5-Zeiten veränderten Verhaltens neuerer Delphi-Versionen bezüglich des Linkens von "C object files"?
Und vor allem könnte jemand dabei helfen, diesen
wertvollen Schatz zu bergen und ihn auch
für ältere Delphi-Versionen zur Verfügung zu stellen. Die Benutzung von
Neslib.MultiPrecision.pas scheint wesentlich einfacher zu sein, als die der deutlich komplexer zu handhabenden MPA-Bibliotheken vom verstorbenen
Gammatester, zumal man hier lediglich den Dateitypen ändern müßte, um auf einen Schlag 128 oder 256-Bit Fließkomma-Genauigkeit
mit Prozessor-Geschwindigkeit zu erhalten.
Danke im Voraus für jedwede Hilfe!
Gruß, Andreas
PS:
Ich werde Erik van Bilsen den Link zu diesem Topic mitteilen, damit er eventuelle Verbesserungsvorschläge direkt verfolgen kann.
[edit]
Eine analoge Anfrage habe ich auch unter
https://www.delphipraxis.net/207850-high-precision-floating-point-types-delphi-hilfe-beim-linken-von-c-object-files.html#post1489036 gestellt, in der Hoffnung auf eine breitere Resonanz.
Th69 - Di 11.05.21 09:17
Wer hat denn die C Object Files kompiliert (und mit welchen Compiler-Einstellungen)?
Kann es sein, daß diese schon von den neueren
AVX [
https://de.wikipedia.org/wiki/Advanced_Vector_Extensions]-512 Befehlen Gebrauch machen?
Was für einen (genauen) Prozessortyp hast du denn?
Edit: Ich habe mal den Blog und die Kommentare von dir und Erik gelesen. Verstehe ich das richtig, daß Erik auf seinem Rechner auch Delphi XE5 installiert hat und es dort ebenso nicht läuft? Dann würde das wohl gegen meine Annahme sprechen. Aber evtl. fragst du ihn nochmal, ob es der gleiche Rechner war (und welcher Prozessortyp dort drauf ist)?
Andreas_500 - Di 11.05.21 09:40
Die C Object Files wurden wahrscheinlich von Erik kompiliert. Ich habe die Dateien so zu verwenden versucht, wie sie auf
https://github.com/neslib/Neslib.MultiPrecision zu finden sind. Da ich kein Informatiker bin, sondern nur ein pensionierter Ingenieur und Hobbyprogrammierer, kann ich leider kein C und weiß auch nichts über die Modalitäten der Erzeugung der C Object Files.
Ich habe einen 6 Jahre alten PC mit folgendem Prozessor:
Intel Core i3 4160, 3.60 GHz.
Danke für Deine Mühe!
Gruß, Andreas
Andreas_500 - Di 11.05.21 10:26
Ja, Erik hat auf meine Anregungen hin XE5 installiert und konnte das von mir beobachtete Fehlverhalten der Bibliothek nachvollziehen:
Beim Zielplattform Win 32: waren alle Ergebnisse falsch;
Bei Win 64: kommt es zum Access violation.
Andreas
Andreas_500 - Di 11.05.21 12:02
Sorry, mit den Inhalten von
BuildX64.bat und
BuildX86.bat kann ich mangels C-Kenntnisse leider nichts anfangen…
Aber in
Neslib.MultiPrecision.pas sind zahlreiche Conditional Defines vorhanden, die für das korrekte Einbinden der Object-Files sorgen sollen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56:
| … {$REGION 'Internal Declarations'} {$IF Defined(WIN32)} const _PU = '_'; {$IF Defined(MP_ACCURATE)} {$LINK 'dd32-accurate.obj'} {$LINK 'qd32-accurate.obj'} {$ELSE} {$LINK 'dd32.obj'} {$LINK 'qd32.obj'} {$ENDIF} {$ELSEIF Defined(WIN64)} const _PU = ''; {$IF Defined(MP_ACCURATE)} {$LINK 'dd64-accurate.obj'} {$LINK 'qd64-accurate.obj'} {$ELSE} {$LINK 'dd64.obj'} {$LINK 'qd64.obj'} {$ENDIF} {$ELSEIF Defined(IOS)} {$DEFINE USE_LIB} const _PU = ''; {$IF Defined(MP_ACCURATE)} const _LIB_MP = 'mp-accurate_ios64.a'; {$ELSE} const _LIB_MP = 'mp_ios64.a'; {$ENDIF} {$ELSEIF Defined(MACOS64)} {$DEFINE USE_LIB} const _PU = ''; {$IF Defined(MP_ACCURATE)} const _LIB_MP = 'mp-accurate_mac64.a'; {$ELSE} const _LIB_MP = 'mp_mac64.a'; {$ENDIF} {$ELSEIF Defined(ANDROID32)} {$DEFINE USE_LIB} const _PU = ''; {$IF Defined(MP_ACCURATE)} const _LIB_MP = 'libmp-accurate_android32.a'; {$ELSE} const _LIB_MP = 'libmp_android32.a'; {$ENDIF} {$ELSEIF Defined(ANDROID64)} {$DEFINE USE_LIB} const _PU = ''; {$IF Defined(MP_ACCURATE)} const _LIB_MP = 'libmp-accurate_android64.a'; {$ELSE} const _LIB_MP = 'libmp_android64.a'; {$ENDIF} {$ELSE} {$MESSAGE Error 'Unsupported CPU'} {$ENDIF} … |
Und der Delphi-Compiler setzt je nach Zielplattform (Win32/Win64) seine eigenen Defines, so daß es passen sollte.
Die Begriffe wie "-msse2 -O3", "SSE2", "gcc" und der Assembler-Code sind für mich Informatik-Laien spanische Dörfer…
Andreas
jaenicke - Di 11.05.21 12:26
Ich habe kein XE5, nur XE6 habe ich installiert. Damit bekomme ich die gleichen Ergebnisse wie mit 10.4.2. Ist es korrekt, dass im Testprogramm Abweichungen vorhanden sind?
Wie sollte denn die korrekte Ausgabe des Testprogramms aussehen?
(Ich habe nicht genauer geschaut, nur mal laufen lassen.)
Andreas_500 - Di 11.05.21 16:44
Hallo Sebastian,
Delphi XE5 kann von den beigefügten Originalbeispielen leider
kein einziges kompilieren, weil etliche neue Units fehlen wie z.B.
System.Messaging,
System.Math.Vectors
FMX.Controls.Presentation,
FMX.EditBox,
FMX.SpinBox
Deshalb habe ich aus
MultiPrecision.QuadDouble.Tests.pas ein einfaches Consolenprogramm gemacht:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108:
| program NesLib_MP_Test_1;
{$APPTYPE CONSOLE}
{$R *.res}
uses System.SysUtils , Neslib.MultiPrecision ;
Procedure Print_SOLL_IST(const SollWert: String; ISTWert: QuadDouble); VAR S: String; Begin WriteLn('SOLL = ', SollWert); S:= QuadDouble.ToString(ISTWert);
WriteLn('IST = ', S); WriteLn; End;
Procedure TestAbs; VAR A, B: QuadDouble; Begin A := Abs(QuadDouble.Pi); Print_SOLL_IST('3.14159265358979323846264338327950288419716939937510582097494459', A);
B := -QuadDouble.Pi; Print_SOLL_IST('-3.14159265358979323846264338327950288419716939937510582097494459', B);
A := QuadDouble.Zero; A := Abs(B); Print_SOLL_IST('3.14159265358979323846264338327950288419716939937510582097494459', A); End;
Procedure TestAddD_QD; VAR A, B: QuadDouble; D: Double; Begin D := 1.1; A := 1.1 + QuadDouble.Pi; B := D + QuadDouble.Pi; Print_SOLL_IST('4.24159265358979332728048535329202611808770284664073082097494459', A); Print_SOLL_IST('4.24159265358979332728048535329202611808770284664073082097494459', B); End;
Procedure TestAddDD_QD; VAR A: QuadDouble; Begin A := DoubleDouble.Pi + QuadDouble.E; Print_SOLL_IST('5.85987448204883847382293085463216837672422621141462003753618010', A); End;
Procedure TestAddQD_D; VAR A, B: QuadDouble; D: Double; Begin D := 1.1; A := QuadDouble.Pi + 1.1; B := QuadDouble.Pi + D; Print_SOLL_IST('4.24159265358979332728048535329202611808770284664073082097494459', A); Print_SOLL_IST('4.24159265358979332728048535329202611808770284664073082097494459', B); End;
Procedure TestAddQD_DD; VAR A: QuadDouble; Begin A := QuadDouble.Pi + DoubleDouble.E; Print_SOLL_IST('5.85987448204883847382293085463216750967152453125167835081770655', A); End;
Procedure TestAddQD_QD; VAR A: QuadDouble; Begin A := QuadDouble.Pi + QuadDouble.E; Print_SOLL_IST('5.85987448204883847382293085463216538195441649307506539594191222', A); End;
VAR FPU_Status: UInt32;
Begin try FPU_Status:= MultiPrecisionInit; TestAbs; TestAddD_QD; TestAddDD_QD; TestAddQD_D; TestAddQD_DD; TestAddQD_QD;
ReadLn; MultiPrecisionReset(FPU_Status); Except On E: Exception Do Writeln(E.ClassName, ': ', E.Message); End; End. |
In der Bibliothek
Neslib.MultiPrecision.pas mußte ich zuvor für XE5 auch noch die Inline-Deklarationen in zwei Routinen ändern:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
| function MultiPrecisionInit: UInt32; VAR ExceptionMask: TArithmeticExceptionMask; RoundMode : TRoundingMode; PrecisionMode: TFPUPrecisionMode; begin {$IFDEF MSWINDOWS} ExceptionMask:= GetExceptionMask; RoundMode := GetRoundMode; PrecisionMode:= GetPrecisionMode;
SetExceptionMask(exAllArithmeticExceptions); SetRoundMode(rmNearest); SetPrecisionMode(pmDouble);
Result := Byte(ExceptionMask) or (Ord(RoundMode) shl 8) or (Ord(PrecisionMode) shl 16); {$ELSE} Result := 0; {$ENDIF} end;
procedure MultiPrecisionReset(const AState: UInt32); VAR ExceptionMask: TArithmeticExceptionMask; RoundMode : TRoundingMode; PrecisionMode: TFPUPrecisionMode; begin {$IFDEF MSWINDOWS} ExceptionMask:= TArithmeticExceptionMask(Byte(AState)); RoundMode := TRoundingMode((AState shr 8) and $FF); PrecisionMode:= TFPUPrecisionMode((AState shr 16) and $FF); SetExceptionMask(ExceptionMask); SetRoundMode(RoundMode); SetPrecisionMode(PrecisionMode); {$ENDIF} end; |
Ein Fehler tritt definitiv in der
function QuadDouble.ToString(..) auf, innerhalb von ToDigits(..)
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| ... else if (E > 300) then begin R := Ldexp(R, -53); R := R / IntPower(Ten, E); R := Ldexp(R, 53); end else R := R / IntPower(Ten, E); ... |
ganz konkret in der Zeile
R := R / IntPower(Ten, E); auf.
Die externe Function IntegerPotenz
IntPower(Ten, E) liefert noch den korrekten Record (Ten = 10, E = 0) --> IntPower(Ten, E) = (1,0,0,0) zurück, aber es scheint im zugelinkten C-Code zu einer Division durch Null zu kommen, weil der Record R nach der Division überall +NAN enthält: R = (+NAN, +NAN, +NAN, +NAN).
Danke für Deine wiederholte Hilfe, Sebastian!
Gruß, Andreas
[edit]:
Alle augedruckten Ergebnisse im obigen Testprogramm liefern für den Istwert einen "Leerstring mit einem Exponenten" wie z.B.
Quelltext
1: 2:
| SOLL = 3.14159265358979323846264338327950288419716939937510582097494459 IST = , E+00 |
jaenicke - Di 11.05.21 23:06
Andreas_500 hat folgendes geschrieben : |
Hallo Sebastian,
Delphi XE5 kann von den beigefügten Originalbeispielen leider kein einziges kompilieren, weil etliche neue Units fehlen wie z.B.
System.Messaging,
System.Math.Vectors
FMX.Controls.Presentation,
FMX.EditBox,
FMX.SpinBox |
Das habe ich auch gesehen, dass da unnötige Units drin sind. Wirf die doch einfach raus...
System.Messaging gibt es erst ab XE6, da musst du die Benachrichtigung rauswerfen. Mach einfach ein ShowMessage draus oder so.
Dann noch die Inline-Variablen durch normale Variablen ersetzt, viel mehr sollte nicht zu ändern sein.
Soweit ich das im Assemblercode nach Auswertung der Register usw. sehe bekommt die Funktion auch bei XE6 die korrekten Parameter mit. Diese haben auch die gleiche Größe und im Speicher den gleichen Inhalt. Das hat also meiner Meinung nach nichts mit Problemen mit Aufrufkonventionen oder ähnlichem zu tun.
Um das zu verifizieren müsste der gelinkte Code die Parameter an _qd_div am Anfang der Funktion lediglich einmal prüfen. Das könnte man dann im Assemblercode aus Delphi heraus verfolgen und so sehen, ob die gelesenen Werte übereinstimmen.
Ich habe leider nicht die Zeit mir das genauer anzuschauen...
Andreas_500 - Mi 12.05.21 15:09
Sebastian, ich bitte Dich noch um einen winzigen Test, bevor ich ganz aufgebe. Allerdings glaube ich nach vielen Tests der Fehlerquelle etwas näher gekommen zu sein: Die implizite Typumwandlung von
String zu QuadDouble ist wahrscheinlich fehlerhaft.
Bitte in obiges Testprogramm Folgendes einfügen und ausprobieren:
Delphi-Quelltext
1: 2: 3:
| VAR D: DoubleDouble; Q: QuadDouble; |
und im Hauptprogramm:
Delphi-Quelltext
1: 2:
| D := '1.1111'; Q := '2.2222'; |
Könntest Du bitte im Debugger den Inhalt der Variablen
D und
Q inspizieren?
Ich erhalte für
D = (1,1111, 2,34479102800833e-17) --->
korrekt
und
Q = (-NAN, -NAN, -NAN, -NAN) --->
FALSCH!
Der überladene Klassen-Operator:
Delphi-Quelltext
1:
| class operator Implicit(const S: String): DoubleDouble; inline; static; |
funktioniert bei mir für den Datentyp DoubleDouble,
aber
nicht für QuadDouble:
class operator Implicit(const S: String): QuadDouble; inline; static; -->
fehlerhaft???
Vielen Dank!
Gruß, Andreas
[edit]:
Einer der im Klassen-Operator verwendeten
Init-Aufrufe scheint fehlerhaft zu sein.
jaenicke - Mi 12.05.21 21:57
Der Fehler passiert in _qd_mul_qd_d, das in Init verwendet wird. Bei XE6 knallt es sogar.
Ohne Debugging der object files wird das nix werden. Dafür habe ich leider keine Zeit.
Andreas_500 - Mi 12.05.21 23:10
Nur noch eine letzte Frage, Sebastian!
Stürzt es auch nach Kompilieren mit Deinem neuen Delphi Delphi 10.4 auch ab? Ein "Ja" oder "Nein" genügt.
Im Falle von "Ja" schreibe ich an Erik, denn dann ist es sein Job, den Fehler zu beheben. Wenn "Nein", dann lasse ich das ganze sein und arbeite weiterhin mit Gammatester’s MPA-Bibliotheken.
Vielen Dank für Deine Hilfe!
Gruß, Andreas
jaenicke - Do 13.05.21 12:09
In Delphi 10.4 läuft es ohne Fehlermeldungen. Ich erhalte (11111, 0) und (22222, 0, 0, 0). Dein Ergebnis bekomme ich nur bei:
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| VAR D: DoubleDouble; Q: QuadDouble; begin D := '1,1111'; Q := '2,2222'; |
Denn mein Dezimalseparator ist auf Komma eingestellt.
Andreas_500 - Do 13.05.21 14:10
Delphi-Quelltext
1: 2:
| D = (11111, 0) und Q = (22222, 0, 0, 0) |
Das wäre das korrekte Ergebnis für den Input als Integerwerte: Also für den Fall von
Delphi-Quelltext
1: 2:
| D:= '11111'; Q:= '22222'; |
[edit]:
Anscheinend nimmt Erik's Programm
keine Rücksicht auf die Einstellung des Dezimalseparators.
Andreas_500 - Do 13.05.21 15:03
Habe ich Dich richtig verstanden Sebastian?
Zitat: |
Dein Ergebnis bekomme ich nur bei: ... |
d.h. auch Du bekommst mit Deinem neuesten Delphi
Delphi-Quelltext
1: 2: 3:
| D = (1,1111, 2,34479102800833e-17) ---> korrekt und Q = (-NAN, -NAN, -NAN, -NAN) ---> FALSCH! |
für
Q das falsche Ergebnis?
jaenicke - Do 13.05.21 19:06
Ich habe doch geschrieben was ich bekomme. Dadurch, dass du (für deutschsprachige Regionen) das Tausendertrennzeichen in deinem Beispiel hattest, habe ich das Ergebnis bekommen, das ich geschrieben habe:
jaenicke hat folgendes geschrieben : |
In Delphi 10.4 läuft es ohne Fehlermeldungen. Ich erhalte (11111, 0) und (22222, 0, 0, 0). |
Wenn ich stattdessen den Dezimalseparator eintrage, bekomme ich das Ergebnis wie du es geschrieben hast.
// EDIT:
Ach so, das meinst du. Ich meine das korrekte: D = (1,1111, 2,34479102800833e-17)
Andreas_500 - Do 13.05.21 19:56
Mir geht es im Moment nur darum, ob auch Du mit Deinem neuesten Delphi das falsche Ergebnis für
Q vom Type
QuadDouble bekommst
Quelltext
1:
| Q = (-NAN, -NAN, -NAN, -NAN) ---> FALSCH! |
wenn das auch bei Dir so ist, dann ist am falschen Ergebnis nicht mein altes Delphi schuld, sondern Erik's Programm hat noch Fehler.
Und erst dann kann ich Erik bitten, den Fehler zu korrigieren.
Andreas
[edit] Es geht um die
Zahl mit Nachkommastellen.
jaenicke - Fr 14.05.21 13:21
Nein, da ist es korrekt.
Andreas_500 - Fr 14.05.21 14:44
Danke, Sebastian!
Zusammenfassend läßt sich sagen: Der beobachtete Fehler kommt von wirklich älteren Delphi-Versionen (bis vor XE6 und evtl. mehr?), im aktuellen Delphi (10.3 - 10.4 und evtl. etwas davor) ist das frühere Fehlverhalten nicht mehr vorhanden.
In diesem Fall muß ich leider kapitulieren, denn ich habe keine Chance, den genauen Fehler in Delphi zu finden und zu beheben.
Fazit für mich
Da ich seit rund 5 Jahren die MPA-Bibliotheken von Gammtester erfolgreich verwende – auch wenn ihre Benutzung etwas umständlicher ist als die von Neslib.MultiPrecision.pas – bin ich mit diesen recht zufrieden, da sie sehr zuverlässig sind und die allermeisten noch im Einsatz befindlichen Compiler-Versionen von Turbo Pascal for Windows 1. über Delphi 10.2 Tokyo, Virtual Pascal, Free Pascal und Lazarus voll unterstützen.
Vielen-vielen Dank für Deine unermüdliche Hilfe und Unterstützung, Sebastian!
Gruß, Andreas
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2024 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!