Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   tan() von Single, Double, etc. (https://www.delphipraxis.net/194431-tan-von-single-double-etc.html)

Rollo62 20. Nov 2017 09:21

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:
LSingle := 90.0;
Result := tan ( DegToRag( LSingle ));
Wenn ich das so schreibe erwarte ich als Ergebnis eigentlich Single.NaN.
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:
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;
Ist aber von der Effektivität vielleicht nicht optimal.


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:
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;
(Ich mag alle diese Try... Funktionen, auch wenn das vielleicht unter den Puristen verpönt ist,
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

TiGü 20. Nov 2017 10:09

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.

gammatester 20. Nov 2017 10:17

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von TiGü (Beitrag 1386690)
Reicht es nicht, wenn du das Tan-Ergebnis auf größer 1 und kleiner minus 1 prüfst?

Warum sollte tan(60°) = sqrt(3) = 1.732... ungültig sein?

Medium 20. Nov 2017 10:59

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.)

Rollo62 20. Nov 2017 11:12

AW: tan() von Single, Double, etc.
 
Hallo TiGü,

Delphi-Quellcode:
if InRange(TanResult, -1.00001, 1.00001) then
wäre auch eine Alternative, aber du meinst sicher InRage innehalb gewisser Grenzen.
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

Namenloser 20. Nov 2017 11:16

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.

gammatester 20. Nov 2017 11:25

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.

Medium 20. Nov 2017 12:02

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Namenloser (Beitrag 1386695)
Abgesehen davon, dass schon an der Stelle gar nicht so klar ist, was eigentlich 90° sind, weil Gleitkommazahlen eben meistens nicht exakt sind.

Das schoss mir auch gerade durch den Kopf. Ich bin mir ziemlich sicher, dass alleine schon durch die Umwandlung in eine Gleitkommazahl mit anschließendem DegToRad einiges an systembedingten Rundungsfehlern auftreten, sodass tan() hier gar nicht mit exakt pi/2 aufgerufen wird. Exakt geht es ja eh schon nicht, aber ich bezweifel selbst das hier der theoretisch beste gerundete Wert erreicht wird.
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 ;)

TiGü 20. Nov 2017 12:14

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von gammatester (Beitrag 1386691)
Zitat:

Zitat von TiGü (Beitrag 1386690)
Reicht es nicht, wenn du das Tan-Ergebnis auf größer 1 und kleiner minus 1 prüfst?

Warum sollte tan(60°) = sqrt(3) = 1.732... ungültig sein?

Stimmt, ich hätte nicht nur mit den paar Werten mit vielfachen von 45 testen sollen. :oops:

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
  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.
Die Grenze mit 1000 ist natürlich willkürlich. Muss man je nach gewünschter Genauigkeit anpassen.
Der Windows-Taschenrechner liefert bspw. für tan(90.001°) = -57295,7795...

Rollo62 20. Nov 2017 13:06

AW: tan() von Single, Double, etc.
 
@Namenloser

Zitat:

Dem muss man sich als Aufrufer einfach bewusst sein.
Ja, genau dehalb bezeiche ich als "Bug", denn das genaue Verhalten ist ja nicht dokumentiert und bei Single, Double, etc. auch individuell anders.
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:

Für Single/Double wird tan(DegToRag(x)) nie eine Exception werfen
Das mag sein, mir geht es aber um die Richtigkeit der Ergebnisse.
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

Medium 20. Nov 2017 13:23

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Rollo62 (Beitrag 1386709)
Ja, genau dehalb bezeiche ich als "Bug", denn das genaue Verhalten ist ja nicht dokumentiert und bei Single, Double, etc. auch individuell anders.

Genau genommen ist es eigentlich eine wohlbekannte und zwangsweise Folge des Rechnens mit Zahlendarstellungen nach IEEE 754. Braucht man höhere Genauigkeit als diese zusichern, muss man zu entsprechenden Libs greifen die diese Eigenschaften umschiffen.

Zitat:

Das mag sein, mir geht es aber um die Richtigkeit der Ergebnisse.
Nochmal: Die Ergebnisse sind richtig. Die Eingabe in Grad und die Nutzung von binärer Gleitkommaarithmetik verschleiern lediglich die Rundungen die auf dem Weg zum Ergebnis hin notwendigerweise passieren. Wie schon erwähnt wirst du genau genommen (und darum geht es dir ja) es niemals schaffen der tan() Funktion einen Parameter zu übergeben der korrekterweise zu NaN führt, da eben genau diese Parameter schon per Definition eine Wertigkeit haben, die kein x86 Computer der Welt (nativ) korrekt abbilden kann.

Zitat:

Das Ganze hat für mich den Geschmack von "irgendeinen Tod muss man sterben".
Das ist schon durch die Nutzung von binären Gleitkommazahlen abgemachte Sache.

gammatester 20. Nov 2017 14:39

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 DAmath siehe unten. Und selbstverständlich kann man die Polstellen für tan bei 90° + k * 180° sauber darstellen, die Funktion tand in DAMath liefert allerdings Infinity statt NaN.
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

TiGü 20. Nov 2017 15:12

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Rollo62 (Beitrag 1386709)
Das Ganze hat für mich den Geschmack von "irgendeinen Tod muss man sterben".

Die Frage ist halt auch, wie genau man es braucht? Und wofür wird es gebraucht?
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;

Redeemer 20. Nov 2017 17:29

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?

Medium 20. Nov 2017 17:55

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von gammatester (Beitrag 1386718)
daß das ein Bug ist.

Dann ist es ein Bug in jeder x86/x64 CPU der Welt. Denn Delphi macht nichts anderes als deren integrierten trigonometrischen Funktionen ohne viel drumrum direkt zu nutzen. In meinen Augen KEIN Bug, sondern eine systembedingte, bekannte Designschwäche, mit der ALLE Compiler entweder leben müssen, und/oder für gehobene Ansprüche Libs einsetzen, die die Spezialfälle gezielt abdecken.

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.)

gammatester 20. Nov 2017 18:49

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Medium (Beitrag 1386744)
Zitat:

Zitat von gammatester (Beitrag 1386718)
daß das ein Bug ist.


Lies noch einmal genau nach! Ich habe beschrieben:
Zitat:

Zitat von gammatester (Beitrag 1386718)
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.

Und wenn tan(89.95°)=Nan oder Inf oder was auch immer Du als ungültig wählst, kein Bug wäre, was ist dann noch ein Bug?
Zitat:

Zitat von Medium (Beitrag 1386744)
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.

Das ist doch Unsinn, selbstverständlich kann 90 exakt als Double/Single darstellen, und man braucht auch keine 'unendlich breite' Register, um korrekt gerundete Werte für sin, tan etc auszurechnen. Man muß es nur wollen! Für ein frei zugängliches Beispiel zu nennen siehe http://cvsweb.openbsd.org/cgi-bin/cv.../lib/libm/src/, und nicht wie EMBA, das Problem aussitzen. Immerhin haben sie in der 64-Bit-RTL einen Schritt in die richtige Richtung gemacht und eine schon 37 Jahre junge Technik von Cody/Waite übernommen, aber den letzten Schritt woll(t)en sie nicht gehen, aus welchen Gründen auch immer (zumindest im letzten für mich vorliegen Quellcode of Delphi18/XE4).

gammatester 20. Nov 2017 18:59

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Redeemer (Beitrag 1386741)
@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?

Ja, und wenn Du Fragen hast, stehe ich selbstverständlich zur Verfügung. Bei den trigonometrischen extended Funktionen kommt erschwerend hinzu, daß ein viel größerer Bereich bis 2^16384 behandelt werden muss und die Reduktion mod 2*Pi entsprechend aufwendig ist. Für Vergleiche siehe die Datei t_amathx.cmp im Archiv http://www.wolfgang-ehrhardt.de/amath_2017-11-02.zip.

Uwe Raabe 20. Nov 2017 21:28

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von gammatester (Beitrag 1386746)
Das ist doch Unsinn, selbstverständlich kann 90 exakt als Double/Single darstellen,

Dummerweise nimmt die Funktion aber keine Grad an sondern Radians. Daher werden die 90° mit einer nur begrenzt genauen Repräsentation von π multipliziert, was dann im Ergebnis eben nicht genau π/2 entspricht.

gammatester 20. Nov 2017 22:02

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1386751)
Dummerweise nimmt die Funktion aber keine Grad an sondern Radians. Daher werden die 90° mit einer nur begrenzt genauen Repräsentation von π multipliziert, was dann im Ergebnis eben nicht genau π/2 entspricht.

Richtig, aber so sollte eine
Delphi-Quellcode:
tand(x)
Funktion mit Argumenten in ° ja auch nicht programmiert werden (tand ist eine de-facto Standardbezeichnung, sie zB Matlab, Scilab, Octave oder GNU Fortran.

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:
math.tan
weiter verarbeitet. In diesem Bereich sind selbst die Delphi-Funkionen genau genug (und es gibt keinen Pol/Infinity).

Uwe Raabe 20. Nov 2017 23:42

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von gammatester (Beitrag 1386755)
Richtig, aber so sollte eine
Delphi-Quellcode:
tand(x)
Funktion mit Argumenten in ° ja auch nicht programmiert werden

Da zum Delphi-Sprachumfang aber nun mal keine tand-Funktion gehört (Delphi 5 kannte noch nicht einmal tan), kann man sich ja bei Bedarf was Passendes selbst programmieren. Mach doch mal einen Vorschlag so wie du es dir vorstellst.

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.

TigerLilly 21. Nov 2017 07:40

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.

Rollo62 21. Nov 2017 07:57

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

TiGü 21. Nov 2017 08:21

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Rollo62 (Beitrag 1386761)
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.

Was genau hält dich auf?

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;

TiGü 21. Nov 2017 08:38

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;

gammatester 21. Nov 2017 09:49

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1386756)
Zitat:

Zitat von gammatester (Beitrag 1386755)
Richtig, aber so sollte eine
Delphi-Quellcode:
tand(x)
Funktion mit Argumenten in ° ja auch nicht programmiert werden

Da zum Delphi-Sprachumfang aber nun mal keine tand-Funktion gehört (Delphi 5 kannte noch nicht einmal tan), kann man sich ja bei Bedarf was Passendes selbst programmieren. Mach doch mal einen Vorschlag so wie du es dir vorstellst.

Hier eine vereinfachte Implementation meiner DAMath-Funktion komplett mit Test-Programm
Delphi-Quellcode:
{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.
Die Ausgabe ist dann
Code:
    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
Im getesteten Bereich -10^10 .. 10^10 ist die Genauigkeit ist die gleiche wie bei DAMath
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

Uwe Raabe 21. Nov 2017 09:53

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von gammatester (Beitrag 1386770)
Hier eine vereinfachte Implementation meiner DAMath-Funktion komplett mit Test-Programm

:thumb:

Rollo62 21. Nov 2017 10:33

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:
  if x=0.0 then begin
Ich versuche so etwas eigentlich immer das mit Epsilon abzufangen, speziell dann wenn die Eingaben aus anderen Rechen-Ergebnissen kommen.
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 doch springen ?
Läuft die Näherung von links und rechts denn immer auf +Infinity raus ) ?


Rollo

TiGü 21. Nov 2017 10:59

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Rollo62 (Beitrag 1386780)
P.S.: Noch eine kleine Frage an die Zahlentheoretiker hier:
Ist denn +Infinity richtig gewählt, die tan Funktion kann doch springen ?
Läuft die Näherung von links und rechts denn immer auf +Infinity raus ) ?

Wie ist denn der Tangens = tan(x) definiert?
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).

gammatester 21. Nov 2017 11:16

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Rollo62 (Beitrag 1386780)
Was mich da einbischen wundert ist das du direkt auf 0.0 vergleichst, z.B.
Delphi-Quellcode:
  if x=0.0 then begin
Ich versuche so etwas eigentlich immer das mit Epsilon abzufangen, speziell dann wenn die Eingaben aus anderen Rechen-Ergebnissen kommen.
Wann ist das Epsilon nötig, und wann nicht ?
Vielleicht bin ich wieder zu übervorsichtig und kann etwas einsparen

MM ist diese epsilon Sache meist falsch bzw unnötig. Für kleine x ist tan(x) ~ x und 0 ist nun mal das einzige Vielfache von 45 nahe 0, also kein Problem.

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:
sinh(1e-20) = 1e-20
arcsinh(1e-20) = 0
Ab Delphi 7 siehts dann so aus
Code:
sinh(1e-20) = 0
arcsinh(1e-20) = 0
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
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.

Fritzew 21. Nov 2017 11:23

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:
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
Damit kann ich leben denke ich :-)

Medium 21. Nov 2017 11:48

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Rollo62 (Beitrag 1386761)
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.

Du hast es immer noch nicht ganz verstanden glaube ich. Das hier:
Delphi-Quellcode:
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;
Das liefert für die drei Variablen:
s = 1,570796370506286620
d = 1,570796326794896560
e = 1,570796326794896620

pi/2 wäre nach diesem Rechner auf 50 Stellen genau: 1.570796326794896619231321691639751442098584699687 6

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:
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;
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.

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:
procedure TForm1.Button1Click(Sender: TObject);
var
  s: Single;
begin
  s := 0.01;
  Edit1.Text := FloatToStrF(s, ffFixed, 30, 30);
end;
Ergebnis: 0,009999999776482582

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.

Uwe Raabe 21. Nov 2017 12:52

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von TiGü (Beitrag 1386784)
Wie ist denn der Tangens = tan(x) definiert?
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.


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;

Uwe Raabe 21. Nov 2017 12:59

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Medium (Beitrag 1386795)
Es ist nämlich schon manchmal nicht möglich ein exakt eingegebenes Literal in ein Float zu packen:

Das ist schon richtig! Allerdings lässt sich gerade 90 schon exakt darstellen. Daher besteht berechtigte Hoffnung, daß bei Verwendung von Grad statt Radians die Singularitäten schon erkannt werden können bevor man in Radians umrechnet.

Medium 21. Nov 2017 13:51

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1386802)
Zitat:

Zitat von Medium (Beitrag 1386795)
Es ist nämlich schon manchmal nicht möglich ein exakt eingegebenes Literal in ein Float zu packen:

Das ist schon richtig! Allerdings lässt sich gerade 90 schon exakt darstellen. Daher besteht berechtigte Hoffnung, daß bei Verwendung von Grad statt Radians die Singularitäten schon erkannt werden können bevor man in Radians umrechnet.

Ja natürlich. Aber dies von der Tan()-Funktion die von Hause aus mitgeliefert wird, und die dokumentierterweise mit Floats und in Radians arbeitet zu erwarten und sogar als Bug zu bezeichnen ist schlicht vermessen. Es ist nicht der Fehler von Emb wenn ein Nutzer die Technologie die er einsetzt nicht ausreichend kennt, dies aber dank mannigfaltig verfügbarer Ressourcen heutzutage jedoch problemlos könnte. Die Tan()-Funktion in Delphi arbeitet zu 100% so wie man es von ihr erwarten kann/muss. Darum geht es mir.

Uwe Raabe 21. Nov 2017 14:02

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Medium (Beitrag 1386810)
Die Tan()-Funktion in Delphi arbeitet zu 100% so wie man es von ihr erwarten kann/muss. Darum geht es mir.

Das sehe ich genau so!

Rollo62 22. Nov 2017 06:28

AW: tan() von Single, Double, etc.
 
@TiGü
Zitat:

In der Regel sagt man sin(x) / cos(x)!
Bei 90° haben wir sin(90°) / cos(90°) = 1 / 0.
Ja logisch, ich hatte nur von der Kurve drauf geschlossen das es springt.
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:

Derselbe Rechner liefert für
tan(s) = -22877332.42942889836981039204072541441251573432
tan(d) = 16882959411340397.91436507298177193134
tan(e) = -1300934329906107203.0757511956816777
Wird das so gespeichert, und man schaltet mal von Single auf Double, etc. passt das nicht zusammen.

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

TiGü 22. Nov 2017 08:11

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Rollo62 (Beitrag 1386844)
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.

Hier ist doch schon dein Denkfehler. Du willst mit dem Ergebnissen von Tan(90°) und Tan(270°) weiterrechnen.
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:

Zitat von Rollo62 (Beitrag 1386844)
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.

Lösungen dazu wurden dir vielfach gezeigt.
Siehe Beitrag #23 und #24 oder nehme eine von den schon genannten Mathematik-Bibliotheken.

Rollo62 22. Nov 2017 08:21

AW: tan() von Single, Double, etc.
 
Zitat:

Hier ist doch schon dein Denkfehler. Du willst mit dem Ergebnissen von Tan(90°) und Tan(270°) weiterrechnen.
Genau DAS will ich nicht, sondern diese Situationen möglichst elegant Vermeiden.

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

Uwe Raabe 22. Nov 2017 09:28

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Rollo62 (Beitrag 1386853)
ich werde wohl +/-Infinity einführen um darauf Testen und Reagieren zu können.

Dank der diversen FloatHelper gibt es das bereits:
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;

Medium 22. Nov 2017 10:18

AW: tan() von Single, Double, etc.
 
Zitat:

Zitat von Rollo62 (Beitrag 1386844)
@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:

Derselbe Rechner liefert für
tan(s) = -22877332.42942889836981039204072541441251573432
tan(d) = 16882959411340397.91436507298177193134
tan(e) = -1300934329906107203.0757511956816777

Nein, dir ist es offenbar nicht so ganz klar. Das Problem ist nicht die Funktion tan() (oder irgendeine andere), sondern das "Problem" setzt schon vorher an. Nämlich an der Stelle, an der du versuchst einen Wert, der "in echt" unendlich viele (und nicht-wiederholende) Nachkommastellen hat in einen begrenzten Speicherplatz zu schreiben, und diesen als Parameter übergibst.
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:

ich muss aber diese Werte in verschiedene Einheiten Umrechen, und zurückrechnen (°, %, mm/m, in/ft, ...).
Ja und? Was hat das damit zu tun?

Zitat:

Das Problem was ich sehe ist das bei Rechnung und Rückrechnung am Ende ein verschiedene Werte rauskommen.
Klar, das ist normal bei binärer Gleitkommaarithmetik. Nicht alle Zahlen die dezimal mit endlich vielen Stellen geschrieben werden können sind in dieser Darstellung endlich darstellbar. Und sobald (vielfache von) PI ins Spiel kommen gilt dies für beide Systeme, sodass bei Rechnung und Umwandlung zwischen diesen Rundungsfehler auftreten müssen.

Zitat:

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.
Klar, da man um einen Grenzbereich herum rundet. Zu erwarten. Wenn du in Grad rechnest (was kein Computer intern tut), dann ist das ein Sonderfall, den du eben als solchen gesondert behandeln musst.

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:

Edit:
Weiterhin wären die Ergebnisse, obwohl logisch gleich im Vergleich nicht mehr gleich.
Auch das kommt als Problem heraus.
Auch dies ist ein [i]normaler[i] Umstand bei Floats, und zu dem Thema "Floats vergleichen" gibt es mehrere zig Threads in diesem Forum hier allein.

Zitat:

Also bitte wieder locker werden ...
Jetzt, ja :) Mich bringt nur auf die Palme wenn man versucht eigenen Mangel an Informationen als Fehler anderer auszulegen, bzw. davon auszugehen dass die eigene Sichtweise und Erwartungshaltung universell wären. Aber du hast ja nun eine Lösung. Das freut mich!


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:32 Uhr.
Seite 1 von 2  1 2      

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