Sinus von Hand berechnen, Probleme beim realisieren
-
Wir haben heute in der Schule die Aufgabe bekommen eine Funktion zu schreiben die
den Sinus eines Winkels berechnet, nun habe ich die Formel die wir bekommen haben
nicht so ganz verstanden und dementsprechend auch nicht hinbekommen.Habe meinen bisherigen falschen Code nur in Java, möchte aber erstmal mit C++
ne lauffähige Version hinbekommen und genau da bräucht ich eure Hilfe, könntet
ihr mir erklären wie man den Sinus genau berechnet, mein Lehrer konnte das mir
nicht verständlich machen und das Arbeitsblatt auch nichtDie Formel die ich habe sieht so aus(Bitte dev-Version des Forums benutzen):
Die Hoch - Tief - Buchstaben vor Sigma sind leider nicht besser hinzubekommen
sin(x)=Nn=0Σ (-1)n * x(2*n+1)/(2*n+1)! <=> sin(x) = x - x3/3! + x5/5! - x7/7! + ...So damit ihr mir auch glaubt, meinen Ansazt in Java, ihr dürft natürlich pow und
fac aus cmath nehmen und müsst es nicht von Hand machenMich interressiert nur wie man das oben in Code bekommt der funktioniert.
Der Zweite parameter von sin ist die Genauigkeit (Nachkommastellen)
// Sinus public double sin (int x, int n) { double result = 0.0; for (int i = 0; i < n; i++) { result += pow (-1, i) * (pow (x, 2*i+1)/fac (2*i+1)); } return result; } // Potenz public int pow (int base, int exp) { if (base == 0 && exp == 0) return 1; // 0^0 als 1 definiert for (int i = 0; i < exp; i++) base *= base; return base; } // Fakultät public int fac (int i) { return i > 1 ? i *= fac (i-1) : i; }
-
ich würde damit anfangen, die Parametertypen von int in double zu ändern
-
Ich würds so machen:
public double sin(double x, int n) { double result = x; bool bToggleSign = false; int i = 3; do { bToggleSign ^= 1; result += pow(x, i)/fac(i) * (bToggleSign)?-1:+1; i+=2; }while( (--n) ) // solange Gliedanzahl nicht null ist return result; }
"result" beginnt mit dem ersten Glied der Fourrier-Reihe. In der while-Schleife werden solange Glieder addiert bzw. subtrahiert, solange die Gliedanzahl nicht Null ist. "i" beginnt mit 3 und wird in der Schleife immer um 2 inkrementiert, dadurch brauchst du 2k+1 nicht immer ausrechnen.
Alles klar?
EDIT: Rat von Bashar befolgt
EDIT: do{}while wäre etwas sinnvoller..
-
Das ändert am Ergebnis nichts so lange ich ganze Zahlen eingebe, ob ich int oder
double habe, bei sin(1) ist das Ergebnismeins: -0.8247988315696649
lib: 0.8414709848078965
-
Hast du meine Funktion schon ausprobiert?
-
Ne, unsere Postings haben sich überschnitten so hab ich deines nicht gesehen.
Die Funktionsweiße ist mir fast klar, nur eines versteh ich gerade nicht wie
funktioniert dein Vorzeichenflag genau?
Am Anfang ist es false dann wird es mit ^= 1 zu true und danach wieder zu false?Ich mag Java nicht, da funktioniert das hin und her schalten von true und false
nicht. Wie kann ich es ändern dass es weiterhin funktioniert? Geht es mit int?Habe das jetzt an Java angepasst, alledings funktioniert es nicht (Ergebnis ist viel zu groß):
public double sin(double x, int n) { double result = x; int ToggleSign = 0; int i = 3; do { bToggleSign ^= 1; result += pow(x, i)/fac(i) * ToggleSign == 1? -1 : +1; i+=2; }while( --n > 0 ); // solange Gliedanzahl nicht null ist return result; }
Ich denke mal dass ^= 1 für den int nicht so wie für bool in c++ wirkt.
Also brauch ich wohl ne andere Möglichkeit das Vorzeichen zu testen, nur wie?
-
Am Anfang ist es false dann wird es mit ^= 1 zu true und danach wieder zu false?
Ja, auf diese Weise kann man Bits elegant "toggeln". Das kannst du eigentlich mit jeder Variable machen, sogar mit Zeigern; mit "^1" wird ja schließlich immer nur das erste bit der Variable ge-XORed.
Du hast noch Fehler in deiner Funktion (die while-Bedingung), aber theoretisch müsste sie funktionieren...
public double sin(double x, int n) { double result = x; int ToggleSign = 0; int i = 3; do { ToggleSign ^= 1; result += pow(x, i)/fac(i) * (ToggleSign == 1)? -1 : +1; i+=2; }while( --n != 0 ); // solange Gliedanzahl nicht null ist return result; }
-
Auch deine Version liefert die gleichen falschen werte
-
Diese Funktion habe ich jetzt selber ausprobiert. Der Ternary-Operator ist anscheinend schwächer als das Mal, deshalb habe ich es umklammert. Statt mit -1 bzw. +1 zu multiplizieren, multipliziere ich jetzt mit -1.0 bzw. +1.0, somit erzwingen wir eine floating point multiplikation.
Für n darfst du nicht so große Zahlen angeben, weil das Resultat sonst ziemlich schnell in die Höhe schnellt (die Funktion fac() sollte ein double zurückgeben, dann kannst du auch etwas größere Zahlen eingeben..).double sin(double x, int n) { double result = x; int ToggleSign = 0; int i = 3; do { ToggleSign ^= 1; result += pow(x, i)/fac(i) * ((ToggleSign)? -1.0 : +1.0); i+=2; }while( (--n) ); // solange Gliedanzahl nicht null ist return result; }
-
Das Ergebnis:
deines: 0.841468253968254
li: 0.8414709848078965
Also lags tatsächlich an dem Trinären Operator.Kannst du mir deine Funktion mal kurz erklären, muss die Optimierungen vor dir
nacher auch meinem Lehrer erklären können.
-
Ich gebe noch mal ein paar Dinge zu bedenken:
(1) Ihr rechnet die Potenzen / Fakultäten jedes mal wieder neu aus.
Wenn ihr den Sinus genau ausrechnen wollt, kostet das schnell viel
Rechenzeit. Das geht auch schlauer(2) Wenn man sich noch zwei Gedanken über Fließkommaarithmetik macht,
bekommt man ne hübschere Abbruchbedingung für die Schleife: Wie lange
muss man rechnen, damit der neu berechnete Therm nichts mehr zum Ergebnis
beiträgt? Dann könnt ihr euch das n sparen.(3) Die Sinusreihe (so heißt die Formel) gibt nur gute Ergebnisse für Werte
nahe x = 0. Wie kann man die Periodizität des Sinus ausnutzen, um auch für
x = 3000*pi das richtige Ergebnis zu bekommen?Wenn du deinen Lehrer beeindrucken willst, kann ich mal was entsprechendes posten
-
SirLant schrieb:
Kannst du mir deine Funktion mal kurz erklären, muss die Optimierungen vor dir
nacher auch meinem Lehrer erklären können.int ToggleSign = 0; //.. ToggleSign ^= 1;
kannst du (hier) durch
bool ToggleSign = false; //.. ToggleSign = !ToggleSign;
ersetzten. Wirds klarer?
-
Gerne,auch wenn mein jetztiges für den Lehrer bereits genug ist, aber für mich nicht
mach aber bitte ein paar Erklärungen dazu, damit ich es auch verstehe
-
Die Stelle ist mir klar, mir wurd jetzt eigentlich alles klar, da das i ja zu Beginn
3ist und danach einfach in 2er Schritten hochgezählt wird
-
(1) Ihr rechnet die Potenzen / Fakultäten jedes mal wieder neu aus.
Wenn ihr den Sinus genau ausrechnen wollt, kostet das schnell viel
Rechenzeit. Das geht auch schlauer(2) Wenn man sich noch zwei Gedanken über Fließkommaarithmetik macht,
bekommt man ne hübschere Abbruchbedingung für die Schleife: Wie lange
muss man rechnen, damit der neu berechnete Therm nichts mehr zum Ergebnis
beiträgt? Dann könnt ihr euch das n sparen.(3) Die Sinusreihe (so heißt die Formel) gibt nur gute Ergebnisse für Werte
nahe x = 0. Wie kann man die Periodizität des Sinus ausnutzen, um auch für
x = 3000*pi das richtige Ergebnis zu bekommen?1. Guter Tipp.
2. Das n ist ein Argument der Funktion, und keine neue Variable, daher denke ich, dass es elegant gelöst ist.
3. Hab ich nicht ganz verstanden.
-
Das n stand auf dem Arbeitsblatt, aber im normalfall rundet man danach ja eher bei bedarf
-
Taurin du wolltest doch noch den Code posten
-
double my_sin(double x) { // sin(x) = summe von k = 0 bis undenlich[pow(-1,k)*pow(x,2*k+1)/fak(2*k+1)] double fak_counter = 1, zaehler, nenner = 1, res = 0, res_old; double vz = 1, x_ohne_nachkomma; if(x < 0) // nur positive Argumente: sin(-x) = -sin(x) { // x ist jetzt positiv x = -x; vz *= -1; } if(x >= 2*pi) // der Sinus ist 2-pi-Peridisch: sin(x) = sin(x + 2*k*pi) { // x ist jetzt kleiner als 2*pi x_ohne_nachkomma = floor(x / (2*pi)); /* Nachkomma abschneiden ! */ x -= 2*pi*x_ohne_nachkomma; } if(pi < x && x < 2*pi) // da sin(x) = -sin(2*pi - x) bringen wir das { // x auf das Intervall [0,pi] x = 2*pi - x; vz *= -1; } if(pi/2 < x && x < pi) // da sin(x) = sin(pi - x) kommen wir auf das { // Intervall [0, pi/2] x = pi - x; } zaehler = x; do { res_old = res; res += zaehler / nenner; zaehler *= -1 * x * x; fak_counter += 2; nenner *= (fak_counter-1) * fak_counter; }while(res != res_old); // wenn res == res_old trägt der nächste Summand // nichts mehr zum Ergebnis bei return vz * res; }
-
Die If-Bedingungen über der Schleife sind nur Optimierungen für bestimme Fälle
wenn ich das richtig verstanden habe und das in der do-while Scheife dann der
eigentliche Code zur Berechnung des Sinuswertes, right?
-
Die if-Bedingungen dienen dazu, x auf das Intervall [0, pi/2] zu schieben
(und das klappt auf jeden Fall. Die eizelnen Bedingungen kann man sich am
besten am Einheitskreis klar machen).
Das kann, je nach ursprünglichem x, einen Vorzeichenwechsel des Ergebnisses
zur folge haben. Dafür wird das Ergebnis genauer, weil die Sinusreihe, wenn sie
nach einer endlichen Anzahl von Thermen abgebrochen wird, bessere Werte für
kleine x liefert.Die Schleife berechnet dann - wie du richtig erkannt hast - den Sinuswert
für das neue x.