Cálculo de Cosseno

Olá galera!

Meu professor lançou um desafio para o cálculo de cosseno que é o seguinte:

Entrada N = número de termos da série depois do 1 (da fórmula do cosseno);
Entrada X = ângulo em graus;

Dado o ângulo x (em graus), calcular o cosseno.

A fórmula é:

[color=green]cos(x)[/color] [color=red]=[/color] [color=blue]1[/color] [color=red]-[/color] [color=blue]x^2/2![/color] [color=red]+[/color] [color=blue]x^4/4![/color] [color=red]-[/color] [color=blue]x^6/6![/color] [color=red]+[/color] [color=blue]x^8/8![/color] [color=red]-[/color] … e assim por diante até chegar ao número de termos que o usuário deseja (Entrada N)

Obs.: O ângulo X na fórmula acima deve ser transformado em radianos para o cálculo.

Eu cheguei ao seguinte código:

import javax.swing.JOptionPane;

public class COSSENO
{ public static void main(String[] args) 
    {

//usuário entra com o número de termos e o ângulo em graus
int num = Integer.parseInt(JOptionPane.showInputDialog("Digite o número de termos da série depois do 1. Quanto mais termos, mais preciso é o resultado."));
double graus = Double.parseDouble(JOptionPane.showInputDialog("Digite o ângulo em graus:"));

//converter os graus para radianos
double rad = (graus*3.14)/180;

int cont = 0;
long fat = 1;
int exp = 2;
double cos = 1;
int operacao = 1;

//condição para o número de termos que o usuário deseja
while (cont < num)
    {
        //fazer o fatorial
        for (int i = 1; i <=exp; i++)  
            {   
                fat *= i; 
            }
         //calcula o cosseno com a operação da vez (soma ou subtração)
        if (operacao == 1)
            {
                cos = cos+operacao*Math.pow(rad, exp)/fat;
                operacao = -1;
             } else
                    {
                        cos = cos+operacao*Math.pow(rad, exp)/fat;
                        operacao = 1;
                    }
          exp += 2;
          cont++;
     }

//subtrai 1 do resultado
cos = 1-cos;

//retorna as informações para o usuário
JOptionPane.showMessageDialog(null, graus+"° "+" = "+rad+" radianos .::. Cosseno de "+rad+" = "+cos);

System.exit(0);

    }
}

O código compila, converte graus em radianos corretamente, mas não retorna o cosseno correto.

Por exemplo, se testarmos com um ângulo de 45° ele retorna 0.785 em radianos e -0.30020 como cosseno, onde o cosseno correto seria 0.70738.

Não consigo identificar o erro no código. Podem me ajudar?

O código parece certo para mim. Tem certeza que a fórmula está correta?

Você pode eliminar o if complemente, já que a operação mesmo se encarrega de somar ou subtrair. Para inverter o sinal da operação sem if, faça simplesmente:
operacao = -operacao;

[quote=ViniGodoy]O código parece certo para mim. Tem certeza que a fórmula está correta?

Você pode eliminar o if complemente, já que a operação mesmo se encarrega de somar ou subtrair. Para inverter o sinal da operação sem if, faça simplesmente:
operacao = -operacao;[/quote]

Sim, a fórmula está correta!

O fatorial estava errado, pois estava fazendo todos antes do cálculo, quando na verdade queremos um por vez.

Dei também uma "secada" no código conforme sua dica, mas ainda não está retornando o valor exato. :?

import javax.swing.JOptionPane;

public class COSSENO
{ public static void main(String[] args) 
    {

//usuário entra com o número de termos e o ângulo em graus
int num = Integer.parseInt(JOptionPane.showInputDialog("Digite o número de termos da série depois do 1. Quanto mais termos, mais preciso é o resultado."));
double graus = Double.parseDouble(JOptionPane.showInputDialog("Digite o ângulo em graus:"));

//converter os graus para radianos
double rad = (graus*3.14)/180;

int cont = 0;
long fat = 1;
int exp = 2;
double cos = 1;
int operacao = -1;

//condição para o número de termos que o usuário deseja
while (cont < num)
    {
        //faz o fatorial
        fat *= exp;
        //calcula o cosseno
        cos = cos+operacao*(Math.pow(rad, exp)/fat);
        //muda o sinal da operação
        operacao=-operacao;
        exp += 2;
        cont++;
     }

//retorna as informações para o usuário
JOptionPane.showMessageDialog(null, graus+"° "+" = "+rad+" radianos .::. Cosseno de "+rad+" = "+cos);

System.exit(0);

    }
}

Obrigado pela ajuda!

O problema ainda está no fatorial. Crie um método separado para calculá-lo. Dica: tente usar recursão.

Outra coisa, se seu professor quer que você calcule o cosseno, creio que seja interessante não usar nenhum método da classe java.lang.Math (senão bastaria usar Math.cos). Crie um método para calcular a potência (tente usar recursão também).

Última coisa: mais importante que a quantidade de vezes que o laço será executado é a precisão do PI. Use ao menos 3.14159265 (do jeito que está ficará muito impreciso).

Em primeiro, a sua função de fatorial ficou errada, principalmente quando você a corrigiu.
O primeiro caso estava mais correto, com a diferença que a variável fatorial deve ser zerada a cada etapa do loop.

Mas tudo ainda parecia certo demais, então resolvi pesquisar um pouco. Primeiro de tudo, o cosseno é definido por:

Ou seja, o resultado não precisa subtrair 1 do resultado, como afirmou seu professor.

Outra coisa, você deve notar que pela formula, a operação começa com -1 e não com 1.

O programa todo corrigido fica assim:

[code]
import javax.swing.JOptionPane;

public class COSSENO {
public static void main(String[] args) {

	// usuário entra com o número de termos e o ângulo em graus
	int num = Integer.parseInt(JOptionPane.showInputDialog(
			"Digite o número de termos da série depois do 1."
			+ "Quanto mais termos, mais preciso é o resultado."));

	double graus = Double.parseDouble(JOptionPane.showInputDialog("Digite o ângulo em graus:"));

	// converter os graus para radianos
	double rad = (graus * 3.14) / 180;

	int exp = 2;
	double cos = 1;
	int operacao = -1;

	// condição para o número de termos que o usuário deseja
	for (int cont = 0; cont < num; cont++) {
		// fazer o fatorial
		long fat = 1;
		for (int i = 1; i <= exp; i++) {
			fat *= i;
		}

		// Calculo do cosseno pela série de Taylor
		cos += operacao * Math.pow(rad, exp) / fat;
		operacao = -operacao;
		exp += 2;
	}

	// retorna as informações para o usuário
	JOptionPane.showMessageDialog(null, graus + "° " + " = " + rad
			+ " radianos .::. Cosseno de " + rad + " = " + cos);
}

}[/code]

[quote=marcobiscaro2112]O problema ainda está no fatorial. Crie um método separado para calculá-lo. Dica: tente usar recursão.

Outra coisa, se seu professor quer que você calcule o cosseno, creio que seja interessante não usar nenhum método da classe java.lang.Math (senão bastaria usar Math.cos). Crie um método para calcular a potência (tente usar recursão também).

Última coisa: mais importante que a quantidade de vezes que o laço será executado é a precisão do PI. Use ao menos 3.14159265 (do jeito que está ficará muito impreciso).[/quote]

Obrigado marcobiscaro2112!

Realmente o problema estava no fatorial, como também diz o ViniGodoy.

Recursão também daria certo, mas como não foi passado em aula, preferi não usar. E quanto ao Math.pow() e outros métodos da classe java.lang.Math, o professor disse que não teria problema em usá-lo, claro, é só não usar o Math.cos(), uma vez que é a essência do desafio.

[quote=ViniGodoy]Em primeiro, a sua função de fatorial ficou errada, principalmente quando você a corrigiu.
O primeiro caso estava mais correto, com a diferença que a variável fatorial deve ser zerada a cada etapa do loop.

Ou seja, o resultado não precisa subtrair 1 do resultado, como afirmou seu professor.

Outra coisa, você deve notar que pela formula, a operação começa com -1 e não com 1.[/quote]

Obrigado ViniGodoy!

Muito bem observado. O código que você enviou está correto!

Eu apenas mudei o cálculo da conversão de graus para radianos a fim de conseguir um resultado mais preciso.

Vou postar o código final abaixo para ajudar outro programador que tenha a mesma dúvida:

import javax.swing.JOptionPane;

public class COSSENO
{   public static void main(String[] args)
        {  
  
// usuário entra com o número de termos e o ângulo em graus  
int num = Integer.parseInt(JOptionPane.showInputDialog("Digite o número de termos da série depois do 1. Quanto mais termos, mais preciso é o resultado."));  
double graus = Double.parseDouble(JOptionPane.showInputDialog("Digite o ângulo em graus:"));  
  
// converter os graus para radianos  
double rad = Math.toRadians(graus);
  
int exp = 2;  
double cos = 1;  
int operacao = -1;  
  
// condição para calcular de acordo com o número de termos que o usuário deseja  
for (int cont = 0; cont < num; cont++)
    {  
        // cálculo do fatorial 
        long fat = 1;  
        for (int i = 1; i <= exp; i++)
            {  
                fat *= i;  
            } 
            // cálculo do cosseno
            cos += operacao * Math.pow(rad, exp) / fat;  
            operacao = -operacao;  
            exp += 2;  
    }
  
// retorna as informações para o usuário  
JOptionPane.showMessageDialog(null, graus + "° " + " = " + rad + " radianos .::. Cosseno de " + rad + " = " + cos);

System.exit(0);

    }  
}

Obrigado pela atenção e pelas respostas!

Usando a mesma série, pode-se calcular o coseno usando BigDecimal.

a) Digam por que é que não preciso usar fatorial aqui
b) Se o número x for grande, o número de termos será maior. Por quê?
c) Por que é que eu somo os números de trás para frente, requerendo um ArrayList? Eu preciso de um arraylist neste caso? Veja se é necessário eu somar os números de trás para frente. Confira a precisão com alguma outra biblioteca que calcule as funções trigonométricas com precisão arbitrária, e diga por que é que estou usando uma suposição ingênua aqui.

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

class BDCoseno {
    public static BigDecimal cos (BigDecimal x, int scale) {
        List<BigDecimal> fat = new ArrayList<BigDecimal>();
        BigDecimal f = BigDecimal.ONE;
        fat.add (f);
        int n = 1;
        do {
            f = f.negate().multiply(x.pow(2)).divide(new BigDecimal ((2 * n) * (2 * n - 1)), scale, RoundingMode.HALF_EVEN);
            n++;
            fat.add (f);
        } while (f.abs().compareTo(f.ulp()) > 0);
        BigDecimal res = BigDecimal.ZERO;
        for (int i = fat.size() - 1; i >= 0; i--) {
            res = res.add (fat.get(i));
        }
        return res;
    }
    public static void main (String[] args) {
        double x;
        for (int i = 0; i <= 360; ++i) {
            x = Math.toRadians (i);
            System.out.println (i + "-> " + cos (new BigDecimal (x), 30) + " " + Math.cos (x));
            System.out.println();
        }
    }
}

[quote=ViniGodoy]Primeiro de tudo, o cosseno é definido por:
[/quote]
A forma inicial também funciona. Um algoritmo utilizando-a (e usando recursão em alguns métodos):

import javax.swing.JOptionPane;

public class Cosseno {

	private static final double PI = 3.14159265;

	public static void main(String[] args) {
		int num = Integer
				.parseInt(JOptionPane
						.showInputDialog("Digite o número de termos da série depois do 1. Quanto mais termos, mais preciso é o resultado."));
		double graus = Double.parseDouble(JOptionPane
				.showInputDialog("Digite o ângulo em graus:"));

		double rad = emRadianos(graus);

		JOptionPane.showMessageDialog(null, graus + "° " + " = " + rad
				+ " radianos .::. Cosseno de " + rad + " = "
				+ cosseno(rad, num));
	}

	public static double emRadianos(double graus) {
		return graus * PI / 180.0;
	}

	public static double emGraus(double radianos) {
		return radianos * 180.0 / PI;
	}

	public static int fatorial(int valor) {
		return valor < 2 ? valor : valor * fatorial(valor - 1);
	}

	public static double potencia(double base, int expoente) {
		return expoente < 2 ? base : base * potencia(base, expoente - 1);
	}

	public static double cosseno(double radianos, int precisao) {
		double cosseno = 1;
		int operacao = -1;
		for (int cont = 0, expoente = 2; cont < precisao; cont++, expoente += 2) {
			cosseno += operacao * potencia(radianos, expoente)
					/ fatorial(expoente);
			operacao = -operacao;
		}
		return cosseno;
	}

}
public static int fatorial(int valor) {  
        return valor < 2 ? valor : valor * fatorial(valor - 1);  
  }  

cuidado. fatorial (1)= fatorial(0) e nãoexiste fatorial de inteiros negativos. Logo
Deveria ser

public static int fatorial(int valor) {  
            if(valor<0){
                thow new IllegalArgumentException();
            } else if (valor < 2 ){
             return 1;
            } 
             
        return valor * fatorial(valor - 1);  
  }  

contudo, a versão não recursiva é melhor opção.

    public static double potencia(double base, int expoente) {  
        return expoente < 2 ? base : base * potencia(base, expoente - 1);  
    }  

Mais uma vez problema com expoente negativo e o fato de algo elevado a zero é 1.

    public static double potencia(double base, int expoente) {  
            if(valor < 0){
                thow new IllegalArgumentException();
            } else if (expoente == 0 ){
                   return 1;
            } else if (expoente == 1 ){
                   return base;
            } else if (expoente == 2 ){
                   return base * base ; // muito comum e termina a recursividade mais cedo.
            }
            return  base * potencia(base, expoente - 1);  
    }  

[quote=sergiotaborda] public static int fatorial(int valor) { return valor < 2 ? valor : valor * fatorial(valor - 1); }

cuidado. fatorial (1)= fatorial(0) e nãoexiste fatorial de inteiros negativos. Logo
Deveria ser

public static int fatorial(int valor) {  
            if(valor<0){
                thow new IllegalArgumentException();
            } else if (valor < 2 ){
             return 1;
            } 
             
        return valor * fatorial(valor - 1);  
  }  

contudo, a versão não recursiva é melhor opção.

    public static double potencia(double base, int expoente) {  
        return expoente < 2 ? base : base * potencia(base, expoente - 1);  
    }  

Mais uma vez problema com expoente negativo e o fato de algo elevado a zero é 1.

    public static double potencia(double base, int expoente) {  
            if(valor < 0){
                thow new IllegalArgumentException();
            } else if (expoente == 0 ){
                   return 1;
            } else if (expoente == 1 ){
                   return base;
            } else if (expoente == 2 ){
                   return base * base ; // muito comum e termina a recursividade mais cedo.
            }
            return  base * potencia(base, expoente - 1);  
    }  

[/quote]
Apesar de eu estar ciente esqueci de ressaltar isso. Esse algoritmo funciona dentro desse contexto (onde sempre haverá números positivos). Uma verificação mais completa é necessária caso isso fosse ser aplicado em uma API, por exemplo. E, nesse caso, deveria ser implementado também a exponenciação para expoentes negativos.

Esses seriam métodos mais realísticos (diferentemente dos simplificados que eu postei lá em cima):

	public static int fatorial(int valor) {
		if (valor < 0) {
			throw new IllegalArgumentException("valor < 0");
		}
		if (valor < 2) {
			return 1;
		}
		int fatorial = valor;
		for (int i = 1; i < valor; i++) {
			fatorial *= valor - i;
		}
		return fatorial;
	}

	public static double potencia(double base, int expoente) {
		if (expoente == 0) {
			if (base == 0) {
				return Double.NaN;
			}
			return 1;
		}
		if (base == 0) {
			return 0;
		}
		if (expoente == 1) {
			return base;
		}
		if (expoente < 0) {
			base = 1.0 / base;
			expoente = -expoente;
		}
		double resultado = 1;
		for (int i = 0; i < expoente; i++) {
			resultado *= base;
		}
		return resultado;
	}

Na prática, quando se vai calcular séries, evita-se usar potenciação, fatoriais e outras expressões repetitivas e lentas.

Costuma-se definir um termo em função de outro termo anterior.

No método que postei, é possível ver que um termo da somatória é igual ao termo anterior, vezes -x*x/(2n (2n-1)).

Algumas coisas que não entendi no seu algoritmo :

Em tese não. então porquê fazer desse jeito sendo que linked list é mais rápido para o caso insere+itera

P.S. melhor ainda, porque não ir somando diretamente, poupando o for no fim ?

Porque vc itera enquanto a condição seguinte é verdadeira ?

while (f.abs().compareTo(f.ulp()) > 0);

Isso significa que o processo itera até que não haja alteração significativa no numero, mas não é isso demasiada “precisão” ?

Teria alguma referencia sobre como transformar series de taylor em multiplicação de termos de uma série diferente (transformar um somatório num multiplicatório) ?

No caso específico de usar BigDecimal (onde a precisão é constante e dada pela escala), então não faz muito sentido eu somar os números de trás para frente. Verifiquei que o resultado é exatamente o mesmo que eu já ir acumulando os termos, sem usar um List<>.

Mas digamos que eu use um número double (ou se usar o mesmo algoritmo em Assembly ou em uma linguagem onde os resultados intermediários sejam em 80 ou 82 bits, em vez de 64 bits - como o compilador g++ para C++).

Neste caso, estarei somando primeiro os números que têm magnitude menor, e depois os números que têm magnitude maior. Aí terei um resultado mais exato.