3.7 Avoiding Errors and Debugging

Avoiding Errors

  1. Because of the way that floating point values are stored, it may happen that values that should be exactly equal are, in fact, only approximately so. Thus, it is not a good idea to test for exact equality of expressions that contain floating point values. A better idea is to see if the values differ by less than some quantity. To see if x and yare sufficiently close, we might use a test of the form |x - y| < ϵ where ϵ (the Greek letter epsilon) is some small positive quantity. A problem with this is that it applies the same test for closeness to both small and large numbers. While we might consider small numbers to be "close" if they differ by 0.001, we may consider very large numbers to be "close" if they differ by only 100. To handle such differences, it is a good idea to make our test of closeness depend on the magnitude of the quantities being examined. A good scheme that employs this principle is to use a test of the form |x - y| < ϵ X max(|x|,|y|)·

  2. It is legal to embed an assignment inside a condition. As an example, an if statement could begin with
    if ((x = y + z) > 0) ... 
    Here the expression (x = y + z) > 0 first assigns to x the value of y + z and then tests the result to see if it is positive. We sometimes refer to the assignment as a side-effect of the evaluation of the condition. Side-effects in programming, like side-effects in taking pharmaceutical drugs, are usually undesirable. Generally speaking, we recommend that expressions involving side-effects be avoided. For the given example, if you later choose to eliminate the if statement, you may inadvertently also eliminate the assignment. Usually it is best to write code that may be a little longer than is necessary in order to make your intentions clear. The example here could be replaced by
    x = y + z;
    if (x > 0)... 

  3. Be very careful about your use of semi-colons. One extra semi-colon in the wrong place can completely destroy the logic of a program. For example, the fragment
    if (x < 0);
       System.out.println("Value is negative"); 
    will always print the message "Value is negative", no matter what value x actually has. The problem here is the semi-colon at the end of the first line. Using our indentation scheme, Java interprets the fragment as
    if (x < 0)
      ;
    System.out.println("Value is negative"); 
    The extra semi-colon is considered to be an empty statement which is executed if x is negative. This terminates the if statement. No matter what value x has, the statement that prints the message is executed.

  4. For statements to be treated as a block, they must be enclosed in brace brackets. Consider the following example.
       if (value < 0)
          System.out.println("Negative value made positive");
          value = -value; 
    The indentation indicates that both statements should be executed if the condition is true but the compiler ignores indentation. Without brace brackets surrounding the two indented statements, the second one is not part of the if statement. The fragment will make negative values positive but it will also cause positive values to be made negative.

Debugging

  1. The most common error committed by beginning Java programmers using if statements is to write = when == is required. The error will usually be detected as a syntax error by the compiler. The error message that it produces is (usually): incompatible types. To explain this, consider the statement that begins with
       if (n = 5) ...  
    where n is of type int. Inside the parentheses is an assignment, an expression whose type is that of the variable on the left side of the assignment. In the example shown here, the type of the assignment expression is int because n is of type int. Java, of course, is expecting a boolean expression (like n == 5). Since the type that it finds is not what it wants, it complains.

  2. Another frequent error with if statements is the failure to take some action for every possible eventuality. The Java compiler is sometimes clever enough to spot this sort of problem so that a fragment such as
       int m;
       int n In.getlnt();
       if (n > 0)
          m = 1;
       else if (n == 0)
          m = 2;
       System.out.println(m); 
    will, during compilation, produce the error message "variable m may not have been initialized". The problem here is that m will not be given a value if n < 0. Unfortunately, sometimes the compiler is a bit over-zealous in flagging possible errors of this type. For example, the fragment
       int m;
       int n Math.abs(In.getlnt());
       if (n > 0)
          m = 1;
       else if (n == 0)
          m = 2;
       System.out.println(m); 
    will still produce the same message even though it is now impossible for n to be negative. The easiest way to get past this problem is to initialize m to zero where we declare it (in the first line of the fragment).

  3. In mathematics, we often restrict the range of a variable x by writing expressions like a < x <= b. Writing similar expressions in Java causes an error. To see what is wrong with such expressions, suppose that we were to try to evaluate a < x <= b as Java might do. Working from left to right, we would begin by evaluating a < x giving a boolean result (true or false). When we then attempted to complete the evaluation, we would be left trying to compare a boolean value with b, producing an error. Java's compiler is smart enough to spot such problems, producing an error message during compilation. The solution to the problem is to rewrite the expression in the form a < x && x <= b.
  4. Another mathematical shortcut that produces an error in Java is an expression of the form x, y > 0. Trying to write this in Java either as it is or in the form x && y > 0 is a mistake. To evaluate x && y > 0, Java would first have to evaluate the sub-expression y > 0 (because > has higher precedence than &&) but then the expression would reduce to either x && true or x && false. For x having a numerical value, either of these expressions would be nonsense in Java. Again, the compiler will catch such an error and refuse to compile a program containing it. The expression should be replaced by x > 0 && y > 0.
  5. If a program that contains if or switch statements is running but producing incorrect results, the error may be caused by the program executing incorrect statements because the logic is wrong. To determine which path a program is actually taking, insert tracing statements in every possible branch of the program. For example, to trace a program containing a segment of the form
    if (...)
       if (...)
          statement1
       else
          statement2
    else
       statement3 
    tracing statements should be inserted at each of the points at which <statement1>, <statement2>, <statement3> are shown.
  6. Tracing statements used for debugging can be turned on and off easily using if statements. To do so, create a boolean variable DEBUG that is set to true when debugging and false otherwise. Then place all tracing statements inside if statements of the form
    if (DEBUG) ... 

Exercise 3.7

    1. In the section on avoiding errors, we suggested that a good test for the closeness of two floating point variables x and y was to see if |x - y| < ϵ x max(|x|,|y|). If x and y are two double variables and EPSILON is a double constant representing E, write a Java expression that will be true if and only if x and y satisfy this test.
    2. For the values of x, y, and EPSILON that follow, state whether the expression in part (a) would be true or false.
      1. x = 123.48,y = 123.77, and EPSILON = 0.01
      2. x = 0.000456, y = 0.000457, and EPSILON = 0.001
      3. x = 8.7658, y = 8.7651, and EPSILON = 0.001
      4. x = 12626.7, y = 12539.2, and EPSILON = 0.01

  1. Assuming that x, a, and b are double variables and a < b, write the simplest Java expression that will be true if and only if
    1. x is between a and b
    2. x is outside the interval between a and b
    3. x is either less than a or less than b
    4. x is greater than both a and b
  2. Examine the following fragment and then answer the questions that follow it.
       if (x < 0)
          System.out.println("invalid");
       else;
          System.out.println("valid"); 
    1. For what value(s) of x will the word valid be printed? Justify your answer.
    2. Rewrite the fragment so that the word valid is printed only if x > = 0
  3. Suppose that a program contains the following statement.
       if (a > 0)
          if (b < 0)
             System.out.println(1);
          else
             System.out.println(2);
       else
          System.out.println(3);
    Under what circumstances will the statement print
    a) 1?        b) 2?        c) 3?