![]() |
AW: tan() von Single, Double, etc.
Zitat:
Delphi-Quellcode:
Das liefert für die drei Variablen:
procedure TForm1.Button1Click(Sender: TObject);
var s: Single; d: Double; e: Extended; begin s := DegToRad(90); d := DegToRad(90); e := DegToRad(90); Edit1.Text := FloatToStrF(s, ffFixed, 30, 30); Edit2.Text := FloatToStrF(d, ffFixed, 30, 30); Edit3.Text := FloatToStrF(e, ffFixed, 30, 30); end; s = 1,570796370506286620 d = 1,570796326794896560 e = 1,570796326794896620 pi/2 wäre nach ![]() Die Differenz von dieser Näherung zu pi/2 ist für s: -0.000000043711390000768678308360248557901415300312 4471 für d: 0.000000000000000059231321691639751442098584699687 553 für e: -0.000000000000000000768678308360248557901415300312 4471 Derselbe Rechner liefert für tan(s) = -22877332.42942889836981039204072541441251573432 tan(d) = 16882959411340397.91436507298177193134 tan(e) = -1300934329906107203.0757511956816777 (Auch jeweils auf 50 Stellen genau, etwas was eine CPU von Hause aus nicht kann.) Die Funktion Tan() ist in Delphi (2007) so realisiert:
Delphi-Quellcode:
Es wird, wie zu erwarten ist, einfach die tan() Funktion der CPU (bzw. FPU) genutzt, so wie es auch sein sollte für eine Basisfunktionalität. Und hier wird eben mit binären Gleitkommazahlen und Radian gearbeitet, so dass du niemals auch nur die CHANCE hättest tand(90) mit einem 100%ig exakt richtigen Parameter aufzurufen. Und für die zwingend gerundeten Parameter stimmt das Ergebnis das du siehst. Es wäre falsch hier NaN oder +/-Inf zurückzugeben.
function Tan(const X: Extended): Extended;
{ Tan := Sin(X) / Cos(X) } asm FLD X FPTAN FSTP ST(0) { FPTAN pushes 1.0 after result } FWAIT end; Das ist anders für DEINEN Anwendungsfall, in dem du explizit Funktionen in Grad nutzen willst. Das ist aber ein Sonderwunsch, und NICHT Aufgabe eines Compilers bzw. des Basisumfangs einer Entwicklungsumgebung. Im Gegenteil: Ich wäre ziemlich sauer wenn Emba so ein Monster für ein popeliges tan() als Basis hätte wie weiter oben gezeigt, wenn doch die CPU selbst schon eine solche Funktion bietet die für 99,999% aller Echt-Welt-Fälle Werte liefert die den üblichen Ansprüchen genügen. Wer besondere Anforderungen hat, nutzt eben besondere Libs, die entsprechende Fälle anders behandeln; meist auf Kosten der Performance. Rechnen mit Floats ist immer ungenau, nicht nur bei Trigonometrie. Wenn dich das hier schon stört, müsstest du eigentlich schon weit eher pochende Schläfen bekommen: Es ist nämlich schon manchmal nicht möglich ein exakt eingegebenes Literal in ein Float zu packen:
Delphi-Quellcode:
Ergebnis: 0,009999999776482582
procedure TForm1.Button1Click(Sender: TObject);
var s: Single; begin s := 0.01; Edit1.Text := FloatToStrF(s, ffFixed, 30, 30); end; Hier müsstest du eigentlich schon aufgegeben haben. Aber das ist normal. Das ist überall so bei aktuellen üblichen Computern, und das war es auch schon Jahrzehnte lang. |
AW: tan() von Single, Double, etc.
Zitat:
Das kann man auch in der System.Math so nachlesen (hier nicht relevante Compiler-Direktiven entfernt):
Delphi-Quellcode:
{ The following constants should not be used for comparison, only
assignments. For comparison please use the IsNan and IsInfinity functions provided below. } NaN = 0.0 / 0.0; Infinity = 1.0 / 0.0; NegInfinity = -1.0 / 0.0; |
AW: tan() von Single, Double, etc.
Zitat:
|
AW: tan() von Single, Double, etc.
Zitat:
|
AW: tan() von Single, Double, etc.
Zitat:
|
AW: tan() von Single, Double, etc.
@TiGü
Zitat:
So gesehen macht es natürlich wunderbar Sinn. @Medium Mir ist das Alles schon klar, aber wie das jetzt implementiert ist FPU, GPU oder CPU interessiert mich nur am Rande. Wichtig für mich wäre das es am Ende mathematisch korrekt interpretiert werden kann. Wie du selbst siehst springt auch dein Ergebnis, und die Zahlen sind relativ "random". Zitat:
Ich verstehe ja das Argument das der Fehler sowieso zu klein ist um ein Problem zu sein, ich muss aber diese Werte in verschiedene Einheiten Umrechen, und zurückrechnen (°, %, mm/m, in/ft, ...). Das Problem was ich sehe ist das bei Rechnung und Rückrechnung am Ende ein verschiedene Werte rauskommen. Klar wird der Fehler minimal sein, aber z.B. die Zahlen oben zeigen auch das es +/- springen kann, und das würde bei mir im Ergebnis eine falsche Richtungsanzeige bedeuten. Wenn man die Rechnung vor- und zurück öfters macht käme u.U. ständig wechselnde Werte und Richtungen heraus, was für meinen Fall nicht Optimal wäre. Also müsste ich diesen Fall im Aufruf abfangen und sauber definieren, z.B. durch +/-Infinity, die Methode von gammatester sieht dazu sehr gut für mich aus. Edit: Weiterhin wären die Ergebnisse, obwohl logisch gleich im Vergleich nicht mehr gleich. Auch das kommt als Problem heraus. Ich will auch gar nicht die tan() Funktion anzweifeln, das dies so korrekt ist sehe ich auch so, sondern ich wollte nur mal wissen wie Ihr mit solchen Problemen umgeht. Lediglich das welcher Stelle man das Infinity abfangen sollte wäre interessant. Also bitte wieder locker werden ... Rollo |
AW: tan() von Single, Double, etc.
Zitat:
Das geht aber nicht! Es ist sachlich falsch. Die entstehenden sehr großen positiven und negativen Zahlen sind nicht richtig. Warum wurde auch schon hinreichend dargelegt. Zitat:
Siehe Beitrag #23 und #24 oder nehme eine von den schon genannten Mathematik-Bibliotheken. |
AW: tan() von Single, Double, etc.
Zitat:
So das die Routinen möglichst fehlerfrei damit umgehen können. Deshalb bin ich ja ein Verfechter von +/-Infinity, weil das dem entspricht was ich hier methematisch erwarte. Die tan() Routinen liefern aber stattdessen irgendeinen Wert zurück. Das Thema ist für mich erstmal erledigt, ich schaue mir die Lösungen aus den Units von gammatester mal genauer an, und ich werde wohl +/-Infinity einführen um darauf Testen und Reagieren zu können. Dankesehr für die konstruktiven Vorschläge und Anregungen. Rolf |
AW: tan() von Single, Double, etc.
Zitat:
Delphi-Quellcode:
var
myFloat: Single; // oder Double, Extended { Abfragen } if myFloat.IsPositiveInfinity then... if myFloat.IsNegativeInfinity then... if myFloat.IsInfinity then... // prüft beides { auch } if myFloat.IsNan then... { Zuweisungen, passen automatisch zu der Deklaration von myFloat. Alternative über direkte Typangabe (z.B. Single.NaN) } myFloat := myFloat.NegativeInfinity; myFloat := myFloat.PositiveInfinity; myFloat := myFloat.NaN; |
AW: tan() von Single, Double, etc.
Zitat:
Die Funktion rechnet genau richtig - für den Wert, der nachher im Parameter wirklich steht. Da ist auch nichts "random" dran. Auf derselben CPU wird völlig deterministisch immer derselbe Wert herauskommen. Die Rundungen und Umwandlungen sind klar definiert und dokumentiert (halt nicht in der Emba-Doku sondern in den Specs der jeweiligen CPU). Dein Denkfehler ist, dass du nicht wahrnimmst, dass DegToRad(90) schon zu einem Ergebnis führt, das von dem "echt-Welt"-Wert PI/2 abweicht. Und ohne Mechanismen dies gezielt zu umschiffen ist dies für die meisten Architekturen auch einfach schlicht unmöglich. tan() sollte NIEMALS NaN oder +/-Inf zurückgeben, da es keine Möglichkeit gibt sie mit einem Parameter aufzurufen für den diese Ergebnisse richtig wären. Zitat:
Zitat:
Zitat:
Was du ja jetzt auch tust. Aber ich bin mir noch immer nicht sicher, dass du wirklich verstanden hast, wo der Hund im Ursprung begraben liegt. Zitat:
Zitat:
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:27 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz