I'm having some trouble figuring out the logic behind this.
I need to display a report calculating balance, interest and principal per month until the balance is zero.
As an example, if input is months=12, balance=25000, rate=4.5%, output should look like this:
months balance interest principal
1 $25000.00 $93.75 $2,040.71
2 $22,959.29 $86.10 $2,048.36
.......
12 $2,126.53 $7.97 $2,126.49
I'm not sure what to write after DISPLAY col-hdr and before STOP RUN. Any ideas?
IDENTIFICATION DIVISION.
PROGRAM-ID. practice.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LOANFMT PIC $$$$,$$$,$$$.$$.
01 LOANAMT PIC S9(9)V9(2) VALUE 0.
01 INTRATE PIC S9V9(2) VALUE 0.
01 INTFMT PIC 9.999.
01 NUMMONTHS PIC S9(3) VALUE 0.
01 MONFMT PIC ZZ9.
01 MONCNT PIC S999 VALUE 1.
01 PMT PIC S9(9)V9(2) VALUE 0.
01 PMTFMT PIC $$$$,$$$,$$$.$9.
01 TOTPMT PIC S9(9)V9(2) VALUE 0.
01 TOTFMT PIC $$$$,$$$,$$$.$9.
01 col-hdr.
05 pic x(15) value "Month".
05 pic x(15) value "Balance".
05 pic x(15) value "Interest".
05 pic x(15) value "Principal".
01 Detail-Line.
05 Pic X(2) Value Spaces.
05 DL-MONTH Pic X(999) VALUE 1.
05 Pic X(5) Value Spaces.
05 DL-BALANCE Pic $$$$,$$$,$$$.$9.
05 Pic X(4) Value Spaces.
05 DL-INTEREST Pic $$$$,$$$,$$$.$9.
05 Pic X(4) Value Spaces.
05 DL-PRINCIPAL Pic $$$$,$$$,$$$.$9.
PROCEDURE DIVISION.
000-MAIN SECTION.
DISPLAY "Enter Loan Amount: " WITH NO ADVANCING
ACCEPT LOANAMT
IF 0 > LOANAMT
PERFORM UNTIL LOANAMT > 0
DISPLAY "Loan Amount must be positive"
DISPLAY "Enter Loan Amount: " WITH NO ADVANCING
ACCEPT LOANAMT
end-PERFORM
END-IF
DISPLAY "Enter Annual Interest Rate: " WITH NO ADVANCING
ACCEPT INTRATE
IF 0 > INTRATE
PERFORM UNTIL INTRATE > 0
DISPLAY "Annual Interest Rate must be positive"
DISPLAY "Enter Annual Interest Rate: " WITH
NO ADVANCING
ACCEPT INTRATE
end-PERFORM
END-IF
DISPLAY "Enter Number of Months: " WITH NO ADVANCING
ACCEPT NUMMONTHS
IF 0 > NUMMONTHS
PERFORM UNTIL NUMMONTHS > 0
DISPLAY "Number of Months must be positive"
DISPLAY "Enter Number of Months: " WITH NO
ADVANCING
ACCEPT NUMMONTHS
end-PERFORM
END-IF
DISPLAY SPACE
move LOANAMT TO LOANFMT
move INTRATE TO INTFMT
MOVE NUMMONTHS TO MONFMT
MOVE PMT TO PMTFMT
MOVE TOTPMT TO TOTFMT
DISPLAY col-hdr
100-init.
DL-BALANCE = LOANAMT
DL-INTEREST = LOAN * (INTRATE/NUMMONTHS)
DL-PRINCIPAL = LOANAMT - DL-INTEREST
DISPLAY DETAIL-LINE
PERFORM 200-ADDMONTH UNTIL NUMMONTHS = DL-MONTH
200-ADDMONTH.
ADD 1 TO DL-MONTH
DL-BALANCE = DL-BALANCE - DL-PRINCIPAL
DL-INTEREST = LOAN * (INTRATE/NUMMONTHS)
DL-PRINCIPAL = LOANAMT - DL-INTEREST
DISPLAY DETAIL-LINE.
STOP RUN.
months balance interest principal
1 $25000.00 $93.75 $2,040.71
2 $22,959.29 $86.10 $2,048.36
.......
12 $2,126.53 $7.97 $2,126.49
Firstly, get that sorted out.
months balance interest principal
01 $25,000.00 $93.75 $2,040.71
02 $22,959.29 $86.10 $2,048.36
.......
12 $2,126.53 $7.97 $2,126.49
That looks much more professional, and easy to produce. I don't like the "months" heading, because it is not clear what it means. Some capitalisation would be good as well, but those are up to you. Actual spacing you can sort out as well. In my experience, Principal would always be before Interest, and a figure of the Payment before that. The user will want to see the Payment, not have to work it out, and want to confirm the split of the payment, and visually verify the interest amount.
Maybe it's regional, however.
As Brian noted in a comment, you've had you elbow on the 9 key whilst defining the month in the detail line. Make it PIC 99 or PIC Z9.
You are writing your program as a "fall through" structure. Perhaps that is what you are used to with other languages. Mainly COBOL programs that you would see would have a different structure.
Here is your code re-arranged, also with attention paid to indentation, which is important for the human reader. The spacing I find useful, but is not as mandatory as the indentation:
PROCEDURE DIVISION.
PERFORM GET-AND-VALIDATE-USER-INPUT
PERFORM PROCESS-USER-INPUT
PERFORM PRODUCE-REPORT
GOBACK
.
GET-AND-VALIDATE-USER-INPUT.
PERFORM GET-AND-VALIDATE-LOAN-AMT
PERFORM GET-AND-VALIDATE-INT-RATE
PERFORM GET-AND-VALIDATE-MONTHS
.
GET-AND-VALIDATE-LOAN-AMT.
DISPLAY "Enter Loan Amount: " WITH NO ADVANCING
ACCEPT LOANAMT
IF 0 > LOANAMT
PERFORM UNTIL LOANAMT > 0
DISPLAY "Loan Amount must be positive"
DISPLAY "Enter Loan Amount: "
WITH NO ADVANCING
ACCEPT LOANAMT
end-PERFORM
END-IF
.
GET-AND-VALIDATE-INT-RATE.
DISPLAY "Enter Annual Interest Rate: " WITH NO ADVANCING
ACCEPT INTRATE
IF 0 > INTRATE
PERFORM UNTIL INTRATE > 0
DISPLAY "Annual Interest Rate must be positive"
DISPLAY "Enter Annual Interest Rate: "
WITH NO ADVANCING
ACCEPT INTRATE
end-PERFORM
END-IF
.
GET-AND-VALIDATE-MONTHS.
DISPLAY "Enter Number of Months: " WITH NO ADVANCING
ACCEPT NUMMONTHS
IF 0 > NUMMONTHS
PERFORM UNTIL NUMMONTHS > 0
DISPLAY "Number of Months must be positive"
DISPLAY "Enter Number of Months: "
WITH NO ADVANCING
ACCEPT NUMMONTHS
end-PERFORM
END-IF
.
PROCESS-USER-INPUT.
PERFORM GET-AND-VALIDATE-MONTHS
move LOANAMT TO LOANFMT
move INTRATE TO INTFMT
MOVE NUMMONTHS TO MONFMT
MOVE PMT TO PMTFMT
MOVE TOTPMT TO TOTFMT
.
PRODUCE-REPORT.
DISPLAY SPACE [don't know what you want that for]
DISPLAY col-hdr
PERFORM FORMAT-INITIAL-LINE
PERFORM OUTPUT-DETAIL-LINE
PERFORM FORMAT-MONTHS-TO-END
.
FORMAT-INITIAL-LINE.
DL-BALANCE = LOANAMT
DL-INTEREST = LOAN
* ( INTRATE
/ NUMMONTHS )
DL-PRINCIPAL = LOANAMT
- DL-INTEREST
.
OUTPUT-DETAIL-LINE.
DISPLAY DETAIL-LINE
.
FORMAT-MONTHS-TO-END.
PERFORM NUMMONTHS = DL-MONTH
ADD 1 TO DL-MONTH
DL-BALANCE = DL-BALANCE
- DL-PRINCIPAL
DL-INTEREST = LOAN
* ( INTRATE
/ NUMMONTHS )
DL-PRINCIPAL = LOANAMT
- DL-INTEREST
PERFORM OUTPUT-DETAIL-LINE
END-PERFORM
.
You have assignments. COBOL does not. COBOL has COMPUTE, so you'll need to use that, although MOVE, ADD, SUBTRACT, DIVIDE and MULTIPLY can clarify as well:
FORMAT-INITIAL-LINE.
MOVE LOANAMT TO DL-BALANCE
COMPUTE DL-INTEREST = LOAN
* ( INTRATE
/ NUMMONTHS )
SUBTRACT DL-INTEREST FROM LOANAMT
GIVING DL-PRINCIPAL
.
Note that GIVING. SUBTRACT A FROM B will change the value of B. If you put GIVING C on the end, B will no longer be changed, instead the result will be placed in C. ADD A TO B changes B. ADD A B GIVING C does not (note this time no need for TO, although syntactically it can be there). Ensure you understand what ADD, SUBTRACT, MULTIPLY and DIVIDE can do.
It is possible to only use COMPUTE. Unlike myth, there is no performance penalty in this, but extra human-reader information is lost.
With modern COBOL compilers it is not necessary to start a program with an arbitrary procedure name (either SECTION or paragraph). It has no meaning, at all. So ditch this (unless dictated by tutor/site-standards):
000-MAIN SECTION.
You have things like this:
IF 0 > LOANAMT
And:
PERFORM UNTIL LOANAMT > 0
I understand the point made by cshneid made in a comment, but there is consistency, and there is the fact that COBOL has no assignment statement. An expression in a conditional construct can never cause a change to any field involved in the expression.
IF LOANAMT > 0
Or:
IF LOANAMT GREATER THAN 0
Can be read, by the mythical average COBOL programmer, without pause.
IF 0 < LOANAMT
Is more of a discontinuity. The reader has to stop and think what that means. There is no benefit in doing it that way, and there are disbenefits.
DISPLAY and ACCEPT are the COBOL verbs which vary the most from the COBOL standard, from compiler to compiler. To the COBOL 85 Standard, ACCEPT and DISPLAY are very plain. You are using a compiler with "Extended" ACCEPT and DISPLAY. This may (probably does) allow the entry of negative amounts, and may prevent the entry of non-numeric data, but you need to check the documentation for your compiler. It will be important that the data entered is numeric. It is easier to get a character in the number than to enter a negative value by accident.
From your original code:
100-init.
DL-BALANCE = LOANAMT
DL-INTEREST = LOAN * (INTRATE/NUMMONTHS)
DL-PRINCIPAL = LOANAMT - DL-INTEREST
DISPLAY DETAIL-LINE
PERFORM 200-ADDMONTH UNTIL NUMMONTHS = DL-MONTH
200-ADDMONTH.
ADD 1 TO DL-MONTH
DL-BALANCE = DL-BALANCE - DL-PRINCIPAL
DL-INTEREST = LOAN * (INTRATE/NUMMONTHS)
DL-PRINCIPAL = LOANAMT - DL-INTEREST
DISPLAY DETAIL-LINE.
STOP RUN.
Here, since 100-init is not PERFORMed, the program control will drop through into 200-ADDMONTH. Labels (paragraphs or SECTIONs) are just labels. They can be the target of a PERFORM, a GO TO, or they can be "fallen through" or "dropped into". They are unlike "subroutine" or "function" definitions in other languages you probably know.
So, 100-init will PERFORM 200-ADDMONTH until it is finished with, then it will fall into 200-ADDMONTH again. Never code that deliberately. Each paragraph/SECTION should be self-contained and not rely on the physical location of its content.
If 100-init were PERFORMed, you'd be OK. Sort of. Because you have a STOP RUN in 200-ADDMONTH. When 200-ADDMONTH is executed the first time, the program will stop executing. Not what you want.
I've not considered the logic of your actual calculation, just the methods of it. You have duplicated code, so that could go in another PERFORMed paragraph/SECTION.
Be aware of the difference between a paragraph and a SECTION when they are PERFORMed. A SECTION can (these days does not have to) contain paragraphs. When a SECTION is PERFORMed, control returns to the completed PERFORM before the next SECTION. When a paragraph is PERFORMed, control returns before the next paragraph. Paragraphs cannot contain other paragraphs. To PERFORM a range of paragraphs, THRU will be required on the PERFORM. Unless dictated by tutor/site-standards, avoid coding that. Again, it relies on the physical location of code. Which is bad.
These days, there should be no intrinsic need for SECTIONs, and no need (except diktat) for PERFORM ... THRU ....
Related
Additional background: this is a follow-up to Adding two integers giving unwanted result in cobol.
As the input data consists of strings and integers are needed for calculating, this program bulk reads each row, then read each field individually from the file and convert the necessary fields to numbers when storing them in the working storage section table.
Now, for some reason, only the first record reads and stores properly. The rest of the records are being read as blanks or nulls, I guess, even though the file contents after the first record are obviously not null.
Here is my current code for the full program:
IDENTIFICATION DIVISION.
PROGRAM-ID. GRADEREPORT.
AUTHOR. JORDAN RENAUD.
DATE-WRITTEN. 09/18/2020.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT GRADES-FILE ASSIGN TO "bill"
ORGANIZATION IS LINE SEQUENTIAL
ACCESS IS SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD GRADES-FILE.
01 INPUT-TOTAL-POINTS PIC 9(4).
01 INPUT-GRADES.
05 INPUT-GRADE OCCURS 1 to 100 TIMES DEPENDING ON RECORD-COUNT.
10 INPUT-ASSIGNMENT-NAME PIC X(20).
10 INPUT-CATEGORY PIC X(20).
10 INPUT-POINTS-POSSIBLE PIC X(14).
10 INPUT-POINTS-EARNED PIC X(14).
WORKING-STORAGE SECTION.
77 GRADES-FILE-EOF PIC 9.
01 RECORD-COUNT PIC 9(8) VALUE 0.
01 TOTAL-EARNED-POINTS PIC 9(14) VALUE ZERO.
01 TOTAL-POSSIBLE-POINTS PIC 9(14) VALUE 5.
01 K PIC 9(14) VALUE 1.
01 TMP PIC 9(14).
01 CURRENT-CATEGORY PIC X(20).
01 CATEGORY-WEIGHT PIC X(3).
01 LAST-CATEGORY PIC X(20).
01 TOTAL-POINTS PIC 9(4).
01 GRADES.
05 GRADE OCCURS 1 TO 100 TIMES DEPENDING ON RECORD-COUNT.
10 ASSIGNMENT-NAME PIC X(20).
10 CATEGORY PIC X(20).
10 POINTS-POSSIBLE PIC 9(14).
10 POINTS-EARNED PIC 9(14).
PROCEDURE DIVISION.
OPEN INPUT GRADES-FILE.
READ GRADES-FILE INTO TOTAL-POINTS.
DISPLAY TOTAL-EARNED-POINTS
PERFORM UNTIL GRADES-FILE-EOF = 1
READ GRADES-FILE
AT END SET
GRADES-FILE-EOF TO 1
NOT AT END
ADD 1 TO RECORD-COUNT
MOVE INPUT-ASSIGNMENT-NAME(RECORD-COUNT) TO ASSIGNMENT-NAME(RECORD-COUNT)
DISPLAY INPUT-ASSIGNMENT-NAME(RECORD-COUNT)
DISPLAY ASSIGNMENT-NAME(RECORD-COUNT)
MOVE INPUT-CATEGORY(RECORD-COUNT) TO CATEGORY(RECORD-COUNT)
DISPLAY INPUT-CATEGORY(RECORD-COUNT)
DISPLAY CATEGORY(RECORD-COUNT)
MOVE FUNCTION NUMVAL (INPUT-POINTS-POSSIBLE(RECORD-COUNT)) TO POINTS-POSSIBLE(RECORD-COUNT)
DISPLAY INPUT-POINTS-POSSIBLE(RECORD-COUNT)
DISPLAY POINTS-POSSIBLE(RECORD-COUNT)
MOVE FUNCTION NUMVAL (INPUT-POINTS-EARNED(RECORD-COUNT)) TO POINTS-EARNED(RECORD-COUNT)
DISPLAY INPUT-POINTS-EARNED(RECORD-COUNT)
DISPLAY POINTS-EARNED(RECORD-COUNT)
COMPUTE TOTAL-EARNED-POINTS = TOTAL-EARNED-POINTS + POINTS-EARNED(RECORD-COUNT)
DISPLAY TOTAL-EARNED-POINTS
END-READ
END-PERFORM.
CLOSE GRADES-FILE.
DISPLAY TOTAL-EARNED-POINTS.
SORT GRADE ASCENDING CATEGORY.
MOVE CATEGORY(1) TO LAST-CATEGORY.
PERFORM RECORD-COUNT TIMES
MOVE CATEGORY(K) TO CURRENT-CATEGORY
IF CURRENT-CATEGORY = LAST-CATEGORY THEN
DISPLAY "SAME CATEGORY"
ELSE
DISPLAY "NEW CATEGORY"
MOVE LAST-CATEGORY TO CURRENT-CATEGORY
END-IF
SET K UP BY 1
END-PERFORM
DISPLAY GRADES.
STOP RUN.
and here is the input file, bill:
1000
MS 1 - Join Grps Group Project 5 5
Four Programs Programming 15 9
Quiz 1 Quizzes 10 7
FORTRAN Programming 25 18
Quiz 2 Quizzes 10 9
HW 1 - Looplang Homework 20 15
As per the code written, the first line read from the table section of the file(lines 2 and forward) has its individual parts DISPLAY 'ed as follows:
MS 1 - Join Grps
MS 1 - Join Grps
Group Project
Group Project
5
00000000000005
5
00000000000005
This is what I expect. Each item is repeated, the first iteration is the input file structure, and the second is the working storage section structure. The difference being that the input structures are read as all strings of 20 and 14 lengths, and the storage structures are formatted as two strings of 20 length, and two ints of 14 length. The numeric strings are converted to ints and stored in the working storage, as stated earlier.
The output of the second row's DISPLAYs show as this:
00000000000000
00000000000000
00000000000005
00000000000000
00000000000000
00000000000005
00000000000000
00000000000000
00000000000005
00000000000000
00000000000000
00000000000005
00000000000000
00000000000000
00000000000005
In this case, the 00000000000005 is the total of a summation accumulator variable, which is always 5 because the first row reads 5 for the earned points, and the rest of them are just evaluating to zero because they're being read as blanks.
How can I get my program to properly read the rest of the file?
Turns out, when reading a table from a file, the subscript to access the current line is always 1, so instead of reading RECORD-COUNT as the subscript of the INPUT-items, I just put 1 for all of them, and the program works as expected!
Is there a kind of "Wait" function in COBOL?
I wrote a calculator, and to make it more 50s, i Print " Computing." "Computing.." ecc
For example:
DISPLAY "SECONDO NUMERO"
ACCEPT B
COMPUTE C= A * B
DISPLAY "Computing"
DISPLAY "Computing."
DISPLAY "Computing.."
DISPLAY "Computing..."
DISPLAY "Computing...."
DISPLAY "Computing....."
DISPLAY "Computing......"
DISPLAY A "x" B " FA..."
DISPLAY C
Now, is there a way to make a little delay (half a second) on COBOL where I put the "Computing" piece? I created a github repo (https://github.com/aIDserse/Super-utility-Submachine-COBOL-CALCULATOR) to this project, look at it (refer to version 1.3) for the complete code (and maybye spread it hahah). Thx!!!
There is a statement for sleeping in standard COBOL, but only with COBOL 202x:
CONTINUE AFTER arithmetic-expression SECONDS
As this standard is in the committee draft state it is hard to find an implementation, but as you've asked for GnuCOBOL - GnuCOBOL 3.1 already implements it.
Other than this there are some dialect specific library routines that can be used, like CALL "C$SLEEP" originating from ACUCOBOL-GT (also implemented with GnuCOBOL, but be aware that pre 3.1-versions only use the non-decimal part, so "0.9" will sleep zero seconds).
For OpenCOBOL/GnuCOBOL you can call the CBL_OC_NANOSLEEP/CBL_GC_NANOSLEEP library routines.
For any COBOL environment that can call native routines you have variants of CALL "sleep".
As mentioned by Rick Smith Many COBOL implementations also implement a callable SYSTEM where you may use something like a ping localhost with a timeout, but whatever you call may not be available (or the process running the COBOL environment has no access to it).
Stephen Gennard mentioned a very common extension:
ACCEPT something WITH TIMEOUT
which has a "beware" that different environments use a different scale (some seconds, some milliseconds). This has the pro/con that the user can "break" out by pressing a key (normally a function key); and the additional issue that it may only work in "graphical" environments.
Anton's answer highlights the IBM library routine CEE3DLY.
There's no wait statement in any ISO Standard COBOL.
However, if you got built in system routines available either C$SLEEP (for seconds) or CBL_GC_NANOSLEEP (for nanoseconds) should do the trick.
Example (sleeps for half a second):
call "CBL_GC_NANOSLEEP" using "500000000" end-call
For IBM's Enterprise COBOL (LE enabled) the CEE3DLY routine is most suitable (there are also other legacy routines available).
For GnuCobol call the C$SLEEP with the number of seconds you want to wait.
CALL "C$SLEEP" USING 2 END-CALL
COBOL has no build in language feature to handle waiting. This is a system specific request and I believe always requires calling an external module to interface with said system.
There is no wait or delay statement in standard COBOL. There may be, for GnuCOBOL, a CALL "SYSTEM" to effect a delay.
I took some code that I use for elapsed time measurement and modified the code to create a procedure for a delay.
Wherever you need a delay, insert the statement PERFORM timed-delay. Of course, the delay may be changed. This code is set to work even if the delay crosses midnight.
Code:
working-storage section.
01 t pic 9(8).
01 t-start.
03 t-start-hour pic 99.
03 t-start-minute pic 99.
03 t-start-second pic 99v99.
01 t-end.
03 t-end-hour pic 99.
03 t-end-minute pic 99.
03 t-end-second pic 99v99.
77 t-elapsed pic 9(7)v99.
procedure division.
begin.
accept t from time
display t
perform timed-delay
accept t from time
display t
stop run
.
timed-delay.
accept t-start from time
move 0 to t-elapsed
perform until t-elapsed > 0.5 *> one-half second
accept t-end from time
perform get-elapsed
end-perform
.
get-elapsed.
if t-start > t-end
move 86400 to t-elapsed
else
move 0 to t-elapsed
end-if
compute t-elapsed = t-elapsed
+ (t-end-hour - t-start-hour) * 3600
+ (t-end-minute - t-start-minute) * 60
+ (t-end-second - t-start-second)
end-compute
.
Output: (shows a delay of 0.55 seconds)
21424364
21424419
The initial PERFORM WITH TEST AFTER ... is nothing like the code I provided in: Cobol-Restart from the program , so I turned it into comments. It should be removed.
If you want to use SLEEP-SEC instead of a fixed value, replace the 0.5 with SLEEP-SEC; but provide a VALUE clause for SLEEP-SEC or MOVE a value to it before the displaying the menu.
For example, in your code (with most code removed):
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SLEEP-SEC PIC S9(2)V9(2).
01 A PIC S9(7)V9(7).
01 B PIC S9(7)V9(7).
01 C PIC S9(7)V9(7).
01 D PIC S9(11)V9(7).
01 INPUT1 PIC 9(14).
01 Q PIC X VALUE "Y".
01 t-start.
03 t-start-hour pic 99.
03 t-start-minute pic 99.
03 t-start-second pic 99v99.
01 t-end.
03 t-end-hour pic 99.
03 t-end-minute pic 99.
03 t-end-second pic 99v99.
77 t-elapsed pic 9(7)v99.
PROCEDURE DIVISION.
MAIN.
* PERFORM WITH TEST AFTER
* UNTIL Q ="YES" OR "Y" OR "y" OR "yes" OR "Yes"
* END-PERFORM.
DISPLAY "CALCULATOR".
DISPLAY "WHAT DO YOU WANT DO DO?".
DISPLAY "1 ADDITION".
DISPLAY "15 EXIT"
DISPLAY "CHOOSE AN OPTION"
ACCEPT INPUT1
EVALUATE INPUT1
WHEN = 15
DISPLAY "OK, GOOD JOB :)"
STOP RUN
WHEN = 1
DISPLAY "FIRST NUMBER"
ACCEPT A
DISPLAY "SECOND NUMBER"
ACCEPT B
COMPUTE C= A + B
DISPLAY "Computing"
PERFORM timed-delay
DISPLAY "(" A ")" "+" "(" B ")" "RESULTS..."
DISPLAY C
END-EVALUATE
IF INPUT1 NOT = 15
DISPLAY "DO YOU WANT TO DO OTHER CALCULATIONS?"
ACCEPT Q
IF Q = "YES" OR "Y" OR "y" OR "yes" OR "Yes" GO TO MAIN
ELSE DISPLAY "OK, GOOD JOB :)"
END-IF
STOP RUN.
timed-delay.
accept t-start from time
move 0 to t-elapsed
perform until t-elapsed > 0.5 *> one-half second
accept t-end from time
perform get-elapsed
end-perform
.
get-elapsed.
if t-start > t-end
move 86400 to t-elapsed
else
move 0 to t-elapsed
end-if
compute t-elapsed = t-elapsed
+ (t-end-hour - t-start-hour) * 3600
+ (t-end-minute - t-start-minute) * 60
+ (t-end-second - t-start-second)
end-compute
.
I am trying to access the same file to do two certain tasks. The first task is to update, add, and delete records. Access has to be random. The second task is to display all records on console. Access has to be sequential. I receive code 65 File locked from COBOL because the program is trying to access the same file twice the same time. Is there any way to fix this error? Or is there an different way to do this? Or do I have to write a separate program to display the record on console? I am stuck!
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MAST-FILE ASSIGN TO 'G:\CPSC315-COBOL\COBOLAssignments\P15-5\SALES.IND.TXT'
ORGANIZATION IS INDEXED
ACCESS IS RANDOM
RECORD KEY M-SALESPERSON-NUM.
SELECT MAST2-FILE ASSIGN TO 'G:\CPSC315-COBOL\COBOLAssignments\P15-5\SALES.IND.TXT'
ORGANIZATION IS INDEXED
ACCESS IS SEQUENTIAL
RECORD KEY M2-SALESPERSON-NUM.
DATA DIVISION.
FILE SECTION.
FD MAST-FILE
LABEL RECORDS ARE STANDARD.
01 MAST-RECORD.
05 M-SALESPERSON-NUM PIC XXX.
05 M-CUSTOMER-NAME PIC X(15).
05 M-TOTAL-SALES PIC 9(5)V99.
05 M-COST-OF-SALES PIC 9(4)V99.
FD MAST2-FILE
LABEL RECORDS ARE STANDARD.
01 MAST2-RECORD.
05 M2-SALESPERSON-NUM PIC X(3).
05 M2-SALESPERSON-NAME PIC X(15).
05 M2-TOTAL-SALES PIC 9(5)V99.
05 M2-COST-OF-SALES PIC 9(4)V99.
WORKING-STORAGE SECTION.
01 SALES-DATA.
05 SALESPERSON-NUM PIC X(3).
05 SALESPERSON-NAME PIC X(15).
05 TOTAL-SALES PIC 9(5)V99.
05 COST-OF-SALES PIC 9(4)V99.
01 OUTPUT-RECORD.
05 PIC X(1) VALUE SPACES.
05 O-SALESPERSON-NUM PIC X(3).
05 PIC X(3) VALUE SPACES.
05 O-SALESPERSON-NAME PIC X(3).
05 PIC X(3) VALUE SPACES.
05 O-TOTAL-SALES PIC 9(5)V99.
05 PIC X(3) VALUE SPACES.
05 O-COST-OF-SALES PIC 9(4)V99.
01 PROGRAM-DATA-ITEMS.
05 I-SALESPERSON-NUM PIC XXX.
05 WAIT-OK PIC X.
05 CHOICE PIC 9 VALUE 0.
05 READ-OK PIC X.
05 REWRITE-OK PIC X.
05 DELETE-OK PIC X.
PROCEDURE DIVISION.
10-MAINLINE.
OPEN I-O MAST-FILE
OPEN INPUT MAST2-FILE
PERFORM 20-PROCESS-LOOP
CLOSE MAST-FILE
MAST2-FILE
STOP RUN.
20-PROCESS-LOOP.
PERFORM UNTIL CHOICE = 5
PERFORM 30-DISPLAY-MENU
EVALUATE CHOICE
WHEN 1
PERFORM 40-UPD-SALES
WHEN 2
PERFORM 90-ADD-SALES
WHEN 3
PERFORM 110-DELETE-SALES
WHEN 4
PERFORM 120-DISPLAY-SALES
END-EVALUATE
END-PERFORM.
30-DISPLAY-MENU.
DISPLAY 'SALES MAINTENANCE SYSTEM'
DISPLAY ' '
DISPLAY ' SELECT ONE:'
DISPLAY ' '
DISPLAY ' 1. UPDATE SALES RECORD'
DISPLAY ' 2. ADD SALES RECORD'
DISPLAY ' 3. DELETE SALES RECORD'
DISPLAY ' 4. DISPLAY SALES RECORD'
DISPLAY ' 5. QUIT'
DISPLAY ' '
DISPLAY 'ENTER CHOICE (1 - 5): ' WITH NO ADVANCING
ACCEPT CHOICE
PERFORM UNTIL CHOICE >= 1 AND <= 5
DISPLAY ' '
DISPLAY 'ERROR: ENTER CHOICE (1 - 5): ' WITH NO ADVANCING
ACCEPT CHOICE
END-PERFORM.
40-UPD-SALES.
DISPLAY 'UPDATE SALES: ENTER SALESPERSON NUMBER: ' WITH NO ADVANCING
ACCEPT SALESPERSON-NUM
MOVE SALESPERSON-NUM TO M-SALESPERSON-NUM
PERFORM 50-READ-RECORD
IF READ-OK = 'N'
DISPLAY 'RECORD DOES NOT EXIST - PRESS ENTER'
ACCEPT WAIT-OK
ELSE
DISPLAY SALES-DATA
PERFORM 100-INPUT-NEW-RECORD
PERFORM 60-REWRITE-RECORD
END-IF.
50-READ-RECORD.
MOVE 'Y' TO READ-OK
READ MAST-FILE INTO SALES-DATA
INVALID KEY
MOVE 'N' TO READ-OK
END-READ.
60-REWRITE-RECORD.
REWRITE MAST-RECORD FROM SALES-DATA
INVALID KEY
DISPLAY 'REWRITE ERROR: SALESPERSON NUMBER ' SALESPERSON-NUM
DISPLAY 'PRESS ENTER TO CONTINUE'
ACCEPT WAIT-OK
END-REWRITE.
70-WRITE-RECORD.
MOVE 'Y' TO REWRITE-OK
WRITE MAST-RECORD FROM SALES-DATA
INVALID KEY
MOVE 'N' TO REWRITE-OK
END-WRITE
IF REWRITE-OK = 'N'
DISPLAY 'WRITE ERROR: SALESPERSON NUMBER ' SALESPERSON-NUM
DISPLAY 'PRESS ENTER TO CONTINUE'
ACCEPT WAIT-OK
END-IF.
80-DELETE-RECORD.
MOVE 'Y' TO DELETE-OK
DELETE MAST-FILE
INVALID KEY
MOVE 'N' TO DELETE-OK
END-DELETE
IF REWRITE-OK = 'N'
DISPLAY 'WRITE ERROR: SALESPERSON NUMBER ' SALESPERSON-NUM
DISPLAY 'PRESS ENTER TO CONTINUE'
ACCEPT WAIT-OK
END-IF.
90-ADD-SALES.
DISPLAY 'ADD SALES RECORD: ENTER SALESPERSON NUMBER: ' WITH NO ADVANCING
ACCEPT SALESPERSON-NUM
MOVE SALESPERSON-NUM TO M-SALESPERSON-NUM
PERFORM 50-READ-RECORD
IF READ-OK = 'Y'
DISPLAY 'RECORD ALREADY EXISTS - PRESS ENTER'
ACCEPT WAIT-OK
ELSE
PERFORM 100-INPUT-NEW-RECORD
PERFORM 70-WRITE-RECORD
IF REWRITE-OK = 'Y'
DISPLAY 'RECORD ' SALESPERSON-NUM ' ADDED TO FILE'
DISPLAY 'PRESS ENTER TO CONTINUE'
ACCEPT WAIT-OK
END-IF
END-IF.
100-INPUT-NEW-RECORD.
MOVE SALESPERSON-NUM TO M-SALESPERSON-NUM
DISPLAY ' ENTER SALESPERSON NAME: ' WITH NO ADVANCING
ACCEPT SALESPERSON-NAME
DISPLAY ' ENTER TOTAL SALES: ' WITH NO ADVANCING
ACCEPT TOTAL-SALES
DISPLAY ' ENTER COST OF SALES: ' WITH NO ADVANCING
ACCEPT COST-OF-SALES.
110-DELETE-SALES.
DISPLAY 'DELETE SALES RECORD: ENTER SALESPERSON NUMBER: ' WITH NO ADVANCING
ACCEPT SALESPERSON-NUM
MOVE SALESPERSON-NUM TO M-SALESPERSON-NUM
PERFORM 50-READ-RECORD
IF READ-OK = 'N'
DISPLAY 'RECORD DOES NOT EXIST - PRESS ENTER'
ACCEPT WAIT-OK
ELSE
PERFORM 80-DELETE-RECORD
IF DELETE-OK = 'Y'
DISPLAY 'RECORD DELETED - PRESS ENTER'
ACCEPT WAIT-OK
END-IF
END-IF.
120-DISPLAY-SALES.
MOVE SALESPERSON-NUM TO M2-SALESPERSON-NUM
MOVE SALESPERSON-NAME TO M2-SALESPERSON-NAME
MOVE TOTAL-SALES TO M2-TOTAL-SALES
MOVE COST-OF-SALES TO M2-COST-OF-SALES
READ MAST2-FILE
AT END MOVE HIGH-VALUES TO M2-SALESPERSON-NUM
END-READ
PERFORM UNTIL M2-SALESPERSON-NUM = HIGH-VALUES
MOVE M2-SALESPERSON-NUM TO O-SALESPERSON-NUM
MOVE M2-SALESPERSON-NAME TO O-SALESPERSON-NAME
MOVE M2-TOTAL-SALES TO O-TOTAL-SALES
MOVE M2-COST-OF-SALES TO O-COST-OF-SALES
DISPLAY OUTPUT-RECORD
READ MAST2-FILE
AT END MOVE HIGH-VALUES TO M2-SALESPERSON-NUM
END-READ
END-PERFORM.
end program Program1.
When you open a file I-O, that means you open it for Input and Output. Get rid of your second file.
To position your file for displaying the data, you can READ with a KEY and then READ ... NEXT ..., or you can use START ... and then READ ... NEXT.
Always use the FILE STATUS in the ASSIGN. Then use the file-status field you tell COBOL to put the file status in to, to check the previous IO. Use it for end-of-file (value of "10"). Use 88s. You don't then need the INVALID KEY and AT END and all the END- statements associated with IO can go, because you then don't have a built-in condition with the IO. Which will simplify things.
Your structure is very good for a beginner. Refreshing to see no PERFORM ... THRU ....
I'd suggest you try the effect of a single full-stop/period in column 12 on a line of its own. You'll then be able to move the last line of code from a paragraph without having to think about the full-stop/period attached to it (because it isn't attached to it).
Use more PERFORMs. OPEN and CLOSE are not vital to the logic of the program. Hide them away in paragraphs, do the FILE STATUS checking on them. Same with the READ/WRITE/DELETE and any other IO statements you end up with. Hide them in well-named procedures which you PERFORM.
Consider the size (number of lines) of some of your IFs. Put the code in a well-named procedure, and the code can be "read" at a high level by a human, with the detail only being looked at if needed.
Do not, do not, do not, mess around with two files. Do not, do not, do not OPEN the same file twice (I've written a few COBOL programs in my time, and I've never, ever, considered that a reasonable way to achieve anything, let alone the simple task you have).
You may want to consider DYNAMIC instead of RANDOM (this is what it is for). You use RANDOM if you are only doing random access. You actually want to do sequential access as well (look for references to skip-sequential access for further discussion).
SELECT MAST-FILE ASSIGN TO 'G:\CPSC315-COBOL\COBOLAssignments\P15-5\SALES.IND.TXT'
ORGANIZATION IS INDEXED
ACCESS IS RANDOMREWRITE-OK = 'N'
RECORD KEY M-SALESPERSON-NUM
FILE STATUS IS W-MAST-FILE-STATUS.
...
01 W-MAST-FILE-STATUS PIC XX.
88 W-MAST-FILE-LAST-IO-OK VALUE "00".
88 W-MAST-FILE-EOF VALUE "10".
88 W-MAST-FILE-REC-NOT-FOUND VALUE "23".
88 W-MAST-FILE-OR-OR-NOT-FOUND VALUE "00" "23".
...
50-READ-RECORD.
MOVE 'Y' TO READ-OK
READ MAST-FILE INTO SALES-DATA
INVALID KEY
MOVE 'N' TO READ-OK
END-READ.
Becomes:
50-READ-RECORD.
READ MAST-FILE KEY key-name INTO SALES-DATA
IF NOT ( W-MAST-FILE-OR-OR-NOT-FOUND )
some code to deal with the pickle, which is nothing to do with
business-logic, so hide it away
END-IF
.
80-DELETE-RECORD.
MOVE 'Y' TO DELETE-OK
DELETE MAST-FILE
INVALID KEY
MOVE 'N' TO DELETE-OK
END-DELETE
IF REWRITE-OK = 'N'
DISPLAY 'WRITE ERROR: SALESPERSON NUMBER ' SALESPERSON-NUM
DISPLAY 'PRESS ENTER TO CONTINUE'
ACCEPT WAIT-OK
END-IF.
Becomes:
80-DELETE-RECORD.
DELETE MAST-FILE
IF NOT W-MAST-FILE-LAST-IO-OK
DISPLAY 'WRITE ERROR: SALESPERSON NUMBER ' SALESPERSON-NUM
DISPLAY 'PRESS ENTER TO CONTINUE'
ACCEPT WAIT-OK
END-IF
.
Each IO paragraph becomes self-contained, self-verifying, and the FILE STATUS field naturally bears only good conditions when you are in your business logic. The "can't happen" (but will, one day) you deal with in these paragraphs.
You code elsewhere simplifies. Your number of "flags" is reduced (the value of the FILE STATUS field replaces the need for the flags) your IOs have no conditional part, so don't need the END- scope delimiter.
A tip about numbering paragraphs. Don't do it until you have tested sufficiently that you are happy with the structure of the logic. Once you are happy with that, rearrange the paragraphs so that the paragraph is always physically after the PERFORM of it. Then put the numbers on. The physical layout of your code then represents the structure of your program logic.
If you number first, you'll end up with the situation you have - you have numbers, but they infer nothing. It is much more tedious to "renumber" paragraphs than it is to add paragraph numbers where there were none (use the power of the editor/utilities available to you to do this numbering).
I am trying to make the program below to pull out records that have customer names beginning with letter the "M" and write the records to a temporary file. The program runs but it won't write records to the output file. I debugged the code, and it seems like the code line "WRITE MAST2-RECORD" never runs. It skips this line of code.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MAST-FILE ASSIGN TO 'G:\CPSC315-COBOL\COBOLAssignments\P15-1\OVERDUE.IND.TXT'
ORGANIZATION IS INDEXED
ACCESS IS RANDOM
RECORD KEY IS M-ACCT-NUM.
SELECT MAST2-FILE ASSIGN TO 'G:\CPSC315-COBOL\COBOLAssignments\P15-1\OVERDUE2.IND.TXT'
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD MAST-FILE
LABEL RECORDS ARE STANDARD.
01 MAST-RECORD.
05 M-ACCT-NUM PIC X(4).
05 M-CUSTOMER-NAME PIC X(15).
05 M-DAYS-OVERDUE PIC 99.
05 M-BALANCE-DUE PIC 999V99.
FD MAST2-FILE
LABEL RECORDS ARE STANDARD.
01 MAST2-RECORD PIC X(50).
WORKING-STORAGE SECTION.
01 COUNTER PIC 9.
01 PROGRAM-DATA-ITEMS.
05 WRITE-OK PIC X VALUE 'Y'.
PROCEDURE DIVISION.
10-MAINLINE.
OPEN OUTPUT MAST-FILE
OUTPUT MAST2-FILE
PERFORM 20-LOAD-MAST-FILE
CLOSE MAST-FILE
MAST2-FILE
STOP RUN.
20-LOAD-MAST-FILE.
PERFORM 30-INPUT-INDEX
PERFORM UNTIL M-ACCT-NUM = 0 OR WRITE-OK = 'N'
PERFORM 40-WRITE-FILE
PERFORM 50-FIND-CUSTOMER-START-WITH-M
PERFORM 30-INPUT-INDEX
END-PERFORM.
30-INPUT-INDEX.
DISPLAY 'ENTER ACCOUNT NUMBER (0 TO QUIT): ' WITH NO ADVANCING
ACCEPT M-ACCT-NUM.
40-WRITE-FILE.
DISPLAY ' ENTER CUSTOMER NAME: ' WITH NO ADVANCING
ACCEPT M-CUSTOMER-NAME
DISPLAY ' ENTER DAYS OVERDUE: ' WITH NO ADVANCING
ACCEPT M-DAYS-OVERDUE
DISPLAY ' ENTER BALANCE DUE: ' WITH NO ADVANCING
ACCEPT M-BALANCE-DUE
WRITE MAST-RECORD
INVALID KEY
MOVE 'N' TO WRITE-OK
DISPLAY 'ERROR ' MAST-RECORD
END-WRITE.
50-FIND-CUSTOMER-START-WITH-M.
MOVE 0 TO COUNTER
INSPECT MAST-RECORD TALLYING COUNTER FOR ALL ' M '
IF COUNTER > 0
WRITE MAST2-RECORD
END-IF.
end program Program1.
You are looking for blank-M-blank, across the entire record.
What you say you want to do is fine customer-names which begin with M.
05 M-CUSTOMER-NAME.
10 M-CUSTOMER-NAME-FIST-CHARACTER PIC X.
88 M-CUSTOMER-NAME-START-M VALUE "M".
If you use that definition in place of what you have, and use the 88 in the test for your write, you should get what you want.
Eg replace:
50-FIND-CUSTOMER-START-WITH-M.
MOVE 0 TO COUNTER
INSPECT MAST-RECORD TALLYING COUNTER FOR ALL ' M '
IF COUNTER > 0
WRITE MAST2-RECORD
END-IF.
By:
50-FIND-CUSTOMER-START-WITH-M.
IF M-CUSTOMER-NAME-START-M
WRITE MAST2-RECORD
END-IF
.
Simpler, easier to understand, so easier to maintain.
You should consider the possible "validity" of your names. In a good system, there will be no leading blanks. In a poor system there may be.
To deal with that, test the first byte of the customer-name for being space as well, if so, test the customer-name for entirely space. If not entirely space, loop until you find the first non-blank. Test that first non-blank for M. So in this case you have two tests.
You can assess the quality of your data separately by copying and cutting-down this program and reporting/outputting where the first byte of the customer-name is blank.
Once you know that, you go to the analyst (tutor) and ask if you need to deal with possible leading blanks. If you don't, keep the test for blank in your actual program, and crash in that case :-)
Having issues with using the GO-TO statement. This is suppose to run until the user types 'END'. If I type 'END' when I first open the program it will close out but if I type it after entering valid data for the first pass thru it just continues to bring back the user input data screen.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT USED-CAR-FILE-OUT
ASSIGN TO 'USED-CAR.RPT'
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD USED-CAR-FILE-OUT.
01 USED-CAR-RECORD-OUT PIC X(80).
WORKING-STORAGE SECTION.
01 FIRST-RECORD PIC X(3) VALUE 'YES'.
01 ID-CODE PIC X(3).
01 TOTAL-CASH-PAYMENT PIC 9(5).
01 MONTHLY-PAYMENT PIC 9(4).
01 NUMBER-OF-MONTHS PIC 9(3).
01 TOTAL-BALANCE PIC S9(6)V99 VALUE ZEROS.
01 INTEREST-COLLECTED PIC 99V99 VALUE ZEROS.
01 MONTH-DIFF PIC 99 VALUE ZEROS.
01 MONTH-NUM PIC 99 VALUE ZEROS.
01 YEAR-NUM PIC 99 VALUE ZEROS.
01 ID-HOLD PIC X(3) VALUE SPACES.
01 PAYMENT-HOLD PIC X(3) VALUE SPACES.
01 DETAIL-LINE.
05 ID-CODE-DL PIC X(3).
05 PIC X(3) VALUE SPACES.
05 PIC X(4) VALUE 'Yr='.
05 YEAR-NUMBER-DL PIC Z9.
05 PIC X(4) VALUE SPACES.
05 PIC X(4) VALUE 'MO='.
05 MONTH-NUMBER-DL PIC Z9.
05 PIC X(4) VALUE SPACES.
05 PIC X(5) VALUE 'Pmt='.
05 PAYMENT-DL PIC $$$,$$$.
05 PIC X(4) VALUE SPACES.
05 PIC X(5) VALUE 'Int='.
05 INTEREST-EARNED-DL PIC $$$$.99.
05 PIC X(3) VALUE SPACES.
05 PIC X(5) VALUE 'Bal='.
05 BALANCE-DL PIC $$$,$$$.99.
PROCEDURE DIVISION.
100-MAIN.
OPEN OUTPUT USED-CAR-FILE-OUT
PERFORM 200-USER-INPUT THRU 299-EXIT
CLOSE USED-CAR-FILE-OUT
STOP RUN.
200-USER-INPUT.
DISPLAY 'Used Car Sales Report'
DISPLAY 'Enter the ID code (or END) - maxium three char.'
ACCEPT ID-CODE
IF ID-CODE = 'END'
GO TO 299-EXIT
END-IF
DISPLAY 'Enter the Total Cash Payment - maximum five digits'
ACCEPT TOTAL-CASH-PAYMENT
DISPLAY 'Enter the Monthly Payment - maximum four digits'
ACCEPT MONTHLY-PAYMENT
DISPLAY 'Enter the Number of Months - maximum three digits'
ACCEPT NUMBER-OF-MONTHS
PERFORM 300-RECORD-PROCESS.
299-EXIT.
EXIT.
300-RECORD-PROCESS.
IF TOTAL-CASH-PAYMENT > 0
IF FIRST-RECORD = 'YES'
MOVE ID-CODE TO ID-CODE-DL
MOVE 1 TO YEAR-NUMBER-DL
MOVE 1 TO YEAR-NUM
move 1 to MONTH-NUMBER-DL
MOVE TOTAL-CASH-PAYMENT TO PAYMENT-DL
MOVE PAYMENT-DL TO MONTHLY-PAYMENT
ADD MONTHLY-PAYMENT TO TOTAL-BALANCE
MOVE 'NO' TO FIRST-RECORD
END-IF
COMPUTE INTEREST-COLLECTED ROUNDED = TOTAL-BALANCE
* .0175 / 12
MOVE INTEREST-COLLECTED TO INTEREST-EARNED-DL
ADD INTEREST-COLLECTED TO TOTAL-BALANCE
MOVE TOTAL-BALANCE TO BALANCE-DL
ADD 1 TO MONTH-DIFF
MOVE MONTH-DIFF TO MONTH-NUMBER-DL
IF MONTH-NUMBER-DL > 13
ADD 1 TO MONTH-NUM
MOVE MONTH-NUM TO MONTH-NUMBER-DL
END-IF
IF MONTH-NUMBER-DL = 13
MOVE 1 TO MONTH-NUM
MOVE MONTH-NUM TO MONTH-NUMBER-DL
END-IF
IF MONTH-NUM = 1
ADD 1 TO YEAR-NUM
MOVE YEAR-NUM TO YEAR-NUMBER-DL
END-IF
MOVE DETAIL-LINE TO USED-CAR-RECORD-OUT
WRITE USED-CAR-RECORD-OUT
AFTER ADVANCING 1 LINE
MOVE ID-HOLD TO ID-CODE-DL
IF MONTH-DIFF < NUMBER-OF-MONTHS
PERFORM 300-RECORD-PROCESS
END-IF
PERORM 200-USER-INPUT
END-IF
IF MONTHLY-PAYMENT > 0
IF FIRST-RECORD = 'YES'
MOVE ID-CODE TO ID-CODE-DL
MOVE 1 TO YEAR-NUMBER-DL
MOVE 1 TO YEAR-NUM
move 1 to MONTH-NUMBER-DL
MOVE 'NO' TO FIRST-RECORD
END-IF
MOVE MONTHLY-PAYMENT TO PAYMENT-DL
MOVE PAYMENT-DL TO MONTHLY-PAYMENT
ADD MONTHLY-PAYMENT TO TOTAL-BALANCE
COMPUTE INTEREST-COLLECTED ROUNDED = TOTAL-BALANCE
* .0175 / 12
MOVE INTEREST-COLLECTED TO INTEREST-EARNED-DL
ADD INTEREST-COLLECTED TO TOTAL-BALANCE
MOVE TOTAL-BALANCE TO BALANCE-DL
ADD 1 TO MONTH-DIFF
MOVE MONTH-DIFF TO MONTH-NUMBER-DL
IF MONTH-NUMBER-DL > 13
ADD 1 TO MONTH-NUM
MOVE MONTH-NUM TO MONTH-NUMBER-DL
END-IF
IF MONTH-NUMBER-DL = 13
MOVE 1 TO MONTH-NUM
MOVE MONTH-NUM TO MONTH-NUMBER-DL
END-IF
IF MONTH-NUM = 1
ADD 1 TO YEAR-NUM
MOVE YEAR-NUM TO YEAR-NUMBER-DL
END-IF
MOVE DETAIL-LINE TO USED-CAR-RECORD-OUT
WRITE USED-CAR-RECORD-OUT
AFTER ADVANCING 1 LINE
MOVE ID-HOLD TO ID-CODE-DL
IF TOTAL-CASH-PAYMENT > 0
MOVE 0 TO TOTAL-CASH-PaYMENT
MOVE 0 TO PAYMENT-DL
END-IF
IF MONTH-DIFF < NUMBER-OF-MONTHS
PERFORM 300-RECORD-PROCESS
END-IF
PERFORM 200-USER-INPUT
END-IF.
EDIT solved the issue below
I also am having issues if months > 24. I step through the program and it shows my last detail line as the correct result but yet my output stops at 24 months. Thanks in advance.
AAAAAAAk!
PERFORM SEVERE-BEATING-ON-WHOEVER-MENTIONED-PERFORM-THROUGH
USING HEAVY-OBJECT
UNTIL PROMISE-EXTRACTED-TO-NEVER-DO-IT-AGAIN.
PERFORM THOUGH is EVIL. It causes layout-dependent code.
At the top control-level, use
PERFORM 200-USER-INPUT
UNTIL ID-CODE = 'END'.
(or possibly use 88 USER-INPUT-ENDED on ID-CODE - matter of style)
How you then determine whether to continue with input in 200-... is your choice, either
IF NOT USER-INPUT-ENDED
DISPLAY 'Enter the Total Cash Payment - maximum five digits'
ACCEPT TOTAL-CASH-PAYMENT
...
ACCEPT NUMBER-OF-MONTHS
PERFORM 300-RECORD-PROCESS.
OR
IF NOT USER-INPUT-ENDED
PERFORM 210-ACCEPT-DETAILS.
210-ACCEPT-DETAILS.
DISPLAY 'Enter the Total Cash Payment - maximum five digits'.
ACCEPT TOTAL-CASH-PAYMENT.
...
ACCEPT NUMBER-OF-MONTHS.
PERFORM 300-RECORD-PROCESS.
Since you PERFORMED 200-... then only 200-... will be executed; 210-... is a new paragraph which can only be reached from 200-... IF END is not entered.
Next step is to slightly modify 300-...
Move the initialisation ( FIRST-RECORD = 'YES' code) before the PERFORM 300-... in 200-... and then modify the PERFORM 300-RECORD-PROCESS. to
PERFORM 300-RECORD-PROCESS
UNTIL TOTAL-BALANCE = 0.
(I'm assuming here that this is the report-terination condition; if it isn't, substitute your report-termination condition)
You can now restructure 300-... to calculate the interest payable, modify the year and month numbers and show the result. ALL of the PERFORMs in 300-... will disappear.
So, in essence you have
MAIN:perform user-input until end-detected.
user-input: get user data; perform calculations until balance is zero.
calculations: one month's calculations at a time.
This also has the advantage that if you choose, you could insert
IF MONTHLY-PAYMENT IS LESS THAN INTEREST-COLLECTED
MOVE 'ERR' TO ID-CODE.
And use 'ERR' in ID-CODE to produce an appropriate error-message in 300-... instead of the progressive report lines AND at the same time assign 0 to TOTAL-BALANCE which terminates the PERFORM 300-... UNTIL ....
Your use of GO TO and PERFORM THROUGH paragraph ranges has corrupted the procedure return mechanism that COBOL
uses to maintain proper program flow of control. In essence, you have a program that is invalid - it might compile
without error but is still an invalid program according to the rules of COBOL.
Here is an outline of what your program is doing from a flow of control perspective. The
mainline program is essentially:
100-MAIN.
PERFORM 200-USER-INPUT THRU 299-EXIT
This is asking COBOL to execute all the code found from the beginning of
200-USER-INPUT through to the end of 299-EXIT. The outline for these
procedures is:
200-USER-INPUT.
IF some condition GO TO 299-EXIT
...
PERFORM 300-RECORD-PROCESS
.
299-EXIT.
Notice that if some condition is true, program flow will skip past the end
of 200-USER-INPUT and jump into 299-EXIT. 299-EXIT does not do anything
very interesting, it is just an empty paragraph serving as the end of a
PERFORMed range of paragraphs.
In paragraph 300-RECORD-PROCESS you have a fair bit of code. The interesting
bit is:
300-RECORD-PROCESS.
...
PERFORM 200-USER-INPUT
Notice that PERFORM 200-USER-INPUT this is not a PERFORM THRU, as you had coded in 100-MAIN.
The problem is that when you get back into 200-USER-INPUT and some codition becomes
true (as it will when you enter 'EXIT'), the flow of control
jumps to 299-EXIT which is past the end of the paragraph
you are currently performing. From this point
forward the flow of control mechanism used by COBOL to manage return from PERFORM verbs has
been corrupted. There is no longer a normal flow of control mechanism to return back to where 200-USER-INPUT
was performed from in 300-RECORD-PROCESS.
What happens next is not what most programmers would expect. Most programmers seem to expect
that when the end of 299-EXIT is reached program flow should return to wherever the last PERFORM
was done. In this case, just after PERFORM 200-USER-INPUT. No, COBOL doesn't work that way, flow of control
will continue with the next executable statement following 299-EXIT. This gets you
right back to the first executable statement in 300-RECORD-PROCESS! And that is why you
are not getting expected behaviour from this program.
Logic flow in COBOL programs must ensure that the end of performed procedures are
always reached in the reverse order from which they were made. This corresponds to the call/return
stack semantics that
most programmers are familiar with.
My advice to you is to avoid the use of PERFORM THRU and GO TO. These are two of the biggest
evils left in the COBOL programming language today. These constructs are hang-overs from a
bygone era of programming and have no constructive benefit today.
Your problem is that you have created an infinite loop for yourself. You 200- paragraph PERFORMs the 300- paragraph, and your 300- paragraph PERFORMS your 200- paragraph.
You need to restructure your program.
A paragraph called 200-USER-INPUT should just concern itself with that.
repeat until end of input
get some input
if there is input to process
process the input
Yoiks! I just noticed you also PERFORM 300- from within 300-!