The problem with floating-point arithmetic is this: binary floating-point numbers (such as those with a type of real, float, or double) cannot represent common decimal fractions exactly. For instance, common decimal fractions like 0.1 do not have a terminating binary representation. Just as 1/3 is a repeating fraction in decimal, 1/10 is a repeating fraction in binary. As a result, floating-point numbers can’t be used safely for financial calculations. In fact, they cannot be used for any calculations where the result produced is required to match those that might be calculated by hand.
In an article on the Java Performance Tuning Guide1, Mikhail Vorontsov emphasizes this point when he notes that double calculations are not precise even for simple operations such as addition and subtraction. For instance, he notes that the Java statement
System.out.println( "362.2 - 362.6 = " + ( 362.2 - 362.6 ) );
produces the output
362.2 - 362.6 = -0.4000000000000341
The advantage of doing computations using fixed-point decimal arithmetic, as COBOL does, is that everyday
numbers such as 0.1 can be represented exactly and the results of COBOL calculations do exactly match those that might be produced by hand.
Doing computations using floating-point arithmetic causes tiny inaccuracies that lead to unacceptable errors when taken over millions of computations. For instance, suppose you are required to calculate a tax of 15% on a 70-cent telephone call that is then rounded to the nearest cent2. Using the Java compiler at compileonline.com, the calculation 0.70 * 1.15 produces a result of 0.8049999999999999, which rounds down to 0.80 cents. The correct result should 292
Chapter 12 ■ advanCed data deClaration
have been 0.805, which would round up to 0.81 cents. This difference of a cent per calculation, when taken over a million calculations of this kind, would result in an undercharge of ten thousand dollars.
If floating-point arithmetic is so problematic, how do languages that do not have native support for fixed-point decimal arithmetic deal with the problem? Nowadays, they implement a class to support decimal arithmetic operations.
However, implementing such a class is not a trivial undertaking. Java’s original implementation of the BigDecimal class was so flawed that IBM raised a Java Specification Request (JSR)2 detailing the problems and requesting changes. These changes were implemented and shipped with Java 1.5 in 2004.
So does the revised BigDecimal class solve the problems with decimal arithmetic in Java? Only partly; decimal arithmetic in Java is implemented as a class instead of as a native type, and computations using the class are cumbersome, unnatural, and slow. For instance, Vorontsov1 found that 100 million BigDecimal calculations took 8.975 seconds, while the same number of double calculations took only 0.047 seconds. BigDecimal operations can be called unnatural in the sense that Java floating-point numbers and integers can use the standard assignment operator (=) and the standard arithmetic operators (+ - / *), whereas the BigDecimal class has to use its class methods. For instance, to multiply two BigDecimal numbers, you might use a statement like
calcResult = num1.multiply(num2);
instead of
calcResult = num1 * num2;
Early in Chapter 1, I showed you a Java program that used the BigDecimal class and asked you to compare it to the COBOL version for readability. I was confident then that you would be able to appreciate the readability of the COBOL version even though at the time you had not yet been introduced to the elements of the language. However, now that we have a more level playing field, let’s look at those programs again (reprinted Listings 1-1 and 1-2).
Listing 1-1. COBOL Version
IDENTIFICATION DIVISION.
PROGRAM-ID. SalesTax.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 beforeTax PIC 999V99 VALUE 123.45.
01 salesTaxRate PIC V999 VALUE .065.
01 afterTax PIC 999.99.
PROCEDURE DIVISION.
Begin.
COMPUTE afterTax ROUNDED = beforeTax + (beforeTax * salesTaxRate)
DISPLAY "After tax amount is " afterTax.
Listing 1-2. Java Version (from http://caliberdt.com/tips/May03_Java_BigDecimal_Class.htm)
import java.math.BigDecimal;
public class SalesTaxWithBigDecimal
{
public static void main(java.lang.String[] args)
{
BigDecimal beforeTax = BigDecimal.valueOf(12345, 2);
BigDecimal salesTaxRate = BigDecimal.valueOf(65, 3);
BigDecimal ratePlusOne = salesTaxRate.add(BigDecimal.valueOf(1));
293
Chapter 12 ■ advanCed data deClaration
BigDecimal afterTax = beforeTax.multiply(ratePlusOne);
afterTax = afterTax.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println( "After tax amount is " + afterTax);
}
}
The COBOL version uses native fixed-point decimal arithmetic and is able to use all the standard arithmetic operators. The Java version has to use the BigDecimal methods to do even simple calculations. The COBOL program is 10 lines and 335 characters long, whereas the Java program is 13 lines and 484 characters long. Reminded me again.
Which one is the verbose language?
Summary
This chapter explored the operation of advanced data-declaration clauses such as the REDEFINES clause, the RENAMES
clause, and the USAGE clause. It showed how you can use REDEFINES clauses to redefine an area of storage with a new name and new data description. You saw how to use RENAMES to group a set of data items under a new name. You also learned how to use the USAGE clause to change the default DISPLAY data format to one of the binary formats such as COMPUTATIONAL or PACKED-DECIMAL. The operation of these binary formats was explored in more depth, and the computational efficiency of the binary formats was weighed against the portability of the DISPLAY format. You investigated the operation-modifying SYNCHRONIZED clause and learned about the USAGE clause extensions provided by many COBOL implementers. The chapter ended with a discussion of the problems inherent in using floating-point arithmetic for financial and commercial calculations and the contrast between COBOL’s native support for decimal arithmetic and the bolted-on capability provided by Java’s BigDecimal class.
The next chapter returns to the topic of tabular data to introduce the SEARCH and SEARCH ALL verbs. Searching tabular data for a particular value is a common operation, but it can be tricky to get the search algorithms right. For this reason, COBOL provides SEARCH and SEARCH ALL. The SEARCH ALL verb allows you to apply a binary search to a table, and SEARCH applies a linear search.
LaNGUaGe KNOWLeDGe eXerCISeS
Sometimes the most instructive lessons arise from the mistakes you make. the debugging exercises that follow are based on some programming errors i made when i was learning to program in CoBol.
locate your 2B pencil, and provide answers to the problems.
The Problems
The first two programs go into an infinite loop (never halt) and have to be stopped by the user. The third program sometimes crashes with the error message shown in the accompanying runs. The fourth program sometimes goes into an infinite loop.
Examine each program, and use the accompanying runs to discover the bug or bugs responsible for the problem.
Identify the problem, and show how you would correct the program to make it work correctly.
294
Chapter 12 ■ advanCed data deClaration
Program 1
This program goes into an infinite loop. Examine the program in Listing 12-3 and the program output and try to figure out what is going wrong. Identify the problem, and suggest a solution.
Listing 12-3. Program Does Not Halt
IDENTIFICATION DIVISION.
PROGRAM-ID. Listing12-3.
AUTHOR. Michael Coughlan.
DATA DIVISION.
WORKING-STORAGE SECTION.
01
Counters.
02 Counter1 PIC 99.
02 Counter2 PIC 99.
02 Counter3 PIC 9.
PROCEDURE DIVISION.
Begin.
DISPLAY "Debug 1. Discover why I can't stop."
PERFORM EternalLooping VARYING Counter1
FROM 13 BY -5 UNTIL Counter1 LESS THAN 2
AFTER Counter2 FROM 15 BY -4
UNTIL Counter2 LESS THAN 1
AFTER Counter3 FROM 1 BY 1
UNTIL Counter3 GREATER THAN 5
STOP RUN.
EternalLooping.
DISPLAY "Counters 1, 2 and 3 are -> "
Counter1 SPACE Counter2 SPACE Counter3.
Answer:
______________________________________________________
______________________________________________________
______________________________________________________
______________________________________________________
______________________________________________________
______________________________________________________
______________________________________________________
______________________________________________________
______________________________________________________
______________________________________________________
295
Chapter 12 ■ advanCed data deClaration
Program 2
This program also goes into an infinite loop. Examine the program in Listing 12-4 and the program output, and try to figure out what is going wrong. Identify the problem, and suggest a solution.
Listing 12-4. What Is Wrong with This Program?
IDENTIFICATION DIVISION.
PROGRAM-ID. Listing12-4.
AUTHOR. Michael Coughlan.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 Counters.
02 Counter1 PIC 99.
02 Counter2 PIC 9.
02 Counter3 PIC 9.
PROCEDURE DIVISION.
Begin.
DISPLAY "Debug2. Why can't I stop?"
PERFORM EternalLooping VARYING Counter1
FROM 1 BY 1 UNTIL Counter1 GREATER THAN 25
AFTER Counter2 FROM 1 BY 1
UNTIL Counter2 GREATER THAN 9
AFTER Counter3 FROM 1 BY 1
UNTIL Counter3 EQUAL TO 5
STOP RUN.
EternalLooping.
DISPLAY "Counters 1, 2 and 3 are "
Counter1 SPACE Counter2 SPACE Counter3.
Answer: _______________________________________
_______________________________________________
_______________________________________________
_______________________________________________
_______________________________________________________
_______________________________________________________
296
Chapter 12 ■ advanCed data deClaration
Program 3
This program sometimes crashes. When it crashes, it produces the error message shown. From the two program outputs shown (one successful and one where the program crashes to produce the error message) and an
examination of the program, try to work out why the program crashes. Identify the problem, and suggest a solution.
Listing 12-5. Program Crashes When Numbers Are Even; OK When Odd.
IDENTIFICATION DIVISION.
PROGRAM-ID. Debug3.
AUTHOR. Michael Coughlan.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT PersonFile ASSIGN TO "PERSON.DAT"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD PersonFile.
01 PersonRec PIC X(10).
88 EndOfFile VALUE HIGH-VALUES.
WORKING-STORAGE SECTION.
01 Surname PIC X(10).
88 EndOfData VALUE SPACES.
01 Quotient PIC 9(3).
01 Rem PIC 9(3).
01 NumberOfPeople PIC 9(3) VALUE ZERO.
PROCEDURE DIVISION.
Begin.
OPEN OUTPUT PersonFile
DISPLAY "Debug3"
DISPLAY "Enter list of Surnames."
DISPLAY "Press RETURN after each name."
DISPLAY "To finish press return
with no value."
DISPLAY "This will fill Surname
with spaces"
DISPLAY "Name -> " WITH NO ADVANCING
ACCEPT Surname
PERFORM GetPersons UNTIL EndOfData
CLOSE PersonFile
297
Chapter 12 ■ advanCed data deClaration
OPEN INPUT PersonFile
READ PersonFile
AT END SET EndOfFile TO TRUE
END-READ
PERFORM CountPersons UNTIL EndOfFile.
CLOSE PersonFile
DIVIDE NumberOfPeople BY 2
GIVING Quotient REMAINDER Rem
IF Rem = 0
DISPLAY "Even number of people"
ELSE
DISPLAY "Odd number of people"
STOP RUN.
GetPersons.
WRITE PersonRec FROM Surname
DISPLAY "Name -> " WITH NO ADVANCING
ACCEPT Surname.
CountPersons.
DISPLAY PersonRec
ADD 1 TO NumberOfPeople
READ PersonFile
AT END SET EndOfFile TO TRUE
END-READ.
Program 4
Sometimes this program goes into an infinite loop (does not halt). From the two program outputs shown (one where the program halts naturally and one where it has to be halted by the user) and an examination of the program, try to work out why the program sometimes does not halt. Identify the problem, and suggest a solution.
Listing 12-6. Program Sometimes Goes into an Infinite Loop
IDENTIFICATION DIVISION.
PROGRAM-ID. Debug4.
AUTHOR. Michael Coughlan.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 Counter1 PIC 99.
01 InNumber PIC 9.
01 Result PIC 999.
298
Chapter 12 ■ advanCed data deClaration
PROCEDURE DIVISION.
Begin.
DISPLAY "DEBUG4. Sometimes I just don't stop"
DISPLAY "Enter number 0-9 :--> " WITH NO ADVANCING
ACCEPT InNumber
PERFORM EternalLooping
VARYING Counter1 FROM 1 BY 1
UNTIL Counter1 GREATER THAN 10
DISPLAY "Back in main paragraph now"
STOP RUN.
EternalLooping.
COMPUTE Result = InNumber * Counter1
IF Result > 60
MOVE 99 TO Counter1
END-IF
DISPLAY "Counter1 = " Counter1 " Result = " Result.
LaNGUaGe KNOWLeDGe eXerCISeS—aNSWerS
Program 1
Problem Cause
The problem here is that Counter1 and Counter2 go negative but are described as PIC 99—a description that only allows positive values. The problem with Counter1 is masked by the problem with Counter2. You can see the effect of the problem with Counter2 in the program output fragment shown in Figure 12-16. When Counter2 has a value of 03, the next value it should take is -1; but because Counter2 is described as PIC 99, it cannot hold a negative value. This means the sign is lost, and instead of -1, the value of Counter2 is 1. Therefore, Counter2 never reaches its terminating value, and the loop never terminates.
PERFORM EternalLooping VARYING Counter1
FROM 13 BY -5 UNTIL Counter1 LESS THAN 2
AFTER Counter2 FROM 15 BY -4
UNTIL Counter2 LESS THAN 1
AFTER Counter3 FROM 1 BY 1
UNTIL Counter3 GREATER THAN 5
299
Chapter 12 ■ advanCed data deClaration
/> Figure 12-16. Fragment of output from Listing 12-3 highlighting the problem area
Problem Solution
The solution to the problem is to describe Counter1 and Counter2 as PIC S99.
Program 2
Problem Cause
The problem here is that Counter2 is described as PIC 9. You can see the problem by examining the flowchart in Figure 12-17.
Figure 12-17. Flowchart showing how the three-counter PERFORM..VARYING works
300
Chapter 12 ■ advanCed data deClaration
Suppose the program is at the point where Counter2 has a value of 9 and Counter3 has a value of 5. At this point the condition Counter3 = 5 is satisfied, and Counter3 is reset to 1 while Counter2 is incremented, making it equal to 10. Because Counter2 is described as PIC 9, there is only room for one digit, so the 1 is truncated, leaving Counter2
with a value of 0. When the Counter2 > 9 condition is tested, it is not satisfied, and the loop never ends.
Problem Solution
The solution to the problem is to describe Counter2 as PIC 99.
Program 3
Problem Cause
The problem here is that the IF before the STOP RUN does not have an explicit terminator. This means the scope of the IF is terminated by the period that follows the STOP RUN; and this means the scope of the ELSE branch of the IF
includes the STOP RUN:
DIVIDE NumberOfPeople BY 2
GIVING Quotient REMAINDER Rem
IF Rem = 0
DISPLAY "Even number of people"
ELSE
DISPLAY "Odd number of people"
STOP RUN.
GetPersons.
WRITE PersonRec FROM Surname
DISPLAY "Name -> " WITH NO ADVANCING
ACCEPT Surname.
Failing to specify the scope of the IF with an explicit terminator has the following effect. When there is an odd number of people, the ELSE branch is taken, the STOP RUN is executed, and the program stops normally; but when there is an even number of people, the ELSE branch is not taken, the STOP RUN is not executed, and control falls into the GetPersons paragraph where it tries to write to the closed PersonFile. This write attempt crashes the program and produces the error message.
Michael Coughlan Page 37