Table of Contents
We advise the students to program according to usual Java conventions. The document Java Coding Guidelines presents a brief introduction to the most important conventions, as well as instructions on how to configure Eclipse according to them.
A debugger is a program to test other programs in a controlled way. A debugger allows finding errors in programs (bugs) and help to understand them.
Common functionalities of a debugger:
Run a program step by step (this is, the program will execute one source code statement and then wait for further instructions).
Stop the execution of a program at a given line of source code and wait for further user instructions.
Allow to inspect the values of the program variables while its execution is stopped.
The Eclipse IDE includes a nice Java debugger that can help you to find and fix bugs in your programs.
The following program computes the value of pi with the desired precision (the number of desired decimal places is passed as a command argument).
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()); } } }
Take a look at its source code without paying too much attention to its details and answer the following questions.
Can you describe the program flow? This is, what methods are invoked and in which order? Is there any method that is being executed more than once?
Justify why the class BigDecimal
is used instead of the native Java types float
or
double
. Look for external documentation to back up
your answer.
The program begins its execution on the main
method, where an object of the class PiCalc
is
instantiated and its compute()
method is
invoked. This method contains a loop; on each iteration, the
method piFunction()
is called. This method can be
executed several times, depending on the loop ending
condition.
The data types float
and double
has a
limited precision, especially the former one. As this program
should be able to calculate the value of pi with an arbitrary
precision, none of these data types can be used. The class
BigDecimal
represents real numbers of arbitrary precision with a cost: the
code is more complex than if we use native Java data types and
simple computations are harder for the computer.
Import the program into Eclipse and run it several times with varied precisions. Note how the program takes more and more time to execute as you ask for higher precisions (in the next section you will understand why is that).
Use the Eclipse debugger to understand how the program works. Set
a breakpoint in the first line of the method
compute()
(menu Run / Toggle
Breakpoint). Run the program in debug mode, using
Debug instead of
Run. The program will begin its execution and
will stop at your breakpoint.
You will be asked to change the Eclipse perspective to the
Debugging Perspective, which will allow you to
control and obtain information about the ongoing debugging
session. You will be able to see your source code, along with a
marker on the line that is going to be executed next. You can also
see the values of your program variables (at first, only
this
is shown, which is the object with the method
where the breakpoint is; if you unfold it, you will be able to see
the values of all its attributes). You can also see the Console.
Now that the program is waiting at the breakpoint, you can resume its execution in several ways:
Option Run / Resume: resume the program execution until it finishes or until a breakpoint is reached.
Option Run / Step Into: execute the next line of code. If this line is a method invocation, the program will stop executing at the first line of code inside that method, so you can execute the program step by step.
Option Run / Step Over: execute the next line of code. If this line is a method invocation, the program will execute the whole method normally and then stop once it returns. This is very useful to understand the general flow of the program without getting into the details of each method.
Option Run / Step Return: execute the code in the current method until it gets to a return statement.
You can also cancel the debugging mode by using the option Run / Terminate.
Debug the program using 10 as its command line argument. Count the number of iterations performed on the loop inside the method compute() before getting the final value of pi. Repeat this for bigger arguments. What can you say about the time it takes to execute the program as a function of its argument?
In a new debugging session, keep track of the value of the variable piK on each loop iteration. How is this value changing with each iteration of the loop?
What is the role of the limit
variable in the
program? Run additional debugging sessions if you need to.
To calculate pi with 10 decimal digits of precision the loop is executed 8 times. The more precision you want, the more loop iterations you will need.
The value of the variable piK
gets lower as the
program iterate on the loop. These are the first values it
takes:
The value of pi
is calculated as the sum of the
values of piK
on each iteration, so with each
iteration the value of pi
is more precise and the
value of its most significant bits become more stable:
The variable limit
is initialized with a value of
10 to the power of minus the number of desired digits of
precision. As an example, if two decimal places of precision is
desired, limit
will be initialized to 0.01. This
value is compared against the value of piK
on each
loop iteration. When piK
becomes less than
limit
it is assumed that the digits within the
desired precision have reach a stable value and the program can
stop looping and return the solution. The number of iterations on
the loop depends on the value of limit
, this is,
the more precision you desire, the more interations you will
need.
What is the mathematical expression that the program is using to calculate the value of pi? You can answer this question by thinking about the source code above and using the debugger.
Taking a close look at the source code you will notice that expression being used is:
pi = SUMMATION from k=0 to infinity of 16^(-k) * [ 4/(8k+1) - 2/(8k+4) - 1/(8k+5) - 1/(8k+6) ]
This is the Bailey-Borwein-Plouffe expression. There are better ways to use it than what we present here as a solution; for instance, by using base 16 operations instead of base 10 operations.
Write a new Java program to calculate the area of a circle knowing
it radius. This program will have a main
method that
will receive as its arguments the decimal places of pi for the
calculation of the solution and the radius of the circle. The
program must write the calculated area to its standard output.
The program must use the class PiCalc
presented above
to generate a suitable value of pi for its calculations. It must
also use the class BigDecimal.
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)); } } }
Write a new Java program to calculate the sum of the areas of
several circles, knowing their radii. The program will have a
main
method with the following arguments: the desired
precision on pi and one or more radii values (one per circle). The
program must write the solution to the standard output.
This program must use the class from the previous section and BigDecimal.
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); } } }