Taylorreihe - Mathematisches Verständnis (C++)



  • Hallo,

    ich habe leider ein Verständnis Problem bei einem C++ Programm. Bzw. dem Mathematischen Hintergrund der Aufgabe da ich noch keine Analysis hatte und damit nicht sehr viel anfangen kann. Zuerst einmal die Aufgabe und mein bisheriger Ansatz:

    Aufgabe:
    Programmieren Sie eine Funktion, die den Grenzwert von n berechnet, ab welchem der Wert der Sinusfunktion durch die unten angegebene Taylor-Reihe auf einen Wert Epsilon genau angenähert wird. Epsilon ist der Übergabeparameter der Funktion. Beachten Sie folgende Punkte bei der Programmierung der Funktion:

    • Zu Epsilon: Überprüfen Sie verschiedene Epsilons: 10−1, 10−7 und 10−20. Für welche davon kann man ein zuverlässiges Ergebnis berechnen, für welche nicht und warum? (sin(x) - ftaylor(x) < Epsilon)

    • Zu x: Berechnen Sie weiterhin den mittleren Fehler zwischen den Ergebnissen der Taylor- Funktion und der Sinus-Funktion für x zwischen -180° und 180° mit den Abständen 0.1° für x

    • Der Rückgabewert der Funktion ist n, wobei n die Laufvariable der Summe in der Taylor-Funktion ist, ab der der berechnete mittlere Fehler der Taylor-Reihe zur Sinus-Funktion kleiner ist als das eingegebene Epsilon.
    ftaylor(x) ist die Taylor-Funktion, die den Sinus-Wert berechnet.

    Mein Ansatz:

    #include <math.h>
    #include <iostream>
    using namespace std;
    
    double fak(int f) {
    	if (f <= 0)
    		return 1;
    	else
    		return f * fak(f - 1);
    }
    
    double taylor(double x) {
    	int n = 0;
    	double result = 0;
    
    	/*Klappt mit der Bedinung aber etwas ungenauer
    	do {
    		result += pow(-1.0, n) * pow(x, 2 * n + 1) / fak(2 * n + 1);
    	} while ((sin(x) - result) >= x);
    	*/
    
    	for (n = 0; n < 3; ++n) { // n < 3 gegen Bedingung: (sin(x) - ftaylor(x) < Epsilon) tauschen? Funktioniert aber nicht...
    		result += pow(-1.0, n) * pow(x, 2 * n + 1) / fak(2 * n + 1);
    	}
    	return result; // Platzhalter zum testen, wird dann n
    }
    
    int main() {
    	double epsilonA = pow(10,-1);
    	double epsilonB = pow(10, -7);
    	double epsilonC = pow(10, -20);
    	int sum = 0;
    
    	cout << "\t\t\t\tTaylorreihe" << endl;
    	cout << "-------------------------------------------------------------------------------\n";
    	for (float epsilon = -180; epsilon < 180; epsilon += 0.1) {
    		sum += taylor(epsilon); //Irgendwie den Mittleren Fehler berechnen
    	}
    	cout << "-------------------------------------------------------------------------------\n";
    	cout << "1. Epsilon - Sinus: " << taylor(epsilonA) << "\t Probe mit math.h: " << sin(epsilonA) << endl;
    	cout << "-------------------------------------------------------------------------------\n";
    	cout << "2. Epsilon - Sinus: " << taylor(epsilonB) << "\t Probe mit math.h: " << sin(epsilonB) << endl;
    	cout << "-------------------------------------------------------------------------------\n";
    	cout << "3. Epsilon - Sinus: " << taylor(epsilonC) << "\t Probe mit math.h: " << sin(epsilonC) << endl;
    	cout << "-------------------------------------------------------------------------------\n";
    	cout << "-------------------------------------------------------------------------------\n";
    	cout << endl;
    	system("PAUSE");
    	return 0;
    }
    

    Mein Probleme mit der Aufgabe sind das ich nicht weiß wie ich festellen kann für welche der drei Testwerte ich das Ergebnis zuverlässig berechnen kann. Ich vermute für 10^−7 und 10^−20 da 10^-1 intern zu einer Fehlberechnung führt wenn ich sin(x) - result berechne (-1,9..). Bin mir aber nicht sicher.
    Zum anderen weiß ich nicht wie genau man einen mittleren Fehler damit berechnen soll - die Formeln die ich bei Google gefunden haben mich eher verwirrt =/.
    Und der letzte Punkt wäre für was das Epislon gut ist wenn ich als Werte ja die -180 bis 180 Grad nutzen soll.

    Wäre für Hilfe sehr dankbar 🙂


  • Mod

    Du hast die Aufgabe nicht richtig verstanden. Du hast hier drei wichtige Variablen:
    Einen Winkel, genannt x.
    Eine Abweichung, genannt epsilon.
    Eine Rechentiefe, genannt n.

    Du schmeißt die bei dir im Programm wild durcheinander. Daher stammen sowohl deine Verwirrung als auch der von dir beobachtete Fehler.

    Mittelwert des Fehlers soll hier der Mittelwert der Abweichung des Ergebnisses der Taylorreihe zum "richtigen" Wert (Ergebnis der sin-Funktion) sein, gemittelt über die angegebenen Werte für x. Wie man den Mittelwert einer Reihe von Zahlen ausrechnet, muss ich nicht erklären, oder?

    PS: An deinem Code sieht man mal wieder die Spätfolgen, wenn man Rekursion anhand der Fakultät erklärt 🙄 . Das hat nichts mit deiner Frage zu tun, schien mir aber erwähnenswert für andere Leser.


  • Mod

    Auch wenn die Vorgaben der Aufgabenstellung sowohl dem Programmierer als auch dem Numeriker in mir Schmerzen bereiten:

    #include <cmath>
    #include <iostream>
    
    float fak(unsigned n)
    {
      float result = 1;
      for(unsigned i = 1; i <= n; ++i)
        {
          result *= i;
        }
      return result;
    }
    
    float ftaylor(float x, unsigned n)
    {
      float result = 0;
      for(unsigned i = 0; i <= n; ++i)
        {
          result += ((i % 2) ? -1 : 1) * std::pow(x, 2*i+1) / fak(2*i+1);
        }
      return result;
    }
    
    unsigned find_convergent_order(float epsilon)
    {
      unsigned n = 0;
      float total_deviation;
      epsilon *= 3601;
      do
        {
          ++n;
          total_deviation = 0;
          for(float x = -180; x <= 180; x += 0.1)
            {
              total_deviation += std::abs(ftaylor(x * M_PI / 180, n) - std::sin(x * M_PI / 180));
              if (total_deviation > epsilon) break;
            }
       }
      while (total_deviation > epsilon);
      return n;
    }
    
    int main()
    {
      std::cout << 1e-1 << ' ' << find_convergent_order(1e-1) << '\n';
      std::cout << 1e-7 << ' ' << find_convergent_order(1e-7) << '\n';
      std::cout << 1e-20 << ' ' << find_convergent_order(1e-20) << '\n';
    }
    

    2, 7, 310. Zumindest ist das die Ausgabe des Programms. Wie valide der Wert 310 ist, darfst du aber selber deinem Lehrer erklären 😉



  • Vielen Dank erstmal für deine Antwort und das Codebeispiel - das macht es deutlich verständlicher was ich an der Aufgabe nicht verstanden habe.

    "PS: An deinem Code sieht man mal wieder die Spätfolgen, wenn man Rekursion anhand der Fakultät erklärt"
    Weil das von der Geschwindigkeit her deutlich langsamer ist, da bei höheren Werten zu viele Funktionsaufrufe gemacht werden, oder weshalb? 🙂

    Ist das Epsilon * 3601 weil der Wert sonst zu klein ist und bei extrem kleinen Werten nicht genau gerechnet wird?

    Die Antwort auf den ersten Teil der Aufgabe ist dann ja, dass man mit 1e-20 ein zuverlässiges Ergebnis berechnen kann weil erst nach 310 Durchgängen die Abweichung größer wäre als die durch Epsilon vorgegebene, zulässige Abweichung. Für die anderen beiden kommt es drauf wie genau man es haben möchte, was aber nicht in der Aufgabe erwähnt wird. Oder liege ich damit komplett daneben?



  • dentho schrieb:

    "PS: An deinem Code sieht man mal wieder die Spätfolgen, wenn man Rekursion anhand der Fakultät erklärt"
    Weil das von der Geschwindigkeit her deutlich langsamer ist, da bei höheren Werten zu viele Funktionsaufrufe gemacht werden, oder weshalb? 🙂

    Weil Du jede Menge Fakultaeten ausrechnest, nicht nur innerhalb der Rekusrion. Du koenntest da jede Menge sparen, wenn Du nicht in jedem Schleifendurchlauf das ganze rekursive Verfahren neu in Gang setzen wuerdest.

    Zu den Epsilons:

    Ich denke, das ist eher anders gemeint. Bei einem Epsilon von 10^-20 sollte das Ergebnis nicht so zuverlaessig sein. Grund: Du subtrahierst da die Taylorentwicklung von der eigentlichen Funktion. Die nimmt Werte zwischen -1 und 1 an. Mit double precision Genauigkeit hast Du aber nur ~16 Stellen Genauigkeit. Das heisst, ein Epsilon von 10^-20 erfordert eine Rechengenauigkeit, die Du einfach nicht hast.



  • Vielen Dank Gregor, an die Genauigkeit des Datentypes hatte ich dabei gar nicht gedacht. Dann müsste die Antwort 1e-7 sein. Die Genauigkeit reicht für 1e-20 nicht aus und bei 1e-1 wird die Rechentiefe n nur 2 - was bei einer direkten Ausgabe von fTaylor(x, 2) und sin(x)zu stark abweichenden Werten führt - erst ab der Rechentiefe 3 passen die Werte. Dann wäre nur noch offen warum epsilon *= 3601; in Zeile 28 steht.


  • Mod

    Vielleicht machst du mal ein bisschen mehr mit meinem Programm, als es nur unreflektiert zu kopieren. Es gibt zum Beispiel sicherlich einen Grund, warum es beim letzten epsilon 310 ausgibt. Bei deiner Erklärung mit der Rechengenauigkeit würde man schließlich erwarten, dass es überhaupt nie terminiert.



  • Na abrechen tut das Programm ja weil: total_Deviation += abs(sin(x * M_PI / 180) - fTaylor(x * M_PI / 180, n)); die Abweichungen in jedem Durchgang addiert. und ab einer gewissen tiefe n weicht es zu oft/zuviel ab im Vergleich zur sin Funktion wodurch die Abbruch Bedingung wahr ist.

    Wenn ich in der Funktion fTaylor die Schleife mit int statt unsigned int laufen lassen ist die Rechentiefe nur 39. Da n aber selbst bei 1e-20 nur den Wert 310 annimmt dürfte das ja gar nicht passieren. (außer ich ändere alle float Datentypen in double, dann passt es wieder)


  • Mod

    Deine Interpretation der Vorgänge im Programm ist so ziemlich exakt das Gegenteil von dem was passiert.



  • Ich habe doch eine totale Abweichung und mit Epsilon eine vorgegebene.
    total_Deviation += abs(sin(x * M_PI / 180) - fTaylor(x * M_PI / 180, n));
    rechnet doch dann den Sinus Wert der Funktion sin minus den Sinus Wert der Taylor Funktion.
    Stimmen die Werte überein kommt 0 raus. 0 < Epsilon - alles okay keine Abweichung / Fehler der Berechnung.

    Ist eine kleine Abweichung vorhanden wird die zur totalen Abweichung dazu addiert. Ist diese größer als Epsilon - if (total_Deviation > epsilon) - springt er aus beiden Schleifen und gibt mir n zurück. n beschreibt wie oft die Funktion insgesamt, ohne zu viele Fehler, durchlaufen ist. Bei jedem weiteren Durchgang erhöht sich außerdem die Genauigkeit von fTaylor. Bei der 310 wurde sehr oft ohne zu viele / große Fehler gerechnet für die benötigte Genauigkeit, bei 7 ausreichend oft, bei 2 zu ungenau.

    Aus der Aufgabenstellung heraus klingt es zwar eher danach das n angibt wie oft die Taylorreihe durchlaufen werden musste bis die Genauigkeit höher ist als die Vorgabe Epsilon aber wenn dem so ist kann ich das nicht im Code erkennen. 😞


  • Mod

    dentho schrieb:

    Ist diese größer als Epsilon - if (total_Deviation > epsilon) - springt er aus beiden Schleifen und gibt mir n zurück.

    Bist du da sicher?

    n beschreibt wie oft die Funktion insgesamt, ohne zu viele Fehler, durchlaufen ist. Bei jedem weiteren Durchgang erhöht sich außerdem die Genauigkeit von fTaylor. Bei der 310 wurde sehr oft ohne zu viele / große Fehler gerechnet für die benötigte Genauigkeit, bei 7 ausreichend oft, bei 2 zu ungenau.

    Das widerspricht sich doch selbst, merkst du das nicht beim Schreiben? Die Berechnung würde mit jedem Durchlauf immer genauer sagst du. Wenn die Berechnung ungenau ist, schreibst du, die Berechnung würde abbrechen. Und dann gibt die Funktion das n zurück, bei dem es genau ist? Wie soll das gehen?



  • Ah, ok da war ich echt blind, jetzt hab ich den Fehler gesehen. Das Programm springt dann natürlich aus der for-Schleife bleibt aber in der while-Schleife. Zig mal drüber gelesen und jedes Mal falsch gedacht... danke fürs hartnäckig bleiben und nicht direkt verraten 🙂

    Gut dann dreht sich die Logik um: 1e-7 bleibt weiterhin ausreichend genau. 1e-20 braucht 310 Durchgänge bei der Berechnung bis die vorgegebene Genauigkeit erreicht ist und ist damit genau, aber viel zu Langsam und diese Genauigkeit wird wohl eher weniger gebraucht bzw. kann ich sie mit float gar nicht nutzen. 1e-2 benötigt zwar nur 2 Durchgänge hat aber eine zu geringe Genauigkeit so dass die Werte von sin und taylor häufig zu unterschiedlich sind da es nur auf eine Kommastelle genau ist.
    Hoffe ich hab es jetzt richtig verstanden 🙂


  • Mod

    dentho schrieb:

    Gut dann dreht sich die Logik um: 1e-7 bleibt weiterhin ausreichend genau. 1e-20 braucht 310 Durchgänge bei der Berechnung bis die vorgegebene Genauigkeit erreicht ist und ist damit genau, aber viel zu Langsam und diese Genauigkeit wird wohl eher weniger gebraucht bzw. kann ich sie mit float gar nicht nutzen. 1e-2 benötigt zwar nur 2 Durchgänge hat aber eine zu geringe Genauigkeit so dass die Werte von sin und taylor häufig zu unterschiedlich sind da es nur auf eine Kommastelle genau ist.
    Hoffe ich hab es jetzt richtig verstanden 🙂

    Vielleicht prüfst du deine Behauptungen auch mal? Experimentier doch mal ein bisschen mit dem Programm! Wie passen denn deine Behauptungen hier mit dem zusammen, was vorher gesagt wurde:

    dentho schrieb:

    Die Genauigkeit reicht für 1e-20 nicht aus

    Die Genauigkeit reicht für 1e-20 gar nicht aus, wie kann es dann sein, dass sie erreicht wird?

    dentho schrieb:

    Wenn ich in der Funktion fTaylor die Schleife mit int statt unsigned int laufen lassen ist die Rechentiefe nur 39. Da n aber selbst bei 1e-20 nur den Wert 310 annimmt dürfte das ja gar nicht passieren. (außer ich ändere alle float Datentypen in double, dann passt es wieder)

    Und dann hängt das Ergebnis plötzlich vom Datentyp der Zählvariable ab? Hier geht doch eindeutig mehr vor sich, als du denkst...

    Vielleicht denkst du mal über die nötigen Rechenschritte nach:

    pow(-1.0, n) * pow(x, 2 * n + 1) / fak(2 * n + 1);
    

    Mit n = 310. Explodiert dir da nicht der Kopf, wenn du nur darüber nachdenkst? Was ist denn beispielsweise die Fakultät von 621?



  • Ich komme leider nicht wirklich auf die Lösung 😞

    Die Aussage über 1e-1 und 1e-7 würde ich so stehen lassen, 1e-7 ist ausreichend genau, 1e-1 zu ungenau da zu wenig Stellen berücksichtig werden.

    Bei 1e-20 übersteigt die Vorgabe Epsilon die maximale Genauigkeit von double - der berechnete Wert ist dadurch zwar so genau wie es der Datentyp zulässt, aber nicht so genau wie die Vorgabe Epsilon. Bei 310 Terminiert er da das Ergebnis von total_Devation unendlich bzw. undefiniert wird - was wohl als 0 interpretiert wird. Jedenfalls wird total_Deviation fälschlicherweise als kleiner wie Epsilon gewertet und so läuft zuerst die Schleife von -180 bis 180 durch und dann wird die while Schleife verlassen.

    Aber wieso wird total_Devitaion zu inf bzw. result von fTaylor zu inf?
    Der Rückgabewert von fak wird schon viel früher zu inf und dadurch wird auch der Wert, der in fTaylor dazu addiert wird, ab ~i >= 85 ca. inf bzw. 0. Bis zu 310 bleibt diese Schleife Stabil und macht immer dasselbe und würde theoretisch bis zur Genauigkeit von 1e-20 laufen wäre da nicht das Limit von double. Terminiert es also weil n in fTaylor bei jedem Durchgang - ja was genau - eine Kommastelle hinzufügt oder auf Genauigkeit prüft? Bei e+310 ist auf jeden Fall das Limit von double erreicht und der Versuch ein weitere Glied zu berechnen gibt ein inf zurück und beendet die Funktion find_Convergent_Order.

    Ein zuverlässiges Ergebnis lässt sich also mit Epsilon: 1e-7 berechnen. Epsilon: 1e-20 übersteigt die maximale Genauigkeit von double - das Ergebnis wäre damit zwar so genau wie durch double darstellbar aber es würden unnötig viele Berechnungen ausgeführt. Epsilon: 1e-1 ist zu ungenau da zu wenig Kommastellen berücksichtig werden.



  • Ob so ein Programm hier zum Verständnis hilft?

    #include <cmath> 
    #include <iostream> 
    
    float fak(unsigned n) 
    { 
      float result = 1; 
      for(unsigned i = 1; i <= n; ++i) 
        { 
          result *= i; 
        } 
      return result; 
    } 
    
    float ftaylor(float x, unsigned n) 
    { 
      float result = 0; 
      for(unsigned i = 0; i <= n; ++i) 
        { 
          result += ((i % 2) ? -1 : 1) * std::pow(x, 2*i+1) / fak(2*i+1); 
        } 
      return result; 
    } 
    
    float ftaylor_1(float x, unsigned n) 
    { 
      float result = 0; 
      float y = x;
      for(unsigned i = 0; i <= n; ++i) 
        { 
          result += y; 
          y *= -x*x/(2*i+2)/(2*i+3);
        } 
      return result; 
    } 
    
    float deviation(unsigned n)
    {
        float total_deviation = 0; 
          for(float x = -180; x <= 180; x += 0.1) 
            { 
              total_deviation += std::abs(ftaylor(x * M_PI / 180, n) - std::sin(x * M_PI / 180)); 
            } 
        return total_deviation;
    }
    
    float deviation_1(unsigned n)
    {
        float total_deviation = 0; 
          for(float x = -180; x <= 180; x += 0.1) 
            { 
              total_deviation += std::abs(ftaylor_1(x * M_PI / 180, n) - std::sin(x * M_PI / 180)); 
            } 
        return total_deviation;
    }
    
    int main() 
    {
       std::cout << deviation(5)   << " " << deviation_1(5)   << std::endl;
       std::cout << deviation(10)  << " " << deviation_1(10)  << std::endl;
       std::cout << deviation(309) << " " << deviation_1(309) << std::endl;
       std::cout << deviation(310) << " " << deviation_1(310) << std::endl;
       std::cout << deviation(311) << " " << deviation_1(311) << std::endl;
    
       return 0;
    }
    

Log in to reply