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 nicht 😞

    Die 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 machen 🙂

    Mich 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 Ergebnis

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


Anmelden zum Antworten