AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

tan() von Single, Double, etc.

Ein Thema von Rollo62 · begonnen am 20. Nov 2017 · letzter Beitrag vom 22. Nov 2017
Antwort Antwort
Seite 1 von 2  1 2      
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.639 Beiträge
 
Delphi 12 Athens
 
#1

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

  Alt 20. Nov 2017, 23:42
Richtig, aber so sollte eine 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.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Rollo62

Registriert seit: 15. Mär 2007
4.170 Beiträge
 
Delphi 12 Athens
 
#2

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

  Alt 21. Nov 2017, 07:57
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
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.073 Beiträge
 
Delphi 10.4 Sydney
 
#3

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

  Alt 21. Nov 2017, 08:21
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;
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.073 Beiträge
 
Delphi 10.4 Sydney
 
#4

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

  Alt 21. Nov 2017, 08:38
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;
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.688 Beiträge
 
Delphi 2007 Enterprise
 
#5

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

  Alt 21. Nov 2017, 11:48
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.
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.639 Beiträge
 
Delphi 12 Athens
 
#6

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

  Alt 21. Nov 2017, 12:59
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.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.688 Beiträge
 
Delphi 2007 Enterprise
 
#7

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

  Alt 21. Nov 2017, 13:51
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.
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.639 Beiträge
 
Delphi 12 Athens
 
#8

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

  Alt 21. Nov 2017, 14:02
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!
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Rollo62

Registriert seit: 15. Mär 2007
4.170 Beiträge
 
Delphi 12 Athens
 
#9

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

  Alt 22. Nov 2017, 06:28
@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

Geändert von Rollo62 (22. Nov 2017 um 06:42 Uhr)
  Mit Zitat antworten Zitat
gammatester

Registriert seit: 6. Dez 2005
999 Beiträge
 
#10

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

  Alt 21. Nov 2017, 09:49
Richtig, aber so sollte eine 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

Geändert von gammatester (21. Nov 2017 um 10:20 Uhr) Grund: Minus bei cot
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 08:24 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