Abruchkriterium funktioniert nicht
-
Hallo miteinadner!
Ich habe die aufgabe bekommen die Sinusfunktion nach der undendlichen Reihe
sin x = x – x3/3! + x5/5! - x7/7! + ... programmtechnisch umzusetzen.
Ich habe das ganze mittels rekursiven aufruf gelöst. Das ergebniss ist beim einzelschritt debugen auch richtig. Nur bekommme ich das abbruchkriterium nicht in den Griff.Die Berechnung der Reihe kann abgebrochen werden,wenn der Wert des nächsten Ausdrucks kleiner als 10^-5 (0.00001) wird. Ich hab dazu eine if abfrage erstellt die den Wert auf d < 0.00001 überprüft. Doch dies ignoriert das Programm einfach. Beim debuggen ist mir aufgefallen das nach dem 4 oder 5ten Durchlauf die Zahl als x.xxxxxxe-005 dargestellt wird. Könnte es damit etwas zu tun haben?
Wäre um hilfe sehr dankbar, da mich das Programm viel Arbeit gekostet hat und es wäre schade wenn es daran scheitern würde nur weil ich das Abbruchkriterium nicht gebacken kriege.
#include <stdio.h> #include <conio.h> #include <math.h> //Funktion zum ausrechnen der Potenz double potenz(double basis, double exponent=2) { double hilf=1; for (int i = 1; i <= exponent; i++) hilf *= basis; return hilf; } //Funktion zum ausrechnen der Fakultät double fak(double n) { double erg; if(n == 0){erg = 1;} else{erg = n * fak(n-1);} return(erg); } //Funktion zum ausrechnen des Sinus double sinus(double x,double wert) { static int i=2;//Zählvariable um die Fakultät stets um zwei zu erhöhen static int zaehler=0;//Zählvariable um festzustellen ob + oder - double d;//Testvariable um Wert zu überprüfen if (zaehler == 0) // überprüfen ob erster durchlauf { x = x-((potenz(wert,3)/fak(3))); //Berechnen des x-Wertes, da erster durchlauf angefangen bei 3 zaehler = zaehler+1;//Zählt die bisherigen durchläufe wird auf einsgesetzt sinus(x,wert);//rekursiver aufruf der sinusfunktion } else //überprüfen auf zweiten durchlauf { if(zaehler&1)//ist die Anzahl der bisherigen Durchläufe gerade { d = (potenz(wert,(3+i))/fak(3+i)); // berechnen des zu addierenden wertes x = x+d; //x = x+((potenz(wert,(3+i))/fak(3+i))); i = i + 2; //fakultät zähler um zwei erhöhen zaehler = zaehler+1; //Durchlaufzählvariable um eins erhöhen if (d < 0.00001){return(x);} // <-------- Überprüfen ob der zu addierende wert kleiner als 10^-5 ist //Wieso funktioniert die Überprüfung nicht? else{sinus(x,wert);} // ist der zu addierende Wert nicht kleiner rekursiver aufruf. } else { d = (potenz(wert,(3+i))/fak(3+i)); x = x-d; //x = x-((potenz(wert,(3+i))/fak(3+i))); i = i + 2; zaehler = zaehler+1; if (d < 0.00001){return(x);} else{sinus(x,wert);} } } } void main() { double d; d = 0; printf("Bitte wert eingeben:"); scanf("%lf",&d); printf ("\nsin(x) = %lf (Vergleichswert mit Bib-Funktion)\n\n", sin (d)); d=sinus(d,d); printf("%lf",d); }
-
Bei mir kommt als Ergebnis immer 0 raus, aber der Abbruch funktioniert...
-
Schorch schrieb:
Die Berechnung der Reihe kann abgebrochen werden,wenn der Wert des nächsten Ausdrucks kleiner als 10^-5 (0.00001) wird.
Bist du wirklich sicher, dass hier nicht der Betrag gemeint ist?
-
- Du benutzt in "potenz" einen Default-Parameter (exponent=2), das gibt es in C nicht, nur in C++. Du benutzt wahrscheinlich einen C++-Compiler und merkst das gar nicht. (Die Nichtstandard-Sachen void main und conio.h ignoriere ich mal)
- Du hast kein return in sinus():
else{sinus(x,wert);}
sollte wahrscheinlich heißenelse return sinus(x, wert);
(ob es dann richtig wird bezweifle ich zwar, aber so hast du es gemeint) - Durch die statischen Variablen kann die sinus()-Funktion nur einmal aufgerufen werden. Was, wenn ich mehrere Sinusse ausrechnen möchte?
- Das ganze ist viel zu kompliziert. Wie kann eine so einfache Vorschrift wie $$\sin x := \sum_{n=0}^\infty (-1)^n \frac{x^{2n+1}}{(2n+1)!}$$ so ein kompliziertes Programm erfordern?
- Deine Aussage, dass die Abbruchbedingung ignoriert wird, muss falsch sein, da die Sinus-Funktion nur beendet wird, wenn so eine Bedingung wahr wird: Es gibt keine anderen returns, alle anderen Pfade führen auf rekursive Aufrufe.
Beim debuggen ist mir aufgefallen das nach dem 4 oder 5ten Durchlauf die Zahl als x.xxxxxxe-005 dargestellt wird. Könnte es damit etwas zu tun haben?
Nein, das ist die normale Darstellungsweise für Fließkommazahlen. 1.234e-5 heißt einfach 1.234*10^-5, also 0.00001234.
-
Ja bin mir dar ganz sicher, habs aus dre Aufgabenstellung kopiert ^^.
Also bei mir kommt da nicht 0 raus, sondern -1.#IND00. Wenn man das Programm aber beim Debuggen Schrittweise durchläuft hat x den korrekten Wert, nur er gibt mir nicht den errechneten Wert aus sonder dieses -1.#IND00. Ich dachte zuerst es liegt am Abbruchkriterium aber ich hab dieses mal das Kriterium auf 0.001 gesetzt. Es funktioniert tatsächlich, nur wieso übernimmt er den errechneten x-wert nicht?
-
Bashar schrieb:
- Du benutzt in "potenz" einen Default-Parameter (exponent=2), das gibt es in C nicht, nur in C++. Du benutzt wahrscheinlich einen C++-Compiler und merkst das gar nicht. (Die Nichtstandard-Sachen void main und conio.h ignoriere ich mal)
Ok stimmt, aber nicht weiter tragisch da dies auch ohne Default-Parameter funktioniert. Das mit void ist ja noch verständlich aber was hast du gegen conio.h??
- Du hast kein return in sinus():
else{sinus(x,wert);}
sollte wahrscheinlich heißenelse return sinus(x, wert);
(ob es dann richtig wird bezweifle ich zwar, aber so hast du es gemeint)
Doch es funktioniert, es war wirklich das return was fehlte
- Durch die statischen Variablen kann die sinus()-Funktion nur einmal aufgerufen werden. Was, wenn ich mehrere Sinusse ausrechnen möchte?
Das stimmt zwar, aber ich möchte keine mehrere Sinuse ausrechnen.
- Das ganze ist viel zu kompliziert. Wie kann eine so einfache Vorschrift wie $$\sin x := \sum_{n=0}^\infty (-1)^n \frac{x^{2n+1}}{(2n+1)!}$$ so ein kompliziertes Programm erfordern?
Das liegt im Auge des betrachters, wielange Programmierst du schon? Ich habe gerade damit angefangen, und das war halt mein erster Lösungsgedanke und das Programm funktioniert. Wenn du mir einen besseren Lösungsweg vorschlagen wolltest, dann tu dies bitte doch. Aber sag nicht auf gut Deutsch: Du depp was machsten da fürn mist das is doch viel einfacher zu machen ohne einen Lösungsansatz zu hinterlassen.
Trotzdem danke für den Hinweis Programm läuft.
-
Zur vollständigkeit halber:
Lauffähigesprogramm:
#include <stdio.h> #include <conio.h> #include <math.h> //Funktion zum ausrechnen der Potenz double potenz(double basis, double exponent) { double hilf=1; for (int i = 1; i <= exponent; i++) hilf *= basis; return hilf; } //Funktion zum ausrechnen der Fakultät double fak(double n) { double erg; if(n == 0){erg = 1;} else{erg = n * fak(n-1);} return(erg); } //Funktion zum ausrechnen des Sinus double sinus(double x,double wert) { static int i=2;//Zählvariable um die Fakultät stets um zwei zu erhöhen static int zaehler=0;//Zählvariable um festzustellen ob + oder - double d;//Testvariable um Wert zu überprüfen if (zaehler == 0) // überprüfen ob erster durchlauf { x = x-((potenz(wert,3)/fak(3))); //Berechnen des x-Wertes, da erster durchlauf angefangen bei 3 zaehler = zaehler+1;//Zählt die bisherigen durchläufe wird auf einsgesetzt return sinus(x,wert);//rekursiver aufruf der sinusfunktion } else //überprüfen auf zweiten durchlauf { if(zaehler&1)//ist die Anzahl der bisherigen Durchläufe gerade { d = (potenz(wert,(3+i))/fak(3+i)); // berechnen des zu addierenden wertes x = x+d; i = i + 2; //fakultät zähler um zwei erhöhen zaehler = zaehler+1; //Durchlaufzählvariable um eins erhöhen if (d < 0.00001){return(x);} // <-------- Überprüfen ob der zu addierende wert kleiner als 10^-5 ist //Wieso funktioniert die Überprüfung nicht? else {return sinus(x,wert);} // ist der zu addierende Wert nicht kleiner rekursiver aufruf. } else { d = (potenz(wert,(3+i))/fak(3+i)); x = x-d; i = i + 2; zaehler = zaehler+1; if (d < 0.00001){return(x);} else{return sinus(x,wert);} } } return(x); } void main() { double winkel_grad,winkel_bog; printf("Bitte wert eingeben:"); scanf("%lf",&winkel_grad); winkel_bog = winkel_grad/180*3.1415926535; printf ("\nsin(x) = %lf (Vergleichswert mit Bib-Funktion)\n", sin(winkel_bog)); winkel_grad=sinus(winkel_bog,winkel_bog); printf("sin(x) = %lf (Mein Errechneter Sinus)\n",winkel_grad); }
-
Schorch schrieb:
Ok stimmt, aber nicht weiter tragisch da dies auch ohne Default-Parameter funktioniert. Das mit void ist ja noch verständlich aber was hast du gegen conio.h??
Es ist kein Standard-C, sondern existiert nur bei uralten Borland-Compilern und welchen, die es aus Kompatibilitätsgründen mitbringen. Da du keine Funktion daraus benutzt, könntest du es auch einfach weglassen.
- Durch die statischen Variablen kann die sinus()-Funktion nur einmal aufgerufen werden. Was, wenn ich mehrere Sinusse ausrechnen möchte?
Das stimmt zwar, aber ich möchte keine mehrere Sinuse ausrechnen.
Schön für dich, aber wenn du nur Sinusse ausrechen willst gibt es dafür eine Standardfunktion. Das ist doch eine Aufgabe, das Programm guckt sich doch nachher noch irgendjemand anderes an, oder nicht? Und der wird sich nicht dafür interessieren, was du berechnen willst, sondern wird vermutlich grundlegende Qualitätsanforderungen (wie "funktioniert auch zweimal hintereinander") stellen.
Oder versteh ich das falsch? In dem Fall ignoriere meinen Hinweis einfach.- Das ganze ist viel zu kompliziert. Wie kann eine so einfache Vorschrift wie $$\sin x := \sum_{n=0}^\infty (-1)^n \frac{x^{2n+1}}{(2n+1)!}$$ so ein kompliziertes Programm erfordern?
Das liegt im Auge des betrachters, wielange Programmierst du schon?
Ich habe gerade damit angefangen, und das war halt mein erster Lösungsgedanke und das Programm funktioniert. Wenn du mir einen besseren Lösungsweg vorschlagen wolltest, dann tu dies bitte doch. Aber sag nicht auf gut Deutsch: Du depp was machsten da fürn mist das is doch viel einfacher zu machen ohne einen Lösungsansatz zu hinterlassen.Verbesserungsvorschläge:
Die statischen Variablen weg, dafür Parameter einführen.Die sinus-Funktion auftrennen in eine äußere Funktion, die man mit
sinus(x)
aufrufen kann und die dann eine weitere Funktion, die die eigentliche Rekursion übernimmt, aufruft. Da kannst du dann für alle Parameter vernünftige Startwerte vorgeben.Den Spezialfall für den ersten Durchlauf auflösen. Das ist dasselbe wie für gerade Zähler.
Die Fakultät und die Potenz nicht immer neu berechnen. Wenn du in einem Schritt x^(2i+1) berechnet hast und im nächsten Schritt x^(2i+3) brauchst, kannst du einfach den zuletzt berechneten Wert mit x^2 multiplizieren. Dasselbe gilt für die Fakultät.
-
Wenn du mir einen besseren Lösungsweg vorschlagen wolltest, dann tu dies bitte doch.
Was ist schon besser? Immerhin wäre der Klassiker durchaus einfacher:
#define LIMIT (0.001) double sinus (double x) { if (x < LIMIT) return x; else { double tmp = sinus (x / 3); return 3 * tmp - 4 * tmp * tmp * tmp; } }
Verbesserungsvorschlag: du hast eigentlich gar keinen Grund, deine Funktion sinus() rekursiv zu formulieren. Rekursion kann nützlich sein, wenn sie mir zB implizit einen Stack vontmp
's aufbaut, aber man sollte in C nicht rekursieren, wenn's genauso gut auch ohne geht. Únd in deinem Fall geht es genauso gut auch ohne. (Deine Funktion sinus() ruft sich zwar selbst auf, aber immer mit einem tail call, so dass man behaupten könnte, dass das genauso gut ist wie eine Schleife. C sieht das aber anders.)Ich würde die Funktion so bauen:
funktion sinus (x) { wert = 0; // Laufvariablen (siehe Bashar), um den jeweiligen Summanden berechnen zu können while (betrag(summand) > LIMIT) wert = wert + summand return wert; }
-
Was ist schon besser? Immerhin wäre der Klassiker durchaus einfacher:
Ich hab gerade aus Langeweile deine Funktion mit Bashars Optimierungen neu gemacht, funktioniert bestens. Und weil's mir egal ist, ob ich deine Hausübungen mache, kann ich sie auch gleich posten:
// brauche fabs() aus math.h #include <math.h> double sinus2 (double x) { double wert = 0, // um den Sinus zu akkumulieren zaehler = 1, // aktueller Zähler nenner = 1, // und Nenner summand; // aktueller Summand, weil ich den in jeden Durchlauf 2mal brauche int sign = 1, // Vorzeichen zum Dazumultiplizieren n = 1; // akutelles n, um auf Bashars Weise die zweit-nächste Fakultät errechnen // zu können // fabs() ergibt den Betrag einer Fließkommazahl, den Betrag hattest du oben vergessen // 0.00001 ist die Abbruchgrenze (sollte man in der echten Welt nätürlich nicht harkodieren) while (fabs(summand = zaehler / nenner * sign) > 0.00001) { wert += summand; sign = -sign; // Vorzeichen umkehren n += 2; // n um zwei weiter zählen zaehler *= x * x; // Zähler mal x^2 (siehe Bashar) nenner *= n * (n - 1); // Fakultät um zwei weiter (siehe Bashar) } return wert; }
Die Schleife kann man natürlich auch durch Rekursion ersetzen, aber nur wenn man unbedingt will, weil das keinen Nutzen bringen würde.
-
µngbd schrieb:
Was ist schon besser? Immerhin wäre der Klassiker durchaus einfacher:
#define LIMIT (0.001) double sinus (double x) { if (x < LIMIT) return x; else { double tmp = sinus (x / 3); return 3 * tmp - 4 * tmp * tmp * tmp; } }
^^hey, die funktion sieht toll aus, kannte ich so noch garnicht. woher haste die?
-
Ich wusste doch, dass ich das hier schonmal gesehen habe:
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-11.html#%_idx_786
-
fricky schrieb:
^^hey, die funktion sieht toll aus, kannte ich so noch garnicht. woher haste die?
Hmm. Verbergen kann man hier nicht viel. Basher hat ganz recht:
Bashar schrieb:
Ich wusste doch, dass ich das hier schonmal gesehen habe:
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-11.html#%_idx_786Ganau von dort. Aber das ist doch echt elegant; das muss man sich doch einfach merken, oder?
-
µngbd schrieb:
Was ist schon besser? Immerhin wäre der Klassiker durchaus einfacher:
Ich hab gerade aus Langeweile deine Funktion mit Bashars Optimierungen neu gemacht, funktioniert bestens. Und weil's mir egal ist, ob ich deine Hausübungen mache, kann ich sie auch gleich posten:
Finde ich wirklich net von dir, doch habe es gestern lieber alleine gelöst.
#include <stdio.h> #include <conio.h> #include <math.h> #define PI 3.1415926535897 //Funktion zum ausrechnen der Fakultät double fak(double n) { double erg; if(n == 0){erg = 1;} else{erg = n * fak(n-1);} return(erg); } //Funktion zum ausrechnen des Sinus double sinus(double bogenmaß) { //Definition int iteration,i; double term,x; //Initialisierung iteration =0; i = 0; term = pow(bogenmaß,2 * iteration + 1)/fak(2 * iteration + 1); x =term; do //Beginn der Berechnungsschleife { i = i + 1;//Laufvariable zur Bestimmung von Addition oder Subtraktion iteration = iteration + 1; //iteration um 1 erhöhen bei jedem neuem durchlauf // Taylorreihe: x - x^3/3! + x^5/5! + ... +(-1)^(n+1)*x^(2*n+1)/(2*n+1)! term = pow(bogenmaß,2 * iteration+1)/fak(2 * iteration+1); if(i&1){x = x-term;} //Bestimmt ob gerader Durchlauf (subtraktion) else{x = x+term;} //Bestimmt ungeraden Durchlauf (addition) } while (fabs(term) >= 0.00001); //Solange wie term größer gleich 10^-5 return(x); //Gib Sinus zurück } void main() { double winkel_grad,winkel_bog; char Abfrage; do { printf("Bitte wert eingeben:"); scanf("%lf",&winkel_grad); winkel_bog = winkel_grad*PI/180; printf("sin(x) = %lf (Vergleichswert mit Bib-Funktion)\n", sin(winkel_bog)); printf("sin(x) = %lf (Mein Errechneter Sinus)\n",sinus(winkel_bog)); printf("\nWollen Sie das Programm beenden? (j/n): "); Abfrage = getche(); printf("\n\n"); } while (Abfrage != 'j'); }
Ist zwar etwas aufwändiger, aber es funktioniert.