[C++] Series de Taylor: sen(x)

Iniciado por Zeta255, 23 Marzo 2021, 19:07 PM

0 Miembros y 1 Visitante están viendo este tema.

Zeta255

Hola tengo una duda, me pide hacer una funcion de sen con sumatoria, para eso ingresare un valor x y me dara el valor con sumatoria pero las funciones tiene el mismo criterio de numero impar pero los signos +- son intercarlados como podria hacer para que me salga de igual manera;
Código (cpp) [Seleccionar]



//Solicite al usuario el valor de x y calcule e^x
#include<iostream>
#include<iomanip>
using namespace std;

int main()
{
double x, num=1,den=1,f=1,signo;
cout<<"INGRESE el valor de x para sen: ";
cin>>x;
cout<<setprecision(4);
for(int i=1;i<=360;i++)
{
num = num*x;
den = den*i;
f =( f + num/den);

}

cout<<"EL valor de sen"<<x<<" = "<<f;

}

K-YreX

La verdad es que la explicación es un poco complicada para el que no sepa de qué le están hablando.
Intuyo que la historia empieza por las series de Taylor y su forma de aproximar funciones como el sen(x) mediante una sumatoria.
Para ver la fórmula de la serie de Taylor para sen(x): https://es.wikipedia.org/wiki/Serie_de_Taylor#Funciones_trigonom%C3%A9tricas
La fórmula dice algo así como:
sen(x) = SUM(n=0->inf) ((-1)^n / (2n+1)! * x^(2n+1)) =
       = x^1/1! - x^3/3! + x^5/5! - x^7/7! + x^9/9! - ...

Ahora igual ya sí podemos empezar a hacer algo...

Si te das cuenta tú estás calculando:
sen(x) = 1+x^1/1 + x^2/2 + x^3/4 + x^4/16 + ...
Por lo que parece que ahí fallan más cosas.

De todas formas, cambiar el signo es muy sencillo. Aunque no nos demos cuenta, cuando cambiamos el signo de algo lo estamos multiplicando por -1. Entonces:
Código (cpp) [Seleccionar]

int numero = 5;
for(int i = 0; i < 10; ++i)
  cout << numero << " ";
  numero *= -1; // numero = numero * -1
}

Salida:
5 -5 5 -5 5 -5 5 -5 5 -5
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

Zeta255

#2
segun lo que entendi va asi, pero no me compila no se que estare haciendo mal, me tira nan.

Código (cpp) [Seleccionar]
#include<iostream>
#include <math.h>
#include<iomanip>

using namespace std;
int main(){
int x, numerador=1, denominador=1, sen,f=0;

cout<<"ingrese el valor de x para sen: "; cin>>x;
cout<<setprecision(4);
for (int i=1; i<=10;i++)
{
numerador=numerador*(-1^x);
denominador=denominador*(2*i+1);
sen=(numerador/denominador)*(x^(2*x+1));
f=f+sen;
}
cout<<"el valor de sen"<<x<<"= "<<f;
}

K-YreX

Lo primero: avisarte de que he borrado el otro tema que has abierto para el mismo problema.
Lo segundo: es mejor si los títulos de los temas son algo más descriptivos así que he cambiado el título de este tema.


En C++ el operador ^ es un XOR (OR exclusivo) que funciona bit a bit. Para usar potencias hay que utilizar la función pow(). Podrías hacerlo así limitándote a usar la fórmula con potencias y factoriales pero la cantidad de cálculos que va a tener que hacer tu programa va a ser muy grande.

Lo mejor es aprovechar que tu programa es iterativo para ahorrarte operaciones que ya tienes.
Piensa que si en una iteración tienes 1! y lo multiplicas por 2, ya tienes 2!. Si 2! lo multiplicas por 3, ya tienes 3!; y así sucesivamente sin tener que hacer todas las operaciones en cada iteración.
Lo mismo pasa con una potencia: si tienes x y lo multiplicas por x, ya tienes x^2. Si x^2 lo multiplicas por x, ya tienes x^3 y así sucesivamente también.

Ahora tienes que ver cómo ir almacenando estos resultados parciales en cada iteración.
No puedo ser más concreto ahora mismo porque no tengo tiempo.
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

BloodSharp

Esta es la implementación de la función seno multiplataforma de ReactOS, espero que te sirva:

Código (cpp) [Seleccionar]
/*
  * COPYRIGHT:        See COPYING in the top level directory
  * PROJECT:          ReactOS CRT
  * FILE:             lib/sdk/crt/math/sin.c
  * PURPOSE:          Generic C Implementation of sin
  * PROGRAMMER:       Timo Kreuzer (timo.kreuzer@reactos.org)
  */

#ifdef _MSC_VER
#pragma warning(suppress:4164) /* intrinsic not declared */
#pragma function(sin)
#endif /* _MSC_VER */

#define PRECISION 9
#define M_PI 3.141592653589793238462643

static double sin_off_tbl[] = {0.0, -M_PI/2., 0, -M_PI/2.};
static double sin_sign_tbl[] = {1,-1,-1,1};

double
sin(double x)
{
     int quadrant;
     double x2, result;

     /* Calculate the quadrant */
     quadrant = (int)(x * (2./M_PI));

     /* Get offset inside quadrant */
     x = x - quadrant * (M_PI/2.);

     /* Normalize quadrant to [0..3] */
     quadrant = (quadrant - 1) & 0x3;

     /* Fixup value for the generic function */
     x += sin_off_tbl[quadrant];

     /* Calculate the negative of the square of x */
     x2 = - (x * x);

     /* This is an unrolled taylor series using <PRECISION> iterations
      * Example with 4 iterations:
      * result = 1 - x^2/2! + x^4/4! - x^6/6! + x^8/8!
      * To save multiplications and to keep the precision high, it's performed
      * like this:
      * result = 1 - x^2 * (1/2! - x^2 * (1/4! - x^2 * (1/6! - x^2 * (1/8!))))
      */

     /* Start with 0, compiler will optimize this away */
     result = 0;

#if (PRECISION >= 10)
     result += 1./(1.*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20);
     result *= x2;
#endif
#if (PRECISION >= 9)
     result += 1./(1.*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18);
     result *= x2;
#endif
#if (PRECISION >= 8)
     result += 1./(1.*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16);
     result *= x2;
#endif
#if (PRECISION >= 7)
     result += 1./(1.*2*3*4*5*6*7*8*9*10*11*12*13*14);
     result *= x2;
#endif
#if (PRECISION >= 6)
     result += 1./(1.*2*3*4*5*6*7*8*9*10*11*12);
     result *= x2;
#endif
#if (PRECISION >= 5)
     result += 1./(1.*2*3*4*5*6*7*8*9*10);
     result *= x2;
#endif
     result += 1./(1.*2*3*4*5*6*7*8);
     result *= x2;

     result += 1./(1.*2*3*4*5*6);
     result *= x2;

     result += 1./(1.*2*3*4);
     result *= x2;

     result += 1./(1.*2);
     result *= x2;

     result += 1;

     /* Apply correct sign */
     result *= sin_sign_tbl[quadrant];

     return result;
}



B#



Zeta255

lo he modificado con respecto a lo que me has dicho sinceramente no se que hacer ya, lo hago asi segun la formular pero me sigue dando nan no se porque no me calcula nada.



Código (cpp) [Seleccionar]
// Utilizar sumatoria taylor para calcular el valor de SEN con respecto a "X"

#include<iostream>
#include <math.h>
#include<iomanip>

using namespace std;
int main(){
double x, sen,numero, numerador=1, denominador=1,f=0;

cout<<"ingrese el valor de x para sen: "; cin>>x;
for (int i=1; i<=360;i++)
{
sen=x*((i*i)+1);
numero=(-1*i);
numerador= numerador+(sen*numero);
denominador=denominador+((i*i)+(1));
f=f+(numerador/denominador);
}
cout<<"el valor de sen"<<x<<"= "<<f;
}

K-YreX

El problema de que te muestre nan como resultado es porque estás calculando números demasiado grandes. Realizar una serie de Taylor para el sen(x) con una precisión de 360 (como estás calculando tú) es una locura.
Llamando precisión al número de elementos de la serie que se calculan, tenemos lo siguiente:

Elemento 1: denominador = 1! = (1 * 2 - 1)!
Elemento 2: denominador = 3! = (2 * 2 - 1)!
Elemento 3: denominador = 5! = (3 * 2 - 1)!
Elemento 4: denominador = 7! = (4 * 2 - 1)!
...
Elemento 360: denominador = (360 * 2 - 1)! = 719!

Puedes intentar calcular 719! a ver qué pasa...

Una precisión de 10 es más que suficiente para una aproximación decente.
Dicho esto volvamos con el código en sí. La cosa es calcular cada uno de los elementos de la serie correctamente. Empezamos.
La estructura general del programa es la siguiente:
Código (cpp) [Seleccionar]

#include <iostream>
#include <cmath>

using namespace std;

const int PRECISION = 10;

int main() {
  double x = 2; // Un valor cualquiera para calcular sen(x)

  double resultado = 0;
  for(int i = 0; i < PRECISION; ++i) {
    resultado += signo * numerador / denominador;
  }

  cout << "El resultado aproximado de sen(" << x << ") es: " << resultado << endl;
  cout << "El resultado real de sen(" << x << ") es: " << sin(x) << endl;
}

Ahora falta calcular cada una de las variables (signo, numerador, denominador).

Para calcular el signo, como ya te dije, basta con ir multiplicando por -1 en cada iteración.
Código (cpp) [Seleccionar]

//...
int signo = 1; // Empezamos con 1 porque el primer elemento es positivo
for(int i = 0; i < PRECISION; ++i) {
  resultado += signo * numerador / denominador;
  signo *= -1; // Cambiamos el signo a -1 para la siguiente iteracion
}
//...


Ahora para calcular el numerador, como ya dije también, hay que darse cuenta de que en cada iteración está elevado a un exponente 2 veces mayor.

Elemento 1: numerador = x^1 = x ^ (1 * 2 - 1)
Elemento 2: numerador = x^3 = x ^ (2 * 2 - 1)
Elemento 3: numerador = x^5 = x ^ (3 * 2 - 1)
...

La primera opción es calcular:
Código (cpp) [Seleccionar]

for(int i = 0; i < PRECISION; ++i) {
  numerador = pow(x, i * 2 - 1);
  //...
}

Pero esto requiere demasiados cálculos innecesarios. Como ya dije:
Citar
Lo mismo pasa con una potencia: si tienes x y lo multiplicas por x, ya tienes x^2. Si x^2 lo multiplicas por x, ya tienes x^3 y así sucesivamente también.
Entonces la mejor opción es:
Código (cpp) [Seleccionar]

//...
double numerador = x; // El primer numerador es x^1 entonces lo dejamos ya almacenado
for(int i = 0; i < PRECISION; ++i) {
  resultado += signo * numerador / denominador;
  signo *= -1; // Cambiamos el signo a -1 para la siguiente iteracion
  numerador *= (x * x); // Si tenemos x^1 y lo multiplicamos por x^2 (x*x), obtenemos x^3
}
//...


Y ya sólo quedaría el denominador. Vuelvo a mencionar lo que dije en el mensaje anterior:
Citar
Piensa que si en una iteración tienes 1! y lo multiplicas por 2, ya tienes 2!. Si 2! lo multiplicas por 3, ya tienes 3!; y así sucesivamente sin tener que hacer todas las operaciones en cada iteración.
Aquí también podríamos calcular el factorial de (2^n-1) pero volverían a ser un montón de cálculos innecesarios. Vamos a ver cómo funcionan los factoriales otra vez:

Elemento 1: denominador = 1! = (1 * 2 - 1)!
Elemento 2: denominador = 3! = (2 * 2 - 1)! = 1! * 2 * 3
Elemento 3: denominador = 5! = (3 * 2 - 1)! = 3! * 4 * 5
Elemento 4: denominador = 7! = (4 * 2 - 1)! = 5! * 6 * 7
...

Como ves, si en la primera iteración tenemos 1!, luego hay que multiplicar por 2 y por 3 para conseguir 3!. Para conseguir el 5! como ya tenemos 3! sólo hay que multiplicar por 4 y por 5 y así sucesivamente.
Código (cpp) [Seleccionar]

//...
double denominador = 1; // El primer denominador es 1! (1) entonces lo dejamos ya almacenado
for(int i = 0; i < PRECISION; ++i) {
  resultado += signo * numerador / denominador;
  signo *= -1; // Cambiamos el signo a -1 para la siguiente iteracion
  numerador *= (x * x); // Si tenemos x^1 y lo multiplicamos por x^2 (x*x), obtenemos x^3
  denominador *= ((i+2) * 2 - 2) * ((i+2) * 2 - 1);
}
//...

Tenemos que sumar 2 a i porque al empezar en 0, obtendríamos números negativos en las primeras iteraciones. Este valor que se suma a i dependerá del número (i) con el que se empiecen a contar las iteraciones.
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;