lunes, 31 de mayo de 2010

El cálculo de PI con 500 decimales

Saludos de nuevo compañeros. Hoy tenemos un nuevo reto el cálculo de PI hasta de 500 decimales, esto como respuesta a un comentario que fue realizado en el post sobre Interfaces Genéricas, si ya sabemos, no tiene nada que ver, pero en ese post fue realizada la duda y en este post UNA posible solución.

Muchos métodos han sido utilizados para calcular el Valor de PI, hoy en día existen algoritmos que generan con gran precision este valor, En la clase Math existe una constante PI de tipo double que contiene el valor literal


public static final PI = 3.141592653589793d;


Pero por supuesto, es insuficiente para cálculos avanzados. No nos da mayor precisión, allí termina.

De hecho cuando he trabajado con los valores primitivos double he tenido dificultades para obtener mayor precisión, soy "todo oído" para soluciones.

Pero gracias a Dios contamos con la clase BigDecimal que permite obtener mayor precisión en el cálculos numérico con decimales, a pesar de que los cálculos son mas limitados.

Si quiere consultar la clase BigDecimal haga clic aqui.

En esta clase contamos con un constructor que recibe un MathContext que en su constructor recibe un parámetro que dice cual es la precisión con la que queremos trabajar en este caso yo paso 500 así:


MathContext mc = new MathContext(500);


Aunque en realidad me da mayor precisión que esa.

Ahora bien es hora de hablar de métodos de cálculo de PI.

Primero que todo debemos hablar de la Serie de Taylor.

Esta propone una serie para el cálculo de el arcotangente:



Siendo así podemos programar un método que calcule el arcotangente de un valor con la Serie de Taylor:


private static BigDecimal serieArcotangente
(double x, int iteraciones) {
MathContext mc = new MathContext(500);
BigDecimal result = new BigDecimal(0.00, mc);
BigDecimal uno = new BigDecimal(1.00);
for(int i = 0; i < iteraciones; i++){
double div = (2.00*i+1.00) *
(Math.pow(1.00/x, 2.00 * i + 1.00));
BigDecimal divisor = new BigDecimal(div);
if(i % 2 == 0){
result = result.add(uno.divide(divisor, mc));
}else{
result = result.subtract(uno.divide(divisor, mc));
}
}
return result;
}


Explicando un poco este método, tenemos que retorna un BigDecimal para mayor precision de PI, tenemos MathContext de 500 decimales de precision. Inicializamos un BigDecimal en cero pasandole este MathContext y un BigDecimal inicializado en 1.0 que servirá para el cálculo.

Después comenzamos las iteraciones (sentencia for), calculamos un double divisor (divisor de la Serie de Taylor) y lo inicializamos como otro BigDecimal, luego tenemos:


if(i % 2 == 0){
pi = pi.add(uno.divide(divisor, mc));
}else{
pi = pi.subtract(uno.divide(divisor, mc));
}

Que significa que si i es par sumo, sino resto, ya que -1 en la serie está elevada a la n (si n es par se convierte en positivo - se suma - sino es negativo y por lo tanto se resta). Además se divide uno entre el divisor con el MathContext de 500 decimales de precisión.

Una vez hecho esto podemos ver las de arcotangentes desarrolladas por algunos matemáticos para calcular el valor de PI:

Haciendo historia, en 1844, Dase, un calculista ultrarrápido, utilizó otra fórmula del tipo arcotangente para conseguir una aproximación con 200 decimales correctos. La fórmula descubierta por Strassnitzky es:

PI/4 = arctan(1/2) + arctan(1/5) + arctan(1/8)

En programación sería:


public static BigDecimal calculoPiDase(){
BigDecimal pi = serieArcotangente(1.00/2.00, 100)
.add(serieArcotangente(1.00/5.00, 100))
.add(serieArcotangente(1.00/8.00,100))
.multiply(new BigDecimal(4.00));
return pi;
}


El gran matemático alemán, Karl Friedrich Gauss (1777-1855), también descubrió algunas fórmulas similares a las anteriores. Una de las más utilizadas ha sido:

PI/4 = 12*arctan(1/18) + 8*arctan(1/57) - 5*arctan(1/239)

este sería así:


public static BigDecimal calculoPiGauss(){
BigDecimal pi = serieArcotangente(1.00/18.00, 60)
.multiply(new BigDecimal(12.00))
.add(serieArcotangente(1.00/57.00, 60)
.multiply(new BigDecimal(8.00))
.subtract(serieArcotangente(1.00/239.00, 60)
.multiply(new BigDecimal(5.00)))
);
return pi.multiply(new BigDecimal(4.00));
}


Y otra fórmula similar descubierta por Störmer (1896):

PI/4 = 6*arctan(1/8) + 2*arctan(1/57) + arctan(1/239)


public static BigDecimal calculoPiStormer(){
BigDecimal pi = serieArcotangente(1.00/8.00, 60)
.multiply(new BigDecimal(6.00))
.add(serieArcotangente(1.00/57.00, 60)
.multiply(new BigDecimal(2.00))
.add(serieArcotangente(1.00/239.00, 60)));
return pi.multiply(new BigDecimal(4.00));
}


Bueno por si acaso, ahora voy a estructurar bien el codigo:



public class PIPreciso {
public static void main(String[] args) {
System.out.println("DASE: "+calculoPiDase());
System.out.println("GAUSS: "+calculoPiGauss());
System.out.println("STORMER: "+calculoPiStormer());
}

public static BigDecimal calculoPiDase(){
//Este codigo ya fue visto
}

public static BigDecimal calculoPiGauss(){
//Este codigo ya fue visto
}

public static BigDecimal calculoPiStormer(){
//Este codigo ya fue visto
}

private static BigDecimal serieArcotangente
(double x, int iteraciones) {
//Codigo ya visto
}
}


Ya tenemos tres métodos para calcular PI con 500 decimales o mas, espero esto haya podido ayudarte. Cualquier pregunta no dudes en escribir aquí o en cualquier post. Hasta Luego

miércoles, 19 de mayo de 2010

Hablemos de intefaces genéricas de java 5.0

   Así como vimos en un artículo anterior (Enero 2009 - tipos genéricos) podemos crear clases genéricas. Ahora, y como respuesta a un comentario que expuso la duda, hablaremos de interfaces genéricas.
Las interfaces genéricas se declaran de la "misma manera" que una clase genérica, veamos la sintaxis general:


interface nombre-interfaz<T1, T2, T3...>{
metodo1();
metodo2();…
}


Donde T1, T2, T3... son los tipos parametrizados.
Aunque no siempre se así, una clase que implementa esta interfaz debe ser en forma general:



class nombre-clase<T1, T2, T3...>
implements nombre-interfaz<T1, T2, T3...>{
   metodo1Implementado(){}
   metodo2Implementado(){}…
}


Pero como les dije esto no es siempre veamos con un ejemplo cuales son las excepciones:
Supongamos que queremos crear una interfaz para verificar si un valor está contenido en un arreglo, la interfaz sería:


interface Contenedora<T>{
   boolean contiene(T valor);
}

El método contiene debe devolver verdadero o falso en caso de que el arreglo contenga o no el valor.
La clase que implementa puede ser:



class Verificadora<T> implements Contenedora<T> {
T[] datos;

public Verificadora(T[] x){
datos = x;
}

public boolean contiene(T dato) {
for(T valor: datos)
if(valor.equals(dato)) return true;
return false;
}
}


Hagamos un ejemplo con una clase Main:


public class Main {
public static void main(String[] args) {
Integer[] x = {0,1,2,3,4};
Verificadora<Integer>; ver =
new Verificadora<Integer>(x);
if(ver.contiene(2)){
System.out.println("Si tiene 2");
}else{
System.out.println("NO tiene 2");
}
if(ver.contiene(6)){
System.out.println("Si tiene 6");
}else{
System.out.println("NO tiene 6");
}
/*if(ver.contiene(9.333)){ //error porque no es Integer
System.out.println("Si tiene 9.333");
}else{
System.out.println("NO tiene 9.333");
}*/
}
}


La interfaz Contenedora no limita el tipo de dato con que trabajará, puede ser Integer, Double, Float o inclusive String, cualquier Object en general, por lo tanto podemos utilizar el método equals de esta clase (Object), tal como lo hace la clase verificadora en el método contiene().

Ahora veamos varios detalles más con respecto a las interfaces genéricas:

Si una clase implementa una interfaz genérica, la clase debe ser genérica a menos que la interfaz defina el tipo de dato parametrizado con el que va a trabajar. Vamos por parte, si la interfaz no define el tipo de dato parametrizado, entonces la clase debe ser genérica:

class Verificadora<T> implements Contenedora<T>{//Bien


Es correcto ya que la clase utiliza el mismo parámetro de que la interfaz.

Sería un error:

class Verificadora implements Contenedora<T>{
//Error de compilación


Debido a que como la interfaz es genérica la clase debe ser genérica.

Pero si la interfaz trabaja con String, es decir que define el parámetro:

class Verificadora implements Contenedora<String>{//Bien


Entonces la clase no debe ser genérica necesariamente, ya que el tipo de dato lo define la interfaz.

También, así como la clase, tal como lo vimos en el artículo de tipos parametrizados, puedo limitar el tipo de dato con que trabajará la interfaz. En caso que queramos trabajar con números podemos hacer:


interface Contenedora<T extends Number>{
boolean contiene(T valor);
}


Si lo hacemos de esta manera la clase debe implementarse así:

class Verificadora<T extends Number> 
implements Contenedora<T>{//Bien


ya no se requerirá repetir el tipo de dato de la interfaz, de hecho, si se hace se caería en un error de compilación:

class Verificadora<T extends Number> 
implements Contenedora<T extends Number>{//Error