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

16 comentarios:

Marcelo dijo...

Buenos Dias!
Me encuentro en el dilema de que tengo la formula de calular pi, pero lo que quiero es "mostrar" los 500 decimales, no calcularlos, se entiende?
Para ello tengo eso, pero se que me falta algo...
Los double por ser primitivos, solo mostraran 15 decimales y el BigDecimal, solo 50.

public class NumeroPI
{
public static int cant=50000;
public static void main (String args[])
{
int i;
double x=0, y = 0, z=0;

for(i=0; i<cant; i++)
{
x = Math.pow((-1),i) / ((2*i)+1);
//System.out.println(x+" "+i);
y = y + x;
//System.out.println(y+" "+i);
}
z = y *4;
System.out.println(y);
System.out.println("El valor de PI es: "+z);

}
}

espero tu respuesta a la brevedad.
Marcelo, Chile!

Kelvin dijo...

Creo que no has entendido el artículo, para que un número te muestre el valor con 500 decimales debes utilizar la clase BigDecimal y hacer los cálculos con la clase BigDecimal. Igual en tu código estás utilizando la ecuación de Leibniz tu haces el cálculo de PI, sólo que lo haces con double (insuficiente para tantos decimales). Yo no he sabido como hacerlo con double (si tienes una solución espero que la compartas con nosotros). Según mi experiencia te digo: Hazla con BigDecimal para que te de mayor precisión utilizando MathContext de 500 decimales de precisión.
Por otra parte, si solo quieres mostrar los decimales utiliza la función de cadena substring(2), que te elimina el 3 y el punto o coma decimal.
Espero te ayude. Saludos

Ayumi Shiroyama dijo...

*O* gracias por explicarme ;) pensé que no me responderías n.n... muchas gracias, tenía la duda con ciertos parametros, pero tu explicación me lo aclaro, de verdad muchas gracias por atender mi consulta!!...

Kelvin dijo...

Ayu Shiroyama: Estamos para servir cuando se pueda.

Acdel dijo...

Saludos!!!
Ya que double usa aritmetica binaria para sus calculos, al utilizar operaciones de calculo primitivas sobre valores flotantes (al momento de definr "div"), no estamos perdiendo presicion? existe algun motivo para realizar esos calculos con double y no hacerlos también con BigDecimal?

Kelvin dijo...

Saludos acdel:
Sabes tienes mucha razón, una razón por la que usé double en vez de BigDecimal fue que generalmente div almacenaba valores enteros grandes, pensé que double podría soportar estos valores grandes.

Luego al realizar visualizaciones con datos reales, pude percibir que en verdad div llega a tener valores bastante grandes y por lo tanto arrojó diferencias pequeñas cuando utilicé BigDecimal.

Esto me llevó a la conclusión de que debido a que BigDecimal tiene mayor precisión para los cálculos es bueno hacerlo con BigDecimal.

Por lo tanto preparé un método llamado calcularDiv de la siguiente manera:

static BigDecimal calculoDiv(double x, int i){
MathContext mc = new MathContext(500);
BigDecimal div = new BigDecimal(1, mc);
int factor = 2*i+1;
div = div.multiply( new BigDecimal(factor, mc) )
.multiply((new BigDecimal(1.00, mc)
.divide(new BigDecimal(x, mc), mc )).pow(factor, mc), mc);
return div;
}

Este método se debe llamar cuando se calcule div y cambiar div a BigDecimal.

BigDecimal div = calculoDiv(x, i);

y esta conversión ya no haría falta:

BigDecimal divisor = new BigDecimal(div);

Gracias por tu comentario atento.

Carlos Celis dijo...

Hubieras complementado el artículo con el valor de PI con 50 decimales, ya que de él existen millones y todavía no hay una máquina que pueda calcularlos hasta el fin... si es que lo hay

Saludos Kelvin

Kelvin dijo...

No entiendo tu planteamiento Carlos Celis, ya que si existen algoritmos que han calculado PI con hasta millones de decimales.

Creo que debes investigar un poco más:

Visita este enlace:

Calculo de PI

Anónimo dijo...

la unica forma de implementarlo es usando vectores ,operar y mostrar el resultado en vectores ,ya que los valores primitivos poseen un rango limitado

Anónimo dijo...

hola!
tengo dudas y si tubieras que hacer el calculo de la exponenciación R^n
ejemplo R = 0.4321 Y n = 20 se tendria como resultado: 0.000000051485546410769561219945112767
67154838481760200726351203835429763013462401 pero lo que muestra es: 5.148554641076956121994511276767154838481760
200726351203835429763013462401E-8
¿Como hacer para que el resultado se muestre sin redondeo?
espero tu respuesta.

Kelvin dijo...

La manera que he encontrado para mostrar resultado de esa forma es a través de la clase DecimalFormat de la siguiente manera:

DecimalFormat df = new DecimalFormat("0.000000000000000000000000000000000000000000000");
double potencia = Math.pow(0.4321, 20);
System.out.println(df.format(potencia));

Espero eso te ayude.

Kelvin dijo...

Si quieres mayor presición podrías realizar el Calculo con BigDecimal y no con Math.pow y mostrar el resultado con DecimalFormat, de la siguiente manera:

DecimalFormat df = new DecimalFormat("0.0000000000000000000000");
BigDecimal bd = new BigDecimal(0.4321);
bd = bd.pow(20);
System.out.println(df.format(bd));

Anónimo dijo...

Gracias por responder, me sirvio mucho el DecimalFormat y el big decimal. Pero quisiera saber si ROUND_UNNECESSARY me serviría para realizar el ejercicio sin redondeo.

Kelvin dijo...

Anónimo:
* BigDecimal.ROUND_UNNECESARY se utiliza en caso de que tengas un resultado exacto para que el redondeo no sea necesario, en este caso tu trabajas con numeros decimales y el resultado necesita redondeo por lo tanto este método no te funcionará.

Unknown dijo...

muchas gracias kelvin por tu aporte soy nuevo en programación java y me ah servido de mucho tu articulo

Jose Ceron dijo...

Muchas gracias es muy interesante