Tabla de contenidos
Recomendamos a los estudiantes programar con un estilo que siga las convenciones habituales del lenguaje Java. El documento Directrices de Programación para Java presenta de forma breve las convenciones más importantes así como instrucciones para configurar Eclipse conforme a las mismas.
Los depuradores son herramientas de programación que permiten ejecutar de forma controlada un programa, típicamente con el fin de probarlo, encontrar la causa de un error o comprender mejor su funcionamiento.
Algunas de las funciones más habituales que proporcionan los depuradores son:
Ejecutar el programa paso a paso (esto es, detener el programa cada vez que este alcance la siguiente línea de código fuente). Tras cada paso, el usuario recupera el control.
Detener la ejecución del programa cuando este alcance una determinada línea del código fuente. A partir de este momento, el usuario recupera el control.
Cuando la ejecución se encuentra detenida, conocer el valor de una variable o expresión en ese momento.
El entorno de desarrollo Eclipse integra un depurador de Java que puede resultar muy práctico para probar tus programas y, si es el caso, encontrar la causa de que no funcionen.
El siguiente programa permite calcular el valor de pi con la precisión (número de dígitos) que se le indique como argumento de línea de comandos:
import java.math.BigDecimal; import java.math.MathContext; public class PiCalc { private int numDigits; private MathContext mc; public PiCalc(int numDigits) { this.numDigits = numDigits; mc = new MathContext(numDigits); } public BigDecimal compute() { BigDecimal pi = new BigDecimal(0); BigDecimal limit = new BigDecimal(1).movePointLeft(numDigits); boolean stop = false; for (int k = 0; !stop; k++) { BigDecimal piK = piFunction(k); pi = pi.add(piK); if (piK.compareTo(limit) < 0) { stop = true; } } return pi.round(mc); } private BigDecimal piFunction(int k) { int k8 = 8 * k; BigDecimal val1 = new BigDecimal(4); val1 = val1.divide(new BigDecimal(k8 + 1), mc); BigDecimal val2 = new BigDecimal(-2); val2 = val2.divide(new BigDecimal(k8 + 4), mc); BigDecimal val3 = new BigDecimal(-1); val3 = val3.divide(new BigDecimal(k8 + 5), mc); BigDecimal val4 = new BigDecimal(-1); val4 = val4.divide(new BigDecimal(k8 + 6), mc); BigDecimal val = val1; val = val.add(val2); val = val.add(val3); val = val.add(val4); BigDecimal multiplier = new BigDecimal(16); multiplier = multiplier.pow(k); BigDecimal one = new BigDecimal(1); multiplier = one.divide(multiplier, mc); val = val.multiply(multiplier); return val; } public static void main(String[] args) { if (args.length != 1) { System.err.println("One command-line argument expected: number of " + "digits."); } else { PiCalc piCalc = new PiCalc(Integer.parseInt(args[0])); System.out.println(piCalc.compute()); } } }
Echa un vistazo general al programa sin preocuparte demasiado por los detalles, y contesta a las siguientes preguntas:
¿Cuál es el flujo del programa? Esto es, qué métodos se ejecutan y en qué orden. ¿Es posible que se ejecute alguno de los métodos más de una vez?
¿Por qué crees que se está usando la clase BigDecimal
en vez del tipo de datos float
o
double
? Busca información en la API de Java o en
otros recursos de la Web para contestar a esta pregunta.
El programa comienza su ejecución en el método
main
. Este construye un objeto de la clase
PiCalc
e invoca sobre él el método
compute()
. En este último, se ejecuta un bucle en
el cual en cada iteración se invoca al método privado
piFunction(int)
. Por tanto, es posible que este
último se ejecute varias veces (tantas como veces se ejecute
el bucle).
Los tipos de datos float
y double
tienen una precisión limitada, sobre todo el primero. Como en
el programa se desea calcular pi con un número arbitrario de
decimales, ninguno de estos tipos de datos nos vale. Sin
embargo, la clase BigDecimal
proporciona tanta precisión como necesitemos. De todas formas,
usar esta clase tiene también un coste: como se ve, el código
fuente es más enrevesado y difícil de entender, y los cálculos
requieren más esfuerzo por parte del ordenador que cuando se
hacen con los tipos float
y double
.
Importa el programa en Eclipse y ejecútalo varias veces, cada una con un número de dígitos distinto. Observa que tarda más cuantos más dígitos pides. La causa de esto la comprenderás cuando analices el código en mayor detalle en el próximo apartado.
Utiliza el depurador de Eclipse para profundizar en tu
conocimiento de cómo funciona el programa. Para ello, coloca un
punto de ruptura (breakpoint) en la primera
línea del método compute()
(menú Run /
Toggle Breakpoint). Ejecuta el programa, pero con la
acción Debug en vez de
Run. El programa comenzará a ejecutarse y
se detendrá cuando alcance la línea en que has puesto el punto
de ruptura.
La interfaz gráfica de Eclipse entrará, tras preguntarte, en una
perspectiva llamada perspectiva de
depuración, que muestra información y controles
específicos para la depuración de un programa. En esta
perspectiva puedes ver el código fuente, en el cual se resalta
la línea donde se ha quedado detenido el programa. Esta línea
aún no ha sido ejecutada. Es la primera que se ejecutará si
reanudas el programa. También puedes ver el valor de las
variables (de momento, sólo muestra this
, que se
refiere al objeto al cual pertenece el método en que está
detenido el programa; si lo despliegas, verás sus
atributos). También se muestra la consola.
Una vez el programa está detenido, puedes reanudar su ejecución de varias formas:
Opción Run / Resume: reanuda el programa, hasta que finalice o hasta que llegue de nuevo a un punto de ruptura.
Opción Run / Step Into: ejecuta la siguiente línea del programa. Si en esta línea se está invocando a un método, se detiene en la primera línea de dicho método, para que lo puedas ejecutar paso a paso.
Opción Run / Step Over: ejecuta la siguiente línea del programa. Si en esta línea se está invocando a un método, se ejecuta dicho método completo en un solo paso. Esto es útil si no te interesa depurar el código de ese método.
Opción Run / Step Return: ejecuta el método actual hasta el final, y se detiene en la línea siguiente a la que invocó al método actual.
También puedes cancelar en cualquier momento la ejecución del programa mediante la opción Run / Terminate.
Depura el programa con argumento de línea de comandos 10. Cuenta cuántas veces se ejecuta el bucle del método compute() antes de obtenerse el valor de pi definitivo. Haz lo mismo con un número de dígitos un poco mayor. ¿Qué observas?
Vuelve a depurar el programa y observa cómo evoluciona el valor de la variable piK en cada iteración del bucle. ¿Qué le pasa a este valor a medida que se ejecuta el bucle?
Basándote en lo que has observado hasta ahora, y depurando el
programa de nuevo si lo crees necesario, ¿qué papel desempeña la
variable limit
en el programa?
Para calcular 10 dígitos, el bucle se ejecuta 8 veces. A medida que se aumenta el número de dígitos a calcular, el bucle se ejecuta más veces.
La variable piK
toma cada vez un valor más
pequeño. Estos son los primeros valores que va tomando:
El valor de pi
se calcula como la suma de los
sucesivos valores de piK
. En cada iteración del
bucle, el valor de pi
es más preciso y los
dígitos más significativos se van estabilizando en su valor
definitivo:
La variable limit
se inicializa al valor 10
elevado a menos número de dígitos deseado. Por ejemplo, si se
piden dos dígitos, se inicializa a 0,01. Este valor se compara
con piK
en cada iteración del bucle. Cuando
piK
se hace menor que limit
, se
asume que los dígitos deseados tienen ya un valor estable que
no cambiará, por lo que se puede parar el bucle y dar for
finalizado el cómputo. El hecho de que limit
es
más pequeño cuantos más dígitos de pi sean necesarios es la
causa de que el bucle se ejecute más veces.
Observando el código fuente, y usando el depurador si te resulta útil, intenta deducir la fórmula para el cómputo de pi que se está utilizando. Escríbela con notación matemática.
Analizando con cuidado el programa, se puede observar que se está calculando pi como:
pi = SUMATORIO de k=0 a infinito de 16^(-k) * [ 4/(8k+1) - 2/(8k+4) - 1/(8k+5) - 1/(8k+6) ]
Esta es la fórmula Bailey-Borwein-Plouffe. Utilizando base 16 en vez de base 10, hay formas de aplicarla mejores que la que hemos programado para este ejercicio.
Escribe un programa con una nueva clase que calcule el área de
un círculo dado su radio. El programa tendrá un método
main
que recibirá como argumentos de línea de
comandos el número de dígitos de pi con que se deben realizar
los cálculos y el radio del círculo. Escribirá en su salida
estándar el área calculada.
Tu programa debe utilizar la clase PiCalc
de los
apartados anteriores para obtener el valor de pi a usar en los
cálculos. Utiliza también la clase BigDecimal
para realizar los cálculos.
import java.math.BigDecimal; public class CircleAreaCalc { private BigDecimal pi; public CircleAreaCalc(int numDigits) { PiCalc piCalc = new PiCalc(numDigits); pi = piCalc.compute(); } public BigDecimal area(BigDecimal radius) { BigDecimal area = radius.pow(2); area = area.multiply(pi); return area; } public static void main(String[] args) { if (args.length != 2) { System.err.println("Two command-line arguments expected: number of " + "digits and circle radius."); } else { int numDigits = Integer.parseInt(args[0]); BigDecimal radius = new BigDecimal(args[1]); CircleAreaCalc circleAreaCalc = new CircleAreaCalc(numDigits); System.out.println(circleAreaCalc.area(radius)); } } }
Escribe un programa con una nueva clase que calcule la suma de
las áreas de varios círculos dados sus radios. El programa
tendrá un método main
que recibirá como argumentos
de línea de comandos el número de dígitos de pi y, a
continuación, el radio de un número arbitrario de círculos, cada
uno como un argumento de línea de comandos. Escribirá en su
salida estándar la suma de las áreas.
Tu programa debe utilizar la clase programada en el apartado anterior, así como la clase BigDecimal para realizar los cálculos.
import java.math.BigDecimal; public class ManyCirclesAreaCalc { public static void main(String[] args) { if (args.length < 1) { System.err.println("At least one argument expected: " + "number of digits for pi."); } else { int numDigits = Integer.parseInt(args[0]); BigDecimal total = new BigDecimal(0.0); CircleAreaCalc circleAreaCalc = new CircleAreaCalc(numDigits); for (int i = 1; i < args.length; i++) { total = total.add(circleAreaCalc.area(new BigDecimal(args[i]))); } System.out.println(total); } } }