Lab 8: Repetitive Execution


 

Introduction

We have seen that the Java if and switch statements use a condition (an expression that evaluates to true or false) to permit a program to execute statements selectively. For example, we could use the code fragment:

   if (op2 != 0)
      return (op1 / op2);
   else
   {
      theScreen.println("\nError: Divide by zero attempted !");
      System.exit(1);
   }

and use the condition op2 != 0 to avoid a divide-by-zero error: if op2 is not equal to zero, then the statement before the else is executed; otherwise, the compound statement following the else is executed.

In addition to facilitating selection, most modern programming languages also utilize conditions to permit statements to be executed repeatedly. These repetitive execution statements typically permit a set of statements to be executed over and over, so long as some condition evaluates to true.

As an example use of this capability, suppose that we have a list of lengths that we need to convert from English to metric system measurements. Without repetition, converting these lengths requires that we execute the conversion program a separate time for each value on the list. However, by adding a repetitive execution statement to that program, we can convert each value in the list with a single execution of the program.

Java provides four different loops (well, three, actually) that we will examine in this exercise. These are called the while loop, the do loop, the for loop, and what we call the forever loop.

 

Getting Started

Today's exercise is to make a calculator program that has more functionality and is more user-friendly than the one we wrote in the last exercise. More precisely, the skeleton program Calculate2.java differs from our previous version by supporting a fifth calculator function: the exponentiation operation. Follow the usual procedure of creating a project for this exercise, save a copy of Calculate2.java along with the ann and hoj packages there. Add them to your project and then personalize the opening documentation of Calculate2.java.

You may recall that exponentiation is available in Java via the method pow() in the Math class. Just for today (to learn about loops), we will not use pow(), but will instead pretend that we are Java implementers and write our own exponentiation method, called power().

Since power() performs an operation that already exists in a module, we will declare and define it within our source file, rather than in a module.

 

The Exponentiation Operation

The exponentiation operation should be a familiar one, since the expression

   x^n

performs exponentiation on the base x and the exponent n. We will implement this operation by writing a method power(), such that a call:

   power(x, n)

will compute and return x raised to the power n. To simplify our task, we will assume that n is nonnegative.

 

Method Design

As usual, we begin by using object-centered design to carefully design an exponentiation method. However, before we can describe its behavior, some simple analysis may shed light on what our method must do.

Method Analysis. When faced with a new problem, it is often helpful to solve it "by hand." For example, to calculate power(2,0), power(2,1), power(2,3) and power(2,5) by hand, we might write:

power(2,0)

power(2,1)

power(2,2)

power(2,3)

power(2,5)

Return 1.

1 * 2 = 2;

1 * 2 = 2;

1 * 2 = 2;

1 * 2 = 2;

 

Return 2;

2 * 2 = 4;

2 * 2 = 4;

2 * 2 = 4;

 

 

Return 4.

4 * 2 = 8;

4 * 2 = 8;

 

 

 

Return 8.

8 * 2 = 16;

 

 

 

 

16 * 2 = 32;

 

 

 

 

Return 32.

If we examine the pattern in these "by hand" computations, we might construct the following general solution:

   power(x, n)
   Initialize result to 1.0.
   Set result to result * base. -
   Set result to result * base.  |
   Set result to result * base.  | exponent
   ...                             | times 
   Set result to result * base.  |
   Set result to result * base. -
   Return result.

This provides us with the information needed to describe our method's behavior.

Method Behavior. We can describe what we want to happen as follows:

   Our method should receive a base value and an exponent value
   from the caller of the method.  It should initialize result 
   to one.  For each value count in the range 1 to the exponent value, 
   it should repeatedly multiply result by the base value.
   Our method should then return result.

Method Objects. From our behavioral description, we can identify the following objects:

Description

Type

Kind

Movement

Name

The base value

double

varying

received

base

The exponent value

int

varying

received

exponent

The result value

double

varying

returned

result

The count value

int

varying

local

count

From this, we can specify the task of our method as follows:

   Receive: base, a double;
            exponent, an int.
   Return: result, a double = base^exponent.

Using this specification, go to Calculate2.java and replace the line

   // ... replace this line with the definition of power() ...

with a method stub for power(). Then uncomment the call to power() within apply() and use the compiler to test the syntax of what you have written.

Note that the call to power() uses a type cast (int) to convert the double argument op2 to an integer. This operation converts an double to an int by truncating the fractional portion of the double value (as opposed to rounding the double to the nearest int). Note that so long as we supply the double equivalent of integer values (e.g., 2.0, 9.0, etc.) as the second parameter to our function, this fractional portion will be zero, so we can safely remove it. Since a double is wider than an int, a type cast could potentially lose information (the fractional portion); the type cast tells the compiler to go ahead and perform the conversion despite its potential to lose information. Without this type cast, our attempt to pass a double argument into an int parameter would result in a compilation error.

Method Operations. From our behavioral description, we have the following operations:

Description

Defined?

Name

Module?

1

Receive base and exponent

yes

method call
mechanism

built-in

2

Initialize result to 1.0

yes

declaration

built-in

3

Repeat a statement for each value in the range 1 to exponent

yes

for

built-in

4

Set result to result*base

yes

*=

built-in

5

Return result

yes

return

built-in

Method Algorithm. We can organize these operations into the following algorithm:

   0. Receive base and exponent from caller.
   1. Initialize result to 1.0;
   2. For each count from 1 to exponent:
       result *= base.
   3. Return result.

 

Coding

As we have seen before, the Java for loop is designed to facilitate the counting behavior required by step 2. Recall that its simplified pattern is:

   for (Type Var = Start; Var <= Stop; Var++)
      Statement

where Type is a Java numeric type, Var is the loop control variable used to do the counting, Start is the first value, and Stop is the last value. Using this information, complete the stub of method power(). Then compile and test Calculate2.java, checking that the exponentiation works for a variety of values.

 

Characterizing Loops

The pattern for a Java for loop is actually more general than we have indicated:

   for (InitializationExpr; Condition; StepExpr)
      Statement

where InitializationExpr is any initialization expression, Condition is any boolean expression, and StepExpr is an arbitrary expression. For example, if for some reason we wanted to count downwards and output the multiples of 12 from 100 to 1, then we could write:

   for (int i = 100; i >= 1; i--)
      theScreen.println(12 * i);

Such a loop will continue to execute so long as the condition i >= 1 evaluates to true.

Java for loops are thus controlled by conditions, just as if statements are controlled by conditions. As we shall see, each of the other Java loop statements are also controlled by conditions.

A loop may be categorized by when it evaluates its condition, with respect to the statements it repeats:

  1. A pretest loop evaluates its condition before the loop's Statement.
  2. A posttest loop evaluates its condition after the loop's Statement.
  3. An unrestricted loop evaluates its condition within the loop's Statement
    (i.e., at its beginning, at its end, or anywhere in between).

The for loop is a pretest loop, because it evaluates its condition before the loop's statement is executed (don't take my word for it -- check back and see!).

However, the for loop is designed primarily for problems that involve counting through ranges of numbers, or problems in which the number of repetitions can be determined in advance. As we shall see, there are many problems that do not fit this pattern.

The other three Java loops differ from the for loop in that they are general-purpose loops, designed for problems where the number of repetitions is not known in advance. Why are there three of them? Because

  1. The Java while loop provides a general pretest loop;
  2. The Java do loop provides a general posttest loop; and
  3. The Java forever loop provides a general unrestricted loop.

To see why this is significant, let's examine the problem of getting a menu-choice and ensuring that it is valid.

 

Getting a Valid Menu Choice

The program in Calculate2.java uses method getMenuChoice() to get the user's menu choice:

	private static char getMenuChoice(String menu)
	{
		theScreen.print(menu);
		char choice;
		choice = theKeyboard.readChar();
		return choice;
	}

Getting a menu choice is a potential source of user error, because the user could enter an invalid choice.

One way to handle such errors is to repeatedly display the menu and input the user's choice, so long as they continue to enter invalid menu choices. Since there is no way to predict in advance how many erroneous choices the user may enter, the for loop is not an appropriate statement to use in this situation.

The general-purpose loops give us a way to handle user errors, but we must decide which one to use. To do so, we can begin by writing a partial algorithm for this problem using a generic Loop statement, in which we don't worry (for the moment) how control will leave the loop:

   Loop:
      a. Display menu.
      b. Read choice.
   End loop.
   Return choice.

Every loop has a continuation condition, that defines the circumstances under which we want repetition to continue; and a termination condition, that defines the circumstances under which repetition should terminate. (A continuation condition is usually the negation of the termination condition.) To determine how control should leave the loop, we must identify these conditions. For this particular problem, we want repetition to continue so long as

   choice is an invalid menu choice

and terminate when

   choice is a valid menu choice

so these are our continuation and termination conditions, respectively.

Since choice is not known until after Step (b), following (b) is the logical place to exit the loop, which we can describe using our termination condition as follows:

   Loop:
      a. Display menu.
      b. Read choice.
      c. If choice is a valid menu choice, exit the loop.
   End loop.
   Return choice.

In this algorithm, it is apparent that the controlling condition is evaluated at the bottom of the loop, which implies that a post-test loop is the appropriate loop to choose.

In Java, the post-test loop is called the do loop, and its pattern is as follows:

   do
   {
      Statements
   }
   while ( Condition );

When execution reaches such a loop, the following actions occur:

  1. Statements execute.
  2. Condition is evaluated.
  3. If Condition is true: control returns to step 1;
    Otherwise, control proceeds to the next statement after the loop.

Note that since repetition continues so long as the condition is true, a do loop uses a continuation condition.

Since its Condition is not evaluated until after the first execution of Statements, the do loop guarantees that Statements will be executed one or more times. For this reason, the do loop is said to exhibit one-trip behavior.

The notion of a "valid" menu choice is a bit tricky. One way to handle it is to require that the valid menu choices be consecutive letters of the alphabet (e.g., 'a' through 'e'). If we then pass the first and last valid choices to getMenuChoice() as arguments:

   char operation = getMenuChoice(MENU, 'a', 'e');

and add parameters to the method definition to store these arguments:

   private static char getMenuChoice(String menu, 
                             char firstChoice, char lastChoice)

Since firstChoice and lastChoice will define the range of valid choices, we can express our loop's continuation condition in terms of those values: choice is invalid if and only if

   choice < firstChoice || choice > lastChoice

We are then ready to add a do loop to the method to implement our algorithm:

   private static char getMenuChoice(String menu, 
                             char firstChoice, char lastChoice)
   {
     char choice;
     do
     {
       theScreen.print(menu);
       choice = theKeyboard.readChar();
     }
     while (choice < firstChoice || choice > lastChoice);
     
     return choice;
   }

Note that we must declare choice outside the loop. If we were to declare it within the loop, it would be defined within the loop's block -- local to the loop -- making it inaccessible by the return statement.

Make the necessary modifications to Calculate2.java so that getMenuChoice() will only return a valid menu choice. (Note that you will need to change the call to getMenuChoice() as show above.) Then thoroughly test your code with invalid and valid values. Continue when it works correctly.

 

Getting Valid Numeric Input

Another potential source of error occurs when our program reads values for op1 and op2 from the keyboard -- the user might enter some nonnumeric value. Currently the code for reading in the two values is:

	theScreen.print("\nNow enter your operands: ");
	double op1, op2;
	op1 = theKeyboard.readDouble();
	op2 = theKeyboard.readDouble();

 

Exceptions

If you look at the code in the ann package, you will find that the readDouble() method of Keyboard invokes the parseDouble() method of Double. In Java an error is indicated by an exception being thrown and the method parseDouble() throws a NumberFormatException whenever it is unsuccessful. If the user were to enter 34.3.44 for one of their values, parseDouble() will fail and throw the exception.

When an exception is thrown, it can be caught. This provides the implementer with the option of taking intelligent action in the case of an error. Currently our code does not catch the error. It will just display an error message and quit. To catch an exception we need to wrap the code that may fail in a try/catch block. In its simplest form the try/catch has the following form.

   try
   {
      Statements that may throw exception
   }
   catch ( ExceptionType ex )
   {
      Statements to handle exception
   }

The code inside the catch will not be executed unless an exception of the appropriate type is generated in the try block.

Pretest Loops

Let's think about out problem again. Suppose we wished to give the user another chance to enter valid values, instead of just terminating the program? Clearly we need to have a loop that will continue executing until we get two good values.

Once again we could use a do loop to solve this problem. But let's use a while loop this time for contrast. Note: Some programmers avoid the do loop for the following reasons:

Like many other languages the Java pretest loop is called the while loop, and its pattern is as follows:

   while ( Condition )
      Statement

As usual, Condition is any Java expression that evaluates to true or false, and Statement can be either a single or compound Java statement. Statement is often referred to as the body of the loop.

When execution reaches this statement, the following actions occur:

   I. Condition is evaluated;
   II. If Condition evaluates to false, then
         Statement is bypassed, and control proceeds to the statement after the loop;
      Otherwise
         A. Statement is executed; and
         B. execution then returns to step I;
      End if.

Since Statement may be executed zero or more times, the while loop is said to exhibit zero-trip behavior (i.e., the Statement controlled by a while loop may go unexecuted, if Condition is initially false).

There are some standard coding patterns that use the while loop. One of them is called a sentinel loop.

Design

The first thing to do is to list the operations that we may need to do in the loop. One tricky point is that we may fail on reading the first data value. In that case, we will need to discard the unread characters for the second data value.

  1. Display a prompt.
  2. Read op1 and op2.
  3. Print an error message. (if needed)
  4. Discard unread characters. (if needed)

The first two tasks must be done at least once. If we use a while loop this means that we must either do them once outside the loop or force the loop to execute at least once. We will choose the second option.

We will use a boolean value called badInput to control the loop. Let's start our algorithm without using try/catch:

   a. Set badInput to true
   b. Loop (pretest), so long as badInput is true:
      1) Display a prompt for two real values.
      2) Input op1 and op2.
      3) Set badInput to false.
   End loop.

What we have here is the "normal" operation for the loop. If there is not an input error, the pretest will succeed, the data values will be read, the pretest will fail, and all will be well. Remember though, we have some operations that we must do only if there is a failure. But this is exactly the behavior that a catch exhibits. Our modified algorithm is:

   a. Set badInput to true
   b. Loop (pretest), so long as badInput is true:
      Try:
         1) Display a prompt for two real values.
         2) Input op1 and op2.
         3) Set badInput to false.
      Catch:
         A) Display an error message.
         B) Discard unread characters.
   End loop.

 

In Calculate2.java, add a while loop to encode our modified algorithm.

Note that the simple boolean expresssion:

   while (badInput)
is equivalent to the more verbose expression:
   while (badInput == true) 

and can thus be used to control the while loop. The latter form is redundant, and generally thought to be poor coding style.

We can use

   theKeyboard.readLine();

to discard any unread input.

Note also the type of exception that we are trying to catch is NumberFormatException.

Translate and thoroughly test what you have written. Continue when what you have written makes the entry of numeric values "foolproof."

 

One Execution, Multiple Calculations

The algorithm that our program is using is basically as follows:

   0. Display a greeting.
   1. Display a menu of operations and read operation,
       guaranteed to be a valid menu operation.
   2. Set badInput to true
   3. Loop (pretest), so long as badInput is true:
      Try:
         a) Display a prompt for two real values.
         b) Input op1 and op2.
         c) Set badInput to false.
      Catch:
         A) Display an error message.
         B) Discard unread characters.
   End loop.
   4. Compute result by applying operation to op1 and op2.
   5. Display result.

Using this algorithm, we have to rerun our program for every calculation, which is inconvenient for the user. A more convenient calculator would "wrap" some of the statements in a loop, so that the user could perform multiple calculations without having to rerun the program. To do so, we might modify our algorithm:

   i.  Display a greeting.
   ii. Loop:
      1. Display a menu of operations and read operation,
          guaranteed to be a valid menu operation.
      2. Set badInput to true
      3. Loop (pretest), so long as badInput is true:
         Try:
            a) Display a prompt for two real values.
            b) Input op1 and op2.
            c) Set badInput to false.
         Catch:
            A) Display an error message.
            B) Discard unread characters.
      End loop.
      4. Compute result by applying operation to op1 and op2.
      5. Display result.
   End loop.

In order to determine which kind of loop to use, we must determine where to evaluate the loop's termination condition, which we might express as:

   the user does not want to quit

One way to have the user indicate that they want to quit is to view quitting as an operation, and provide an additional menu choice (i.e., 'f') by which the user can indicate that they want to quit. The condition

   operation == 'f'

thus evaluates to true if the user wishes to quit.

So when do we check this condition? The ideal place is to evaluate it as soon as it can be known, namely immediately following the input of operation:

Put differently, if the user wants to quit, steps 2 through 5 should not be executed -- execution should immediately leave the loop. We thus have a situation where the loop's condition should not be evaluated at the loop's beginning (eliminating the pretest loop), nor at its end (eliminating the posttest loop), but in its middle (suggesting the unrestricted loop):

   i.  Display a greeting.
   ii. Loop:
      1. Display a menu of operations and read operation,
          guaranteed to be a valid menu operation.
 
      2. If operation is quit, exit the loop.
      3. Set badInput to true
      4. Loop (pretest), so long as badInput is true:
         Try:
            a) Display a prompt for two real values.
            b) Input op1 and op2.
            c) Set badInput to false.
         Catch:
            A) Display an error message.
            B) Discard unread characters.
      End loop.
      5. Compute result by applying operation to op1 and op2.
      6. Display result.
   End loop.

In Java, the unrestricted loop is a simplification of the while loop that we call the forever loop, that has the following pattern:

   while (true)
   {
      StatementList1

      if ( Condition ) break;

      StatementList2
   }

Since the while condition always evaluates to true, it is an infinite loop. Of course, we do not want an infinite number of repetitions, but instead want execution to leave the loop when a termination condition becomes true. As shown in the pattern above, this can be accomplished by placing an if statement that uses a termination condition to control a break statement (called an if-break combination) within the body of the loop.

When a break statement is executed, execution is immediately transferred out of the forever loop to the first statement following the loop. By only selecting the break statement when Condition is true, a forever loop's repetition can be controlled in a manner similar to the other general-purpose loops.

As a result, this loop can be used to solve our problem, by making StatementList1 step 1 of our modified algorithm, and making StatementList2 steps 3 through 6. Modify your source program to incorporate this approach, and then translate and test the newly added statements.

Note that this loop can be used to solve a sentinel loop problem without redundant statements. For example, here is code that will read and process data values until the user enters 0:

   while (true)                                 // LOOP:
   {
      theScreen.print("Type in a data value "); //  executed one or more times
      data = theKeyboard.readInt();             //  executed one or more times

      if ( data == 0) break;                    //  if zero, exit the loop

      processData(data);                        //  executed zero or more times
   }

Because it eliminates redundant coding, some programmers prefer the forever loop in situations where some statements must be executed zero or more times and other statements must be executed one or more times.

Implement an unrestricted loop to allow the user to do multiple computations in Calculate.java.

Testing and Debugging

Compile and test what you have written. When it is correct, save your work and print a hard copy of Calculate2.java.

 

Phrases you should now understand:

Condition, Repetitive Execution, For Loop, While Loop, Do Loop, Forever Loop, General-Purpose Loop, Counting Loop, Zero-Trip Behavior, One-Trip Behavior, Pretest Loop, Posttest Loop, Unrestricted Loop, Break Statement, If-Break Combination.


 

Submit:

A hard copy of your final version of Calculate2.java and an execution record showing its execution.


Back to This Lab's Table of Contents

Back to the Prelab Questions

Forward to the Homework Projects


 

Back to the Table of Contents

Back to the Introduction


Copyright 2000 by Prentice Hall. All rights reserved.