Universidad Carlos III de Madrid

Ingeniería de Telecomunicación

Enero-Mayo 2010 / January-May 2010

Recap: Fundamentals of Java

Lab Section1.  Session 2 (lab): Review exercises

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.

Exercise Section1.1.  Debugging Java programs with Eclipse: Calculation of pi

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.

Section 1: A program to compute pi

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.

Solution

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.

Section 2: Running the program

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).

Section 3: Using the Eclipse debugger

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.

Solution

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:

  • With k = 0: 3.1333333333.
  • With k = 1: 0.008089133084375.
  • With k = 2: 0.0001649239239453125.
  • With k = 3: 0.00000506722085449218750.

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:

  • With k = 0: 3.1333333333.
  • With k = 1: 3.141422466384375.
  • With k = 2: 3.1415873903083203125.
  • With k = 3: 3.14159245752917480468750.

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.

Section 4: The expression to calculate pi

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.

Solution

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.

Section 5: Area of a circle

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.

Solution
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));
        }
    }
}

Section 6: Total area of several circles

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.

Solution
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);
        }
    }
}