![]() |
tan() von Single, Double, etc.
Hallo zusammen,
zu der tan() Funktion ist klar das es Polstellen bei 90° und Vielfachen davon gibt. Was mir nicht klar ist warum das die tan() Funktion nicht korrekt abbildet.
Delphi-Quellcode:
Wenn ich das so schreibe erwarte ich als Ergebnis eigentlich Single.NaN.
LSingle := 90.0;
Result := tan ( DegToRag( LSingle )); Stattdessen bekomme ich einen hohen, auch noch negativen Wert, der je nach Single, Double, unterschiedlich ausfällt. Ist klar warum, weil Pi eben irrational ist, und irgendwo gekürzt ist, aber sollte das nicht abgefangen sein. Ich schreibe das eigentich singemäß so um, damit es den Erwartungen entspricht:
Delphi-Quellcode:
Ist aber von der Effektivität vielleicht nicht optimal.
function MyTan(const ADeg : Single) : Single;
begin Result := DegToRad( ADeg); if Abs(cos(Result)) > Single.Epsilon) then Result := sin(Result) / cos(Result) else Result := Single.NaN; end; Eigentlich frage ich mich wozu ich die tan() Funktion brauchen soll, wenn ich die nicht zuverlässig nutzen kann. Ist das ein Bug in den tan() Funktionen oder denke ich nur zu kompliziert ? Meiner Meinung nach kann man tan() nur nutzen wenn 90° und deren Periodische im Aufruf ausgeklammert werden, und das MUSS immer im Aufrufer erfolgen. Hat irgendjemand die Tan() Funktion so im Einsatz, und wie sollte man am Besten die Polstellen abfangen ? Meinen Ansatz oben sehe ich als Vorteil weil ich das in der Funktion abgefangen haben, und ich am Ergebnis abfragen kann. So kann sich zumindest ein Fehler nur schwer einschleichen. Wenn ich das aussen im Aufrufer mache muss ich die Periodizität im Aufrufer beachten mit "mod" o.ä., und es könnten sich 1. bei jeder Nutzung Fehler einschleichen, wenn man die Absicherung mal vergisst, was ich für suboptimal halte. 2. unnötig ineffiziente Abfragen bei jedem Aufrufer einbauen Eine Option die mir als Lösung sehr sympatisch erscheint wäre ein TryTan(), so in der Art:
Delphi-Quellcode:
(Ich mag alle diese Try... Funktionen, auch wenn das vielleicht unter den Puristen verpönt ist,
function TryTan(const ADeg : Single; var ATan : Single) : Boolean;
begin ATan := DegToRad( ADeg); if Abs(cos(ATan)) > Single.Epsilon) then begin ATan := sin(ATan) / cos(ATan); Result := True; end else begin ATan := Single.NaN; Result := False; end; end; denn so kann ich immer entsprechend bequem im Aufrufer drauf reagieren, muss es aber nicht) :stupid: Wie haltet Ihr das mit dem tangens, würde mich mal interessieren ? Rollo |
AW: tan() von Single, Double, etc.
Reicht es nicht, wenn du das Tan-Ergebnis auf größer 1 und kleiner minus 1 prüfst?
Delphi-Quellcode:
program Project3;
{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Math, Types; procedure Main; var I: Integer; Degree, Rad, TanResult: Single; LogMsg: string; DegArray: TArray<Integer>; begin DegArray := [0, 45, 90, 135, 180, 225, 270, 315, 360]; for I in DegArray do begin Degree := I; Rad := DegToRad(Degree); TanResult := Tan(Rad); if InRange(TanResult, -1.00001, 1.00001) then begin LogMsg := Format('Grad: %3.d - Rad: %2.4f - Tan: %2.4f', [I, Rad, TanResult]); end else LogMsg := Format('Ungültige Eingabe für Tan(%3.d) - Rad: %2.4f - Tan: %2.4f', [I, Rad, TanResult]) ; Writeln(LogMsg); end; end; begin try Main; Readln except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end. |
AW: tan() von Single, Double, etc.
Zitat:
|
AW: tan() von Single, Double, etc.
Ich nehme mal stark an, dass man sich dafür entschieden hat den Performance-Weg zu gehen, und sich einfach auf das Ergebnis verlässt das die CPU ausspuckt ohne zusätzliche Prüfungen zu machen. Das halte ich prinzipiell auch für den richtigen Weg bei einem Compiler, da gerade solche Funktionen häufig in zeitkritischen Abläufen genutzt werden. Wer es 100% mathematisch wasserdicht braucht, kann ja wie du es gemacht hast entsprechend darauf aufsetzen. (Ich gebe aber zu, dass es schön wäre wenn sowas dokumentiert würde.)
|
AW: tan() von Single, Double, etc.
Hallo TiGü,
Delphi-Quellcode:
wäre auch eine Alternative, aber du meinst sicher InRage innehalb gewisser Grenzen.
if InRange(TanResult, -1.00001, 1.00001) then
Damit könnte man den gültigen Bereich vorgeben, z.b. +/- 1000000. Hätte auch seinen Charme, vielleicht auch um herauszufinden von welcher Seite man kommt. Obwohl ich noch nicht genau den Nutzen/Anwendung sehe. Trotzdem denke ich das eigentlich der Wert selbst bei 90° einfach mathematisch undefiniert ist, und dafür wäre der richtige Wert Single.NaN oder besser Single.Infinity. Weil es aber Infinity nur als PositiveInfinity und NegativeInfinity gibt fände ich NaN an der Stelle korrekter. Jeder andere Wert wäre doch "falsch". Rollo |
AW: tan() von Single, Double, etc.
Mal ein paar Gedanken von mir dazu. Ich denke nicht, dass man das als "Bug" beizeichnen kann. Ich nehme an, der Grund, warum der 90°-Fall nicht abgefangen wird, ist, dass die Ergebnisse schon in der Nähe von 90° unbrauchbar (numerisch instabil) werden. Dem muss man sich als Aufrufer einfach bewusst sein. Es führt kein Weg daran vorbei. Es wäre willkürlich, nur den 90°-Fall abzufangen, weil es nur ein Problem aus einer schier unendlichen Menge von Problemen lösen würde. Abgesehen davon, dass schon an der Stelle gar nicht so klar ist, was eigentlich 90° sind, weil Gleitkommazahlen eben meistens nicht exakt sind. Deswegen hast du ja auch dein Epsilon in deinem Code. Aber da stellt sich die Frage: Wie wählt man Epsilon? Und sollte Epsilon wirklich in der Funktion für alle Verwender hardgecoded sein? Oder ist es nicht besser, das dem Aufrufer zu überlassen, so wie es jetzt gelöst ist? Der kann ja seine eigene Wrapper-Funktion schreiben, die speziell auf seine Bedürfnisse angepasst ist, so wie du es auch gemacht hast.
|
AW: tan() von Single, Double, etc.
Für Single/Double wird tan(DegToRag(x)) nie eine Exception werfen (wenn Du eine findest darfst Du sie behalten und ich geben Dir symbolisch ein Bier aus).
D.h. wenn es Dir auf Geschwindigkeit ankommt, ist die Sache damit erledigt. (Zu mindest für kleine bis mittlere Winkel). Wenn Du es genau haben willst, reduzierst Du modulo 360, und wenn dabei 90 oder 270 herauskommt, lieferst Du Nan oder Infinity. |
AW: tan() von Single, Double, etc.
Zitat:
Da der Parameter der zu NaN führen müsste zwangsweise ein für CPUs üblicher Art nicht darstellbarer Wert ist, ist es zu erwarten und völlig korrekt, dass NaN NICHT als Ergebnis vorkommt. Wenn wir schon genau sein wollen, dann aber auch wirklich ;) |
AW: tan() von Single, Double, etc.
Zitat:
Delphi-Quellcode:
Die Grenze mit 1000 ist natürlich willkürlich. Muss man je nach gewünschter Genauigkeit anpassen.
program Project3;
{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Math, Types; procedure Main; var I: Integer; Degree, Rad, TanResult: Single; LogMsg: string; DegArray: TArray<Integer>; begin for I := 0 to 360 do begin Degree := I; Rad := DegToRad(Degree); TanResult := Tan(Rad); if InRange(TanResult, -1000, 1000) then begin LogMsg := Format('Grad: %3.d - Rad: %2.4f - Tan: %2.4f', [I, Rad, TanResult]); end else LogMsg := Format('Ungültige Eingabe für Tan(%3.d) - Rad: %2.4f - Tan: %2.4f', [I, Rad, TanResult]) ; Writeln(LogMsg); end; end; begin try Main; Readln except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end. Der Windows-Taschenrechner liefert bspw. für tan(90.001°) = -57295,7795... |
AW: tan() von Single, Double, etc.
@Namenloser
Zitat:
Wie du schon sagts, es würde auch das Epsilon fehlen, das muss sich selbst der Aufrufer noch erraten (oder ertesten). In der Praxis lege ich auch ein Epsilon fest, und selbst dabei kommt man in logische Probleme. Habe nämlich gerade des Epsilon von 1 nano auf 1 micro hochgesetzt, und selbst das ist bei bestimmten Werten nicht genug wenn man z.B. millimeter pro meter braucht können große Aubweichungen Auftreten. Deshalb ist der Vorschlag von TiGü ja interessant, weil ich den Bereich nicht im Eingang sondern im Ausgang festlegen kann. @gammatester Zitat:
Wahrscheinelich wäre eine Exception sogar sinnvoller als falsche Werte. Das Ganze hat für mich den Geschmack von "irgendeinen Tod muss man sterben". Rollo |
AW: tan() von Single, Double, etc.
Zitat:
Zitat:
Zitat:
|
AW: tan() von Single, Double, etc.
Das mit nachträglichen Korrektur ist doch Bastelei, und jemandem, dem es auf Genauigkeit ankommt, unwürdig. Selbst bei einem Bereich von -1000..1000 wäre dann tan(89.95°) ungültig. Und ich würde mit Recht darauf hinweisen, daß das ein Bug ist.
Wie gesagt, die Gleitkomma-Arithmetik ist exakt, genau so exakt wie Integer-Arithmetik. Sorgen mach mir der schlampige Umgang von EMBA z.B. mit der Bereichsreduktion für sin, cos etc. Für Beispiele im Vergleich zu ![]()
Code:
(Machine eps for double = 2.220446049250E-016)
----------------------------------------------------------------- Test of DAMath.cos at 10000 random values in [-10.0000000000 .. 10.0000000000] RMS = 0.25, max rel = 0.91 eps at x(dbl) = -8.11413601040840E+000 = $C0203A7009000000 y(dbl) = -2.57229736666779E-001 = $BFD07673B6A2B86F y(mpf) = -2.57229736666778837208918104020787799191734279958E-1 Test of DAMath.cos at 10000 random values in [0.0000000000 .. 1000000000.0000000000] RMS = 0.24, max rel = 0.90 eps at x(dbl) = 3.09463918209076E+008 = $41B2720B6E358600 y(dbl) = -2.58174239840314E-001 = $BFD085ED3F3229E9 y(mpf) = -2.58174239840313841975953009564740744055663588700E-1 Test of DAMath.cos at 10000 random values in [1000000000.0000000000 .. 5.000000000000E+018] RMS = 0.24, max rel = 0.88 eps at x(dbl) = 6.18510209900251E+017 = $43A12AC7848E1D0B y(dbl) = 2.60040880178592E-001 = $3FD0A48280FF5DF3 y(mpf) = +2.60040880178592397382630269801769340226378199265E-1 Test of system.cos at 10000 random values in [-10.0000000000 .. 10.0000000000] RMS = 0.19, max rel = 0.55 eps at x(dbl) = 7.85423278808594E+000 = $401F6ABC00000000 y(dbl) = -2.51154108814005E-004 = $BF3075AAAF01351D y(mpf) = -2.51154108814004449238616298892359426342137631612E-4 Test of system.cos at 10000 random values in [0.0000000000 .. 1000000000.0000000000] RMS = 257703.83, max rel = 14362143.15 eps at x(dbl) = 7.73584246635437E+008 = $41C70DFABB515600 y(dbl) = 3.12296110038815E-004 = $3F347775944C00FF y(mpf) = +3.12296109042891252641009359676336492980461957722E-4 Test of system.cos at 10000 random values in [1000000000.0000000000 .. 5.000000000000E+018] RMS = 931290736633651.00, max rel = 60109746020807300.00 eps at x(dbl) = 2.46350079825587E+018 = $43C1180E763FF750 y(dbl) = 2.93392152252295E-003 = $3F6808E11F9FE142 y(mpf) = -2.37621355417792405078768037054894663841091297810E-4 |
AW: tan() von Single, Double, etc.
Zitat:
Für komplexe mathematische Geschichten würde ich auf eine externe Bibliothek setzen. Ob man wirklich den tan-Wert für bspw. 89.9999° braucht sei mal dahingestellt. Die Genauigkeit, bis wann man welche Eingangswerte behandelt muss man sich vorher überlegen. Was für Werte hast du denn? Willst du irgendwas darstellen?
Delphi-Quellcode:
const
FAKTOR = 10000; procedure Main; var I: Integer; Degree, Rad, TanResult: Double; LogMsg: string; DegArray: TArray<Integer>; begin for I := 89 * FAKTOR to 90 * FAKTOR do begin Degree := I / FAKTOR; Rad := DegToRad(Degree); TanResult := Tan(Rad); LogMsg := ''; if InRange(TanResult, -500000, 500000) then begin LogMsg := Format('Grad: %2.4f - Tan: %2.4f', [Degree, TanResult]); end else LogMsg := Format('Ungültige Eingabe für Tan(%2.4f) - Tan: %2.4f', [Degree, TanResult]) ; if LogMsg <> '' then Writeln(LogMsg); end; end; |
AW: tan() von Single, Double, etc.
@gammatester: Ich verwende derzeit in meiner SVG-Unit die trigonometrischen Funktionen von Delphi. Genutzt werden ausschließlich Extended-Werte. Die Genauigkeit überzeugt mich nicht. Verbessert AMath (nicht DAMath) desselben Autors diese Genauigkeit merklich?
|
AW: tan() von Single, Double, etc.
Zitat:
Und NEIN, du kannst 90° usw. niemals 100%ig korrekt darstellen, es sei denn du hast unendlich breite Register, ooooder eben eine Lib die bei entsprechenden Parametern gesondert vorgeht, und/oder andere Algorithmen verwendet, die sich nicht auf die integrierten Funktionen der CPU verlassen. (Die sind dann natürlich etwas langsamer in aller Regel.) |
AW: tan() von Single, Double, etc.
Zitat:
Zitat:
Zitat:
![]() |
AW: tan() von Single, Double, etc.
Zitat:
![]() |
AW: tan() von Single, Double, etc.
Zitat:
|
AW: tan() von Single, Double, etc.
Zitat:
Delphi-Quellcode:
Funktion mit Argumenten in ° ja auch nicht programmiert werden (tand ist eine de-facto Standardbezeichnung, sie zB
tand(x)
![]() ![]() ![]() ![]() Zuerst wird x modulo 360 reduziert, dann werden exakte Werte 0,+1,Inf,-1 je nach Vielfachen von 45 zurückgeliefert, der Rest (bei geschickter Reduktion ist der im Bereich -45 < x < 45) wird halt mit Pi/180 multipliziert und dann mit
Delphi-Quellcode:
weiter verarbeitet. In diesem Bereich sind selbst die Delphi-Funkionen genau genug (und es gibt keinen Pol/Infinity).
math.tan
|
AW: tan() von Single, Double, etc.
Zitat:
Kleine Anmerkung am Rande: Die Delphi Tan-Funktion wird je nach Zielplattform auf eine eigene Implementierung namens Tangent in System.pas weitergeleitet. Potentiell kommen da auf unterschiedlichen Plattformen und Prozessoren auch unterschiedliche Werte raus. |
AW: tan() von Single, Double, etc.
Ich bewege mich auf dünnem Eis, weil ich ähnliches vor Jahren mal hatte + nicht weiß, ob es das in der Form jetzt immer noch gibt.
Das Problem damals war, dass das Verhalten des mathematische 8087-Coprozessors maskiert werden konnte und dann warf er bestimmte Exceptions oder eben auch nicht. Im konkreten Fall wurde eine Division durch 0 durch einen Druckertreiber maskiert, dh ohne Drucken wurde die Exception geworfen, nach dem Drucken wurde sie nicht mehr geworfen. :-/ War cool zum Finden. Vielleicht gibt es für die mathematischen Routinen was ähnliches. |
AW: tan() von Single, Double, etc.
Delphi-Quellcode:
Wie gesagt, die Gleitkomma-Arithmetik ist exakt, genau so exakt wie Integer-Arithmetik.
Was sollte denn die tan(90°) (Eingabe als Radian, natürlich Uwe) deiner Meinung nach zurückgeben ? Der richtige Wert wäre vielleicht MAX_SINGLE als größtmögliche Näherung an Infinity ? Obwohl ich bei tan(90°) nicht meine das es überhaupt + oder - Infinity ist, sondern einfach eine undefinierte Polstelle (Kann mich auch irren. bin jetzt kein Zahlentheoretiker). Jedenfalls liefert tan(90°) bei mir irgendwas mit -235343.. zurück, also eine konkrete Zahl die unter dem möglichen Max_Single liegt und relativ "zufällig" ist. Wie Uwe schon geschrieben hat, diese Zahl ist womöglich von System zu System verschieden. Bei meiner Anwendung geht es jetzt nicht unbedingt um hochpräzise Mathematik für Wetterberechnungen o.ä., sondern einfach um den praktischen Umgang mit der tan() Funktion die in der Library vorhanden ist. Klar kann man das Alles je nach Aufruf und Anwendung gesondert abfangen, aber um das möglichst einfache und sichere Abfangen geht es mir ja. Ich denke schon dass das einfache Weiterrechnen mit dem was tan() mir zurückgibt keine Option sein kann. Was spräche denn gegen NaN, das ist doch in der Praxis ein klarer Marker für das Resultat das man den Wert nicht verwenden kann/darf ? Für solche Fälle ist NaN ja wohl auch vorgesehen worden. Rollo |
AW: tan() von Single, Double, etc.
Zitat:
Delphi-Quellcode:
// Eingangswertebereich: -360 bis 360 Grad als Ganzzahl
// Wer das bis ins plus minus Unendliche haben möchte, soll sich das doch selber schnitzen function tand(const X: Integer): Double; var Rad: Double; begin if (Abs(X) = 90) or (Abs(X) = 270) then begin Result := System.Math.NaN; end else begin Rad := DegToRad(X); Result := System.Math.Tan(Rad); end; end; |
AW: tan() von Single, Double, etc.
Und für diejenigen, die nicht ohne Nachkommastellen auskommen können:
Delphi-Quellcode:
// Eingangswertebereich: -360.00 bis 360.00 Grad als FLießkommazahl
// Wer das bis ins plus minus Unendliche haben möchte, soll sich das doch selber stricken function tand(const X: Double; const Epsilon: Double = 0): Double; var Rad: Double; begin if SameValue(Abs(X), 90.0, Epsilon) or SameValue(Abs(X), 270.0, Epsilon) then begin Result := System.Math.NaN; end else begin Rad := DegToRad(X); Result := System.Math.Tan(Rad); end; end; |
AW: tan() von Single, Double, etc.
Zitat:
Delphi-Quellcode:
Die Ausgabe ist dann
{Test/dev program for tand (c) W.Ehrhardt 2017}
program t_tand; {$apptype console} uses math; {Vereinfachte Implementation einer tand-Funktion, d.h. Tangens mit } {Argument in Grad. Achtung: nur bis ca 10^13 = 2^53/180 genau, weil} {ich nicht noch mehr DAMath-Funktioen einbauen wollte. Compilierbar} {ab Delphi 6} {---------------------------------------------------------------------------} function floord(x: double): double; {-Return the largest integer <= x} var t: double; begin t := int(x); if (x>=0.0) or (x=t) then floord := t else floord := t - 1.0; end; {---------------------------------------------------------------------------} procedure trig_deg(x: double; var y,z: double; var n: integer; var m45: boolean); {-Reduce x in degrees mod 90; y=x mod 90, |y|<45. z=x/45, m45 if x is multiple of 45} const c45: single = 45.0; begin {Basic internal reduction routine mod 90. Use Cody/Waite logic, but no} {pseudo-multiprecision because 45.0 has only 6 non-zero mantissa bits.} if x=0.0 then begin y := 0.0; z := 0.0; n := 0; m45 := true; end else begin z := x/c45; m45 := (frac(z)=0.0) and (frac(x)=0.0); y := floord(z); n := trunc(y - 16.0*floord(y/16.0)); if odd(n) then begin inc(n); y := y + 1.0; end; n := (n shr 1) and 7; y := x - y*c45; end; end; {---------------------------------------------------------------------------} function tand(x: double): double; {-Return tan(x), x in degrees} var y,z: double; n : integer; m45: boolean; begin trig_deg(x,y,z,n,m45); if m45 then begin z := abs(z); y := sign(x); case round(4.0*frac(0.25*z)) of 0: tand := 0.0; 1: tand := y; 2: tand := Infinity; else tand := -y; end; end else begin z := DegToRad(y); if odd(n) then tand := -cot(z) else tand := tan(z); end; end; var d: integer; begin d := 0; while d<=360 do begin writeln(d:5, tand(d):25:16); d := d + 15; end; end.
Code:
Im getesteten Bereich -10^10 .. 10^10 ist die Genauigkeit ist die gleiche wie bei DAMath
0 0.0000000000000000
15 0.2679491924311228 30 0.5773502691896258 45 1.0000000000000000 60 1.7320508075688770 75 3.7320508075688768 90 +Inf 105 -3.7320508075688768 120 -1.7320508075688770 135 -1.0000000000000000 150 -0.5773502691896258 165 -0.2679491924311228 180 0.0000000000000000 195 0.2679491924311228 210 0.5773502691896258 225 1.0000000000000000 240 1.7320508075688770 255 3.7320508075688768 270 +Inf 285 -3.7320508075688768 300 -1.7320508075688770 315 -1.0000000000000000 330 -0.5773502691896258 345 -0.2679491924311228 360 0.0000000000000000
Code:
Test DAMath V0.95 with MP_Arith V1.37.01 (31/32 bit) (c) 2013-2017 W.Ehrhardt
Karatsuba cutoffs: mul/sqr = 16/32 Toom-3, BZ cutoffs: mul/sqr = 32/64, div = 32 Current mp_float default bit precision = 160, decimal precision = 48.2 Machine eps for double = 2.22044604925E-0016 ----------------------------------------------------------------- Test of DAMath.tand at 10000 random values in [-10000000000.0000000000 .. 10000000000.0000000000] RMS = 0.38, max rel = 1.25 eps at x(dbl) = -4.52875830026280448E+0009 = $C1F0DEF5E1C43473 y(dbl) = -1.75054511612172736E+0000 = $BFFC023B987EA53D y(mpf) = -1.75054511612172687511491287528577674916550040814 ----------------------------------------------------------------- Test of tand (Delphi Praxis) at 10000 random values in [-10000000000.0000000000 .. 10000000000.0000000000] RMS = 0.38, max rel = 1.25 eps at x(dbl) = -4.52875830026280448E+0009 = $C1F0DEF5E1C43473 y(dbl) = -1.75054511612172736E+0000 = $BFFC023B987EA53D y(mpf) = -1.75054511612172687511491287528577674916550040814 |
AW: tan() von Single, Double, etc.
Zitat:
|
AW: tan() von Single, Double, etc.
Dankesehr für die interessanten Ansätze.
Also liege ich mit meinem Ansatz NaN o.ä. nicht ganz falsch. Das muss man aber wohl immer je nach Aufgabe abwägen, für mal schnell hier und da tan() benutzen ist das OK für mich. @gammatester DAMath und ähnliche werde ich mir mal in einer ruhigen Minute genauer ansehen, da steckt ja eine ganze Menge Gehirnschmalz drin. Tolle Libraries :thumb: Was mich da einbischen wundert ist das du direkt auf 0.0 vergleichst, z.B.
Delphi-Quellcode:
Ich versuche so etwas eigentlich immer das mit Epsilon abzufangen, speziell dann wenn die Eingaben aus anderen Rechen-Ergebnissen kommen.
if x=0.0 then begin
Wann ist das Epsilon nötig, und wann nicht ? Vielleicht bin ich wieder zu übervorsichtig und kann etwas einsparen :stupid: P.S.: Noch eine kleine Frage an die Zahlentheoretiker hier: Ist denn +Infinity richtig gewählt, die tan Funktion kann ![]() Läuft die Näherung von links und rechts denn immer auf +Infinity raus ) ? Rollo |
AW: tan() von Single, Double, etc.
Zitat:
In der Regel sagt man sin(x) / cos(x)! Bei 90° haben wir sin(90°) / cos(90°) = 1 / 0. Irgendetwas durch irgendetwas unendlich kleines ist halt abnormal übergroß bzw. unendlich. Ob unendlich positiv oder negativ hängt von den Vorzeichen von Nenner und Zähler ab. Vereinfacht: Positiv unendlich bei 90° und minus unendlich bei 270° (da 1 / 0 und -1 / 0). |
AW: tan() von Single, Double, etc.
Zitat:
Zu Epsilon bzw zur Math-Funktion iszero. Ebenson wie tan(x) ~ x gilt zB sinh(x) ~ x und arcsinh(x) ~ x, aber was liefert Delphi: Für Delphi 6 ist wenigsten der sinh-Wert korrekt
Code:
Ab Delphi 7 siehts dann so aus
sinh(1e-20) = 1e-20
arcsinh(1e-20) = 0
Code:
Ich benutzte diese epsilons, wenn ich zB vergleichen will ob kleine Änderungen vorliegen, d.h. ob x+delta ~ x ist, und dann als Test in der Form
sinh(1e-20) = 0
arcsinh(1e-20) = 0
Delphi-Quellcode:
.
if abs(delta)<= epsilon*abs(x)
Edit: Sinnvoll sind die Epsilons sind auch bei irrationalen Nullstellen von Funktionen, zB eben beim Tangens die Nullstellen pi/2 + n*Pi des Cosinus. |
AW: tan() von Single, Double, etc.
Eine sehr interessante Diskussion hier.
Habe gerade mal geschaut wie das bei uns gelöst ist. Mal davon abgesehen das wir Winkelfunktionen so weit es geht vermeiden.. Wir haben einen Static Record Tdeg die bei uns für Ermittlung der Tan, etc zuständig ist. Spasseshalber habe ich mal den Test erweitert Tand: Gammatester Tdeg: unsere Routine Tan aus der system.math Ergebnisse:
Code:
Damit kann ich leben denke ich :-)
Tand : 0 0.0000000000000000
Tdeg : 0 0.0000000000000000 Tan : 0 0.0000000000000000 Tand : 15 0.2679491924311228 Tdeg : 15 0.2679491924311228 Tan : 15 0.2679491937160492 Tand : 30 0.5773502691896258 Tdeg : 30 0.5773502691896258 Tan : 30 0.5773502588272095 Tand : 45 1.0000000000000000 Tdeg : 45 1.0000000000000000 Tan : 45 1.0000000000000000 Tand : 60 1.7320508075688770 Tdeg : 60 1.7320508075688779 Tan : 60 1.7320508956909180 Tand : 75 3.7320508075688768 Tdeg : 75 3.7320508075688776 Tan : 75 3.7320504188537598 Tand : 90 +Inf Tdeg : 90 +Inf Tan : 90-22877332.0000000000000000 Tand : 105 -3.7320508075688768 Tdeg : 105 -3.7320508075688763 Tan : 105 -3.7320508956909180 Tand : 120 -1.7320508075688770 Tdeg : 120 -1.7320508075688763 Tan : 120 -1.7320505380630493 Tand : 135 -1.0000000000000000 Tdeg : 135 -1.0000000000000000 Tan : 135 -1.0000000000000000 Tand : 150 -0.5773502691896258 Tdeg : 150 -0.5773502691896256 Tan : 150 -0.5773503184318542 Tand : 165 -0.2679491924311228 Tdeg : 165 -0.2679491924311225 Tan : 165 -0.2679493129253387 Tand : 180 0.0000000000000000 Tdeg : 180 0.0000000000000000 Tan : 180 0.0000000874227766 Tand : 195 0.2679491924311228 Tdeg : 195 0.2679491924311228 Tan : 195 0.2679492235183716 Tand : 210 0.5773502691896258 Tdeg : 210 0.5773502691896260 Tan : 210 0.5773502588272095 Tand : 225 1.0000000000000000 Tdeg : 225 1.0000000000000000 Tan : 225 0.9999998807907104 Tand : 240 1.7320508075688770 Tdeg : 240 1.7320508075688790 Tan : 240 1.7320512533187866 Tand : 255 3.7320508075688768 Tdeg : 255 3.7320508075688794 Tan : 255 3.7320518493652344 Tand : 270 +Inf Tdeg : 270 +Inf Tan : 270-83858280.0000000000000000 Tand : 285 -3.7320508075688768 Tdeg : 285 -3.7320508075688719 Tan : 285 -3.7320513725280762 Tand : 300 -1.7320508075688770 Tdeg : 300 -1.7320508075688768 Tan : 300 -1.7320511341094971 Tand : 315 -1.0000000000000000 Tdeg : 315 1.0000000000000000 Tan : 315 -1.0000002384185791 Tand : 330 -0.5773502691896258 Tdeg : 330 -0.5773502691896253 Tan : 330 -0.5773505568504334 Tand : 345 -0.2679491924311228 Tdeg : 345 -0.2679491924311226 Tan : 345 -0.2679489552974701 Tand : 360 0.0000000000000000 Tdeg : 360 0.0000000000000000 Tan : 360 0.0000001748455531 |
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 12:32 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