I am attempting to perform simple addition, but I'm not sure how to do this in cobol. I would like to add the last four years of tuition to a variable total_tuition. However, it will only add this once my first loop has been completed, hence giving me an addition of the last years tuition 4 times (which is not what I want). I completed the task in python to help clarify what I am trying to accomplish. Thank you!
WORKING-STORAGE SECTION.
01 tuition pic 9(5)v99.
01 increase pic 9(5)v99 value 00000.05.
01 final_tuition pic 9(5)v99.
01 total_tuition pic 9(5)v99 value 00000.00.
01 temp pic 9(7)v99.
01 num pic 9 value 1.
PROCEDURE DIVISION.
MAIN-PROCEDURE.
display 'Please enter the tuition amount: '.
accept tuition.
perform aLoop 10 times.
display 'Tuition after 10 years is: $', final_tuition.
perform bLoop with test after until num > 6.
display '4-year tuition in 10 years is: $', total_tuition.
STOP RUN.
aLoop.
multiply tuition by increase giving temp .
add temp to tuition giving final_tuition.
set tuition to final_tuition.
bLoop.
add final_tuition to total_tuition giving total_tuition.
add 1 to num.
display total_tuition.
Python working solution:
n = 0
tuition = 10000
increase = .05
temp = 0
total = 0
final = 0
i = 0
while n < 10 :
temp = increase * tuition
final = tuition + temp
tuition = final
if n >= 6:
total = final + total
print(i, ") total", total)
i = i + 1
n = n+1
print(final, total)
In the Python version, you have an if statement. In the COBOL version, you have a second loop.
Try this code:
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO-WORLD.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 tuition pic 9(5)v99 value 10000.
01 increase pic 9(5)v99 value 00000.05.
01 final_tuition pic 9(5)v99.
01 total_tuition pic 9(5)v99 value 00000.00.
01 temp pic 9(7)v99.
01 num pic 9 value 0.
PROCEDURE DIVISION.
MAIN-PROCEDURE.
perform aLoop 10 times.
display 'Tuition after 10 years is: $', final_tuition.
display '4-year tuition in 10 years is: $', total_tuition.
STOP RUN.
aLoop.
multiply tuition by increase giving temp .
add temp to tuition giving final_tuition.
set tuition to final_tuition.
if num >= 6 then
add final_tuition to total_tuition giving total_tuition
end-if
add 1 to num.
Output
Tuition after 10 years is: $16288.91
4-year tuition in 10 years is: $60647.68
Related
I'm reading a file into a table, note the first line is not part of the table.
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
In the code, the table is represented as follows:
01 GRADES.
05 GRADE OCCURS 1 TO 100 TIMES DEPENDING ON RECORD-COUNT INDEXED BY J.
10 ASSIGNMENT-NAME PIC X(20).
10 CATEGORY PIC X(20).
10 POINTS-POSSIBLE PIC 9(14).
10 POINTS-EARNED PIC 9(14).
I have a few other accumulator variables designated for calculating sums/percentages later on.
01 RECORD-COUNT PIC 9(8) VALUE 0.
01 TOTAL-EARNED-POINTS PIC 9(14).
01 TOTAL-POSSIBLE-POINTS PIC 9(14) VALUE 0.
My issue is, while I'm reading the records, line by line, I want to do the following:
ADD POINTS-EARNED(RECORD-COUNT) TO TOTAL-EARNED-POINTS
Where RECORD-COUNT is the current position in the iteration.
I expect the value of TOTAL-EARNED-POINTS after the first iteration to simply be 5, right?
However, when I DISPLAY the value of TOTAL-EARNED-POINTS, the console reads:
50000000000000
Is this 50 trillion? Or is it a funny looking representation of the number 5?
How can I write this so that I can do proper mathematics with it to print a proper grade report?
EDIT: I know it's likely that there's better ways of writing this program but I've never used cobol before attempting to write this program, and I probably won't use it ever again, or at least for a very long time. This is for a class, so as long as I can print my output properly, I'm good.
Full code, so far:
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 INDEXED BY I.
10 INPUT-ASSIGNMENT-NAME PIC X(20).
10 INPUT-CATEGORY PIC X(20).
10 INPUT-POINTS-POSSIBLE PIC 9(14).
10 INPUT-POINTS-EARNED PIC 9(14).
WORKING-STORAGE SECTION.
77 GRADES-FILE-EOF PIC 9.
01 RECORD-COUNT PIC 9(8) VALUE 0.
01 TOTAL-EARNED-POINTS PIC 9(4) 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 INDEXED BY J.
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-GRADES TO GRADE(RECORD-COUNT)
SET TOTAL-EARNED-POINTS UP BY 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.
Edit 2: Upon implementing the given answer to convert the input from the file to a numeric form, the FIRST ROW of the table reads fine, but from then on it's all blank values.
Here's the READ block's new code, I'm not sure if there's a more efficient way to read and convert specific fields in a group field but this is how I assumed it should be done.
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.
is it a funny looking representation of the number 5?
No, it is an unchecked fatal exception: EC-DATA-INCOMPATIBLE.
The reason:
Your data definition and record-definition doesn't match:
10 POINTS-EARNED PIC 9(14).
would be
"00000000000005"
not
"5 "
which looks like the better definition for would be
10 SOME-POSSIBILY-NUMERIC-DATA PIC X(14).
If you use GnuCOBOL as the tags suggest, then add -debug to the compile command and you will see the fatal exception stopping the program (the COBOL standard defines that all exception checking is off by default, in my opinion: because of legacy and performance, but at least for developing and testing it is very reasonable to activate them [in most cases it is even more reasonable to let the program abend instead of doing wrong math when the test is over]).
As with any computer language you should be very sure to have valid data (never trust external data, no matter if it is part of a blockchain or a text file you read in).
How can I write this so that I can do proper mathematics with it to
print a proper grade report?
If you want to go with "bad data is just ignored" (which may be appropriate here) just convert it:
MOVE FUNCTION NUMVAL (SOME-POSSIBILY-NUMERIC-DATA)
TO POINTS-EARNED(RECORD-COUNT)
Otherwise do an explicit check (either of completely numeric [own check], or numeric with possible spaces to the left/right FUNCTION TEST-NUMVAL) and stop the program/skip the bad line with a DISPLAY ... UPON SYSERR or whatever is appropriate for you.
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!
I'm trying to display the day of the year in cobolol but it doesn't work. this code doesn't display anything and I can't figure out why. Can anyone help me, please?
IDENTIFICATION DIVISION.
PROGRAM-ID. SAMPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 T-MOFYEAR.
05 T-M-LINE OCCURS 12.
10 T-EL-MN PIC 9(2).
Here I take the value of year month and day
01 YEAR PIC 9(4) VALUE 2017.
01 MONTH PIC 9(2) VALUE 01.
01 DAYY PIC 9(2) VALUE 04.
01 I PIC 9(2).
01 MN PIC 9(2).
01 DOFY PIC 9(3).
PROCEDURE DIVISION.
MAIN SECTION.
MAINA.
PERFORM INIT
PERFORM
VARYING I
FROM 1 BY 1
UNTIL I = MONTH
PERFORM LEAP
END-PERFORM
.
MAINZ.
STOP RUN.
Here I init the month of the year
INIT SECTION.
INITA.
MOVE 31 TO T-EL-MN(1)
MOVE 28 TO T-EL-MN(2)
MOVE 31 TO T-EL-MN(3)
MOVE 30 TO T-EL-MN(4)
MOVE 31 TO T-EL-MN(5)
MOVE 30 TO T-EL-MN(6)
MOVE 31 TO T-EL-MN(7)
MOVE 31 TO T-EL-MN(8)
MOVE 30 TO T-EL-MN(9)
MOVE 31 TO T-EL-MN(10)
MOVE 30 TO T-EL-MN(11)
MOVE 31 TO T-EL-MN(12)
.
INITZ.
EXIT.
Here I check if the year is a leap
LEAP SECTION.
LEAPA.
EVALUATE TRUE
WHEN FUNCTION MOD (YEAR 4) NOT ZERO
WHEN FUNCTION MOD (YEAR 100) ZERO
AND FUNCTION MOD (YEAR 400) NOT ZERO
ADD T-EL-MN(I), DAYY TO DOFY
DISPLAY "DAY OF YEAR = " DOFY
WHEN OTHER
DISPLAY 'IT IS A LEAP YEAR ' YEAR
MOVE 29 TO T-EL-MN(2)
ADD T-EL-MN(I), DAYY TO DOFY
DISPLAY "DAY OF YEAR = " DOFY
END-EVALUATE
.
LEAPZ.
EXIT.
It doesn't display anything because you told it to never DISPLAY anything. See explanation in the appendix.
The possible solution to fix this code part may be to change the code to
UNTIL I > MONTH
And in general it is likely a good idea to check your compiler for support of
01 small-yd.
05 syd-year pic 9(02).
05 syd-day pic 9(03).
01 full-yd.
05 fyd-year pic 9(04).
05 fyd-day pic 9(03).
ACCEPT small-yd FROM DAY
ACCEPT full-yd FROM DAY YYYYDDD
Appendix: No DISPLAY because:
01 MONTH PIC 9(2) VALUE 01.
PERFORM
VARYING I
FROM 1 BY 1
UNTIL I = MONTH
PERFORM LEAP
END-PERFORM
equals
PERFORM
VARYING I
FROM 1 BY 1
UNTIL I = 1
equals
PERFORM
UNTIL 1 = 1
equals
MAIN SECTION.
MAINA.
PERFORM INIT
.
MAINZ.
STOP RUN.
and there is no DISPLAY in INIT SECTION.
If you only want to display the day of the year in cobol, isn't it much easier to use something like:
...
WORKING-STORAGE SECTION.
01 TESTDATE PIC 9(8).
...
PROCEDURE DIVISION.
MAIN SECTION.
MOVE FUNCTION CURRENT-DATE (1:8) TO TESTDATE.
COMPUTE TESTDATE = FUNCTION DAY-OF-INTEGER(
FUNCTION INTEGER-OF-DATE(TESTDATE)
).
DISPLAY "DAY OF YEAR " TESTDATE (6:3).
...
AFAIK these intrinsic functions are available at all cobol compilers.
** is used to compute exponentiation values in Cobol. That works OK with "small" numbers for example 5 ** 10 and so on.
Now there is a task where we should find X ** 365 + X ** 364 + X ** 363 + X ** 362 + X ** 361 + ... etc. where X is a decimal number with V9(02).
If ** is used with higher numbers for example 5.00 ** 41 then Truncation of high order digit positions occurs due to the fact that I'm able to keep PIC S9(29)V9(02) COMP-3 MAX (31 digits) with CBL ARITH(EXTEND) option.
Is there a work-around for this / Exponential function?
Is it possible at all on Cobol Enterprise for z/Os?
You could try something like this
The array "big-one" is like having a 1000 byte long numeric field.
i.e. pic 9(1000)
For "5 ** 365", you set mult to 5 and thymes to 365.
Since normal cobol won't support arithmetic on such large numbers, you have to do it yourself.
Start by setting big9(1000) to 1.
This is like having pic 9(1000) value 1.
Then loop "thymes" times thru paragraph "do-mult" that multiplies the digits of big-one by "mult', handling any "karry", by adding it to the intermediate result when calculating on the previous digit.
At the end, the digits of big-one represent the result.
IDENTIFICATION DIVISION.
PROGRAM-ID. cb043.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
DATA DIVISION.
FILE SECTION.
WORKING-STORAGE SECTION.
01 sub1 pic 9(05).
01 big-one.
03 big9 occurs 1000 pic 9(01).
01 mult pic 9(06) value 5.
01 thymes pic 9(03) value 365.
01 sub2 pic 9(05).
01 sub3 pic 9(05).
01 interm pic 9(10).
01 filler redefines interm.
03 karry pic 9(09).
03 rite pic 9.
01 staw pic 9(09).
PROCEDURE DIVISION.
MAINLINE.
******* zeroise the big array
perform varying sub1
from 1 by 1
until sub1 > 1000
move 0 to big9(sub1)
end-perform.
******* make big-one like pic 9(1000) value 1
MOVE 1 to big9(1000).
******* do the multiplication "thymes" times
perform varying sub2
from 1 by 1
until sub2 > thymes
perform do-mult
end-perform.
******* find the first non-zero digit
perform varying sub1
from 1 by 1
until big9(sub1) not = 0
end-perform.
******* display the digits of the result
perform varying sub2
from sub1 by 1
until sub2 > 1000
display sub2 big9(sub2)
end-perform.
stop run.
do-mult.
******* zeroise the stored carry field, "staw"
move 0 to staw.
******* multiply every digit of "big-one"
******* starting at big9(1000) and working backwards to big9(1)
******* the left hand 9 bytes of "interm", represent any carry, as "karry"
******* which is stored in "staw" and is added when the next calculation is
******* done
perform varying sub3
from 1000 by -1
until sub3 = 0
compute interm = (big9(sub3) * mult)
+ staw
move karry to staw
move rite to big9(sub3)
end-perform.
Some problems:
First, 5 ** 365 requires 255 digits.
Second pic S9(29)V9(02) requires that x be somewhat less than 1.2.
However, x is defined as V9(02) (unless something more was intended). The "work-around" is logarithms. FUNCTION LOG10 is available in Enterprise COBOL.
identification division.
program-id. big-exp.
data division.
1 x binary pic v99 value 0.99.
1 log10-of-x comp-2.
1 value-of-x-to-n comp-2.
1 sum-of-values comp-2 value 0.
1 disp-sum pic z(3).9(15).
1 n binary pic 9(3).
procedure division.
begin.
compute log10-of-x = function log10 (x)
perform varying n from 365 by -1
until n = 0
compute value-of-x-to-n = 10 ** (log10-of-x * n)
compute sum-of-values = sum-of-values +
value-of-x-to-n
end-perform
move sum-of-values to disp-sum
display disp-sum
stop run
.
For x = 0.99 the answer is approximately 96.473721519223000.
But note that as x drops below approximately 0.10, underflow may occur.
I have a requirement where any date (DD.MM.YYYY) should be converted to last date of month (ex: If date is 20.01.1999 then it should convert into 31.01.1999) ?
Exactly what are you having trouble with? COBOL or the algorithm? I'm guessing its COBOL.
I'm not going to give you a direct answer because you are
obviously leaning the language and there is value in working out the specific details
for yourself.
Here are a couple of hints:
Define a date field in WORKING-STORAGE so that you can pick out the day, month and year as separate items. Something like:
01 TEST-DATE.
05 TEST-DAY PIC 99.
05 PIC X.
05 TEST-MONTH PIC 99.
05 PIC X.
05 TEST-YEAR PIC 9999.
Note the unnamed PIC X fields. These contain the day/month/year delimiters. They do not need to be given data names because
you do not need to reference them. Sometimes this type of data item is given
the name FILLER, but the name is optional.
Read up on the EVALUATE statement. Here is a link to
the IBM Enterprise COBOL manual. This description of EVALUATE should be similar in all versions of COBOL.
MOVE the date of interest TO TEST-DATE. Now you can reference the year, month and day as individual items: TEST-DAY, TEST-MONTH and TEST-YEAR.
Use EVALUATE to test the month (TEST-MONTH). If the month is a 30 day month then MOVE 30 to TEST-DAY. Do the same for
31 day months. February is a special case because of leap years. Once you have determined that the month is February,
test TEST-YEAR to determine if it is a leap year
and MOVE 28 or 29 TO TEST-DAY depending on the outcome of the test.
Now TEST-DATE will contain the date you are looking for. MOVE it to wherever it is needed.
You can use function integer-of-date which gives returns an integral value corresponding to any date. Assuming your input date is in ddmmyyyy format and you expect hte output in the same format. Lets say date is 20011999 and you want as 31011999. You can follow the below steps.
Increase the month of the input date by one. (20*02*1999)
Make the day as 01 and use function integer-of-date (*01*021999)
subtract one from the integer returned.
use function date-of-integer which will give you the required result.
Note here you will have to add one more check for handling December month.
Here you go! Run the code here
IDENTIFICATION DIVISION.
PROGRAM-ID. STACK2.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DATE PIC X(10).
01 WORK-DATE.
05 WORK-DAY PIC 9(2).
05 PIC X.
05 WORK-MONTH PIC 9(2).
05 PIC X.
05 WORK-YEAR PIC 9(4).
01 MONTH-31 PIC 9(2).
88 IS-MONTH-31 VALUES 01, 03, 05, 07, 08, 10, 12.
88 IS-MONTH-30 VALUES 04, 06, 09, 11.
01 WS-C PIC 9(4) VALUE 0.
01 WS-D PIC 9(4) VALUE 0.
PROCEDURE DIVISION.
ACCEPT WS-DATE.
MOVE WS-DATE TO WORK-DATE.
DISPLAY 'ACTUALE TEST-DATE: ' WORK-DATE.
MOVE WORK-MONTH TO MONTH-31.
EVALUATE TRUE
WHEN IS-MONTH-31
MOVE 31 TO WORK-DAY
WHEN IS-MONTH-30
MOVE 30 TO WORK-DAY
WHEN OTHER
DIVIDE WORK-YEAR BY 4 GIVING WS-C REMAINDER WS-D
IF WS-D NOT EQUAL 0
MOVE 28 TO WORK-DAY
ELSE
MOVE 29 TO WORK-DAY
END-IF
END-EVALUATE.
DISPLAY 'MODIFIED TEST-DATE: ' WORK-DATE
STOP RUN.
This solution goes hand in hand with #NealB's answer.
The procedure "compute-month-end-date" does not require any checks for leap year or December.
identification division.
program-id. last-day.
data division.
working-storage section.
1 test-date.
88 test-1 value "20.01.1999".
88 test-2 value "20.02.2004".
88 test-3 value "20.12.2005".
2 dd pic 99.
2 pic x.
2 mm pic 99.
2 pic x.
2 yyyy pic 9999.
1 month-end-date binary pic 9(8) value 0.
procedure division.
begin.
set test-1 to true
perform run-test
set test-2 to true
perform run-test
set test-3 to true
perform run-test
stop run
.
run-test.
display test-date " to " with no advancing
perform test-date-to-iso
perform compute-month-end-date
perform iso-to-test-date
display test-date
.
compute-month-end-date.
*> get date in following month
compute month-end-date = function
integer-of-date (month-end-date) + 32
- function mod (month-end-date 100)
compute month-end-date = function
date-of-integer (month-end-date)
*> get last day of target month
compute month-end-date = function
integer-of-date (month-end-date)
- function mod (month-end-date 100)
compute month-end-date = function
date-of-integer (month-end-date)
.
test-date-to-iso.
compute month-end-date = yyyy * 10000
+ mm * 100 + dd
.
iso-to-test-date.
move month-end-date to dd
.
end program last-day.
Results:
20.01.1999 to 31.01.1999
20.02.2004 to 29.02.2004
20.12.2005 to 31.12.2005
While this might be a bit daunting to the novice COBOL programmer, there is a simple explanation. The procedure "compute-month-end-date" consists of two identical parts with the exception of the "+32".
Taking the second part first, it subtracts the day of month from the integer of a date giving the integer value for the 'zeroth' day of the month. This is precisely the integer value for the last day of the prior month. The following compute gives the date in 'yyyymmdd' format.
The first part does the same, except that it adds 32 to get a date in the following month, the 1st through the 4th, depending on the number of days in the original month.
Taken togther 19990120 is first changed to 19990201, then changed to 19990131. And 20040220 to 20040303, then 20040229. 20051220 to 20060101, then 20051231.