Sinus von Hand berechnen, Probleme beim realisieren



  • 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.



  • Ok dann ist klar 🙂



  • Das folgende geht leider nicht, nur bei 1.0 😞
    Fiel mir erst eben auf.

    // Sinus
    	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.0 : +1.0);
        		System.out.println ("Result ("+i+")" + result);
        		System.out.println ("Fakultät von i " + fac(i));
        		System.out.println ("Potenz von pow(x,i)" + pow(x,i));
        		System.out.println ("i " + i);
        		System.out.println ("ToggleSign " +ToggleSign);
        		System.out.println ("----");
        		i+=2;
      		}while( (--n != 0) ); // solange Anzahl nicht null ist
    
      		return result;
    	}
    

    Aber das ist doch richtig :???:


Anmelden zum Antworten