GnuCOBOL PIC 999V99 - unexpected result? - cobol

What am I doing wrong with the following piece of code under GnuCOBOL 3.1-rc1.0?
IDENTIFICATION DIVISION.
PROGRAM-ID. NUMTEST.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NUM PIC 999V99.
PROCEDURE DIVISION.
DISPLAY "ENTER NUMBER: ".
ACCEPT NUM.
DISPLAY "NUMBER = ".
DISPLAY NUM.
STOP RUN.
I enter 123.45 as my input. I'm expecting 123.45 as the output, but instead I get 123.40

These are plain ACCEPTs, they only read in data from the command line (you can also enter a big lorem ipsum there).
While I think it is a reasonable request to have this working "as expected" the best option you currently have is ACCEPTing only PIC X and then use MOVE FUNCTON NUMVAL (INPUT-DATA) TO NUM (maybe test the data with FUNCTION TEST-NUMVAL() before). For DISPLAY you likely want an edited field with a PICTURE like ZZ9.99.
In any case: be aware that V is an implied decimal point, it is not part of the actual storage.
Using "extended" screenio (= input not from the command line) gives some benefits (like only allowing numeric data and not more than the fields's size) but has different culprits (for example you should use COLUMN/LINE for it and numeric ACCEPT still has some issues in GC 3.1).
As suggested by JoelFan I've tested edited fields - these work currently only correctly when in "command line mode" (so not if any attributes like positioning is used):
PROGRAM-ID. NUMTEST.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NUM-INP PIC 999.99.
01 NUM PIC 999V99.
01 NUM-OUT PIC zz9.99.
PROCEDURE DIVISION.
DISPLAY "ENTER NUMBER: ".
ACCEPT NUM-INP.
DISPLAY "NUMBER = ".
MOVE NUM-INP TO NUM
MOVE NUM TO NUM-OUT
DISPLAY NUM "/" NUM-OUT.
STOP RUN.
Producing the expected result:
ENTER NUMBER:
123.45
NUMBER =
123.45/123.45
ENTER NUMBER:
1.2
NUMBER =
001.20/ 1.20
ENTER NUMBER:
a
NUMBER =
000.00/ 0.00
ENTER NUMBER:
1234567
NUMBER =
567.00/567.00
Note: the third case should actually raise an exception when compiled with -fec=all / -debug (currently doesn't), the last case is completely correct as numbers are right justified.
Still: ACCEPTing alphanumeric data, do explicit checks/conversions, and display as edited field like NUM-OUT above is the safest option.

Related

Why does my COBOL working storage variable have trailing zeroes?

I'm building a COBOL program to calculate the average of up to 15 integers. The execution displays a number that is far bigger than intended with a lot of trailing zeroes. Here is the relevant code:
Data Division.
Working-Storage Section.
01 WS-COUNTER PIC 9(10).
01 WS-INPUT-TOTAL PIC 9(10).
01 WS-NEXT-INPUT PIC X(8).
01 WS-CONVERTED-INPUT PIC 9(8).
01 WS-AVG PIC 9(8)V99.
Procedure Division.
PROG.
PERFORM INIT-PARA
PERFORM ADD-PARA UNTIL WS-COUNTER = 15 OR WS-NEXT-INPUT = 'q'
PERFORM AVG-PARA
PERFORM END-PARA.
INIT-PARA.
DISPLAY 'This program calculates the average of inputs.'.
MOVE ZERO TO WS-COUNTER
MOVE ZERO TO WS-INPUT-TOTAL
MOVE ZERO TO WS-AVG.
ADD-PARA.
DISPLAY 'Enter an integer or type q to quit: '
ACCEPT WS-NEXT-INPUT
IF WS-NEXT-INPUT NOT = 'q'
MOVE WS-NEXT-INPUT TO WS-CONVERTED-INPUT
ADD WS-CONVERTED-INPUT TO WS-INPUT-TOTAL
ADD 1 TO WS-COUNTER
END-IF.
AVG-PARA.
IF WS-COUNTER > 1
DIVIDE WS-INPUT-TOTAL BY WS-COUNTER GIVING WS-AVG
DISPLAY 'Your average is ' WS-AVG '.' WS-NEXT-INPUT
END-IF.
The reason I put WS-NEXT-INPUT as alphanumeric and move it to a numeric WS-CONVERTED-INPUT if the IF condition is satisfied is because I want it to be able to take "q" to break the UNTIL loop, but after the condition is satisfied, I want a numeric variable for the arithmetical statements. Here's what it looks like with the numbers 10 and 15 as inputs:
10is program calculates the average of inputs.
Enter an integer or type q to quit:
15
Enter an integer or type q to quit:
q
Your average is 1250000000.
The console is a bit buggy so it forces me to input the 10 in that top left corner most of the time. Don't worry about that.
You see my problem in that execution. The result is supposed to be 00000012.50 instead of 1250000000. I tried inserting a few of my other variables into that display statement and they're all basically as they should be except for WS-INPUT-TOTAL which with that combination of numbers ends up being 0025000000 instead of 0000000025 as I would have expected. Why are these digits being stored in such a weird and unexpected way?
You have that strange output because of undefined behavior - computing with spaces.
The MOVE you present has the exact same USAGE and same size - it will commonly be taken over "as is", it normally does not convert the trailing spaces by some magic, so WS-CONVERTED-INPUT ends up with 10 . As the standard says for the move:
De-editing takes place only when the sending operand is a numeric-edited data item and the receiving item is a numeric or a numeric-edited data item.
and if it would be an edited field then it still should raise an exception on the MOVE:
When a numeric-edited data item is the sending operand of a de-editing MOVE statement and the content of that data item is not a possible result for any editing operation in that data item, the result of the MOVE operation is undefined and an EC-DATA-INCOMPATIBLE exception condition is set to exist.
When computing with spaces you commonly would raise a fatal error, but it seems your compile does not have that activated (and because you didn't share your compile command or even your compiler, we can't help with that).
Different COBOL dialects often use (partial only when checks are not activated which would lead to an abort) zero for invalid data, at least for spaces (but they can use everything. This will then lead to WS-CONVERTED-INPUT "seen as" 10000000 - so your computation will then include those big numbers.
So your program should work if you enter the necessary amount of leading zeroes on input.
General:
"never trust input data - validate" (and error or convert as necessary)
at least if something looks suspicious - activate all runtime checks available, re-try.
Solution - Do an explicit conversion:
MOVE FUNCTION NUMVAL(WS-NEXT-INPUT) TO WS-CONVERTED-INPUT, this will strip surrounding spaces and then convert from left to right until invalid data is found. A good coder would also check up-front using FUNCTION TEST-NUMVAL, otherwise you compute with zero if someone enters "TWENTY".

Displaying COBOL binary as numeric

I'm entirely new to COBOL. I have a small COBOL program and a small C file. According to this article: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.1.0/com.ibm.zos.v2r1.ceea400/sdtpt.htm
the equivalent of a C signed integer in COBOL is
PIC S9(9) USAGE IS BINARY
I want to call the functions in the C file from COBOL, and display the result in COBOL. I am able to call the function, and it seems to behave as expected, data being passed as expected, but I'm not able to display the binary value with DISPLAY in COBOL.
My COBOL program:
IDENTIFICATION DIVISION.
PROGRAM-ID. MSQLTST5_COBHELPER.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SQLCODE PIC S9(9) USAGE IS BINARY VALUE 100.
PROCEDURE DIVISION.
HEAD SECTION.
MAIN.
DISPLAY "COBOL, sqlcode is: " SQLCODE.
CALL "CONNECT_DEFAULT" USING SQLCODE.
DISPLAY "COBOL, sqlcode is: " SQLCODE.
STOP RUN.
END PROGRAM MSQLTST5_COBHELPER.
The C function I'm calling:
void connect_default(int* sqlcode)
{
printf("C, sqlcode is: %d\n", *sqlcode);
// internal code that places the expected error code -14006 in the variable sqlcode
printf("C, sqlcode is: %d\n", *sqlcode);
}
The output of running my COBOL program:
COBOL, sqlcode is: d
C, sqlcode is: 100
C, sqlcode is: -14006
COBOL, sqlcode is: J▒▒▒
It seems that the variable does indeed have the value 100 that I gave it, and then is passed correctly between C and COBOL, but when I ask COBOL to display the variable it seems to try to pick out the character that has the given ASCII code, rather than the numerical value, as the character 'd', which has the ASCII code 100, is displayed rather than the number 100.
How do I display this value as a numeric value in COBOL?
Why don't you transfer the contents of the SQLCODE field to a USAGE DISPLAY field using the MOVE instruction? It'll convert the binary number to a numeric one.
If the formatting is strange — here is why:
Signed numeric pictures without the SIGN SEPARATE clause must somehow include the sign without taking up space, so most compilers combine the last digit with the sign of the number. Most conventions use J for −1 till R for −9. They also pad the number with leading zeros. So -14006 will convert to 00001400O, because the last digit is a 6, and because it must be conbined with the minus sign, the last digit becomes -6, which is represented by an O. Roughly the same reasoning counts for 00000010{.
In order to reformat the number, you could actually use another picture, like
pic S9(9) sign leading separate
That will display -14006 as -000014006
This should achieve your intended results.
IDENTIFICATION DIVISION.
PROGRAM-ID. MSQLTST5_COBHELPER.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SQLCODE PIC S9(9) USAGE IS BINARY VALUE 100.
01 SQLCODE-E PIC -9(9).
PROCEDURE DIVISION.
HEAD SECTION.
MAIN.
PERFORM DISPLAY-SQLCODE.
CALL "CONNECT_DEFAULT" USING SQLCODE.
PERFORM DISPLAY-SQLCODE.
STOP RUN.
DISPLAY-SQLCODE.
MOVE SQLCODE TO SQLCODE-E.
DISPLAY "COBOL, sqlcode is: " SQLCODE-E.
END PROGRAM MSQLTST5_COBHELPER.
The standard way to do this is to move the binary field to a "numeric edited" field:
01 DISPLAY-SQLCODE PIC -(9)9.
Then
MOVE SQLCODE TO DISPLAY-SQLCODE
Full example:
IDENTIFICATION DIVISION.
PROGRAM-ID. MSQLTST5_COBHELPER.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SQLCODE PIC S9(9) USAGE IS BINARY VALUE 100.
01 DISPLAY-SQLCODE PIC -(9)9.
PROCEDURE DIVISION.
HEAD SECTION.
MAIN.
MOVE SQLCODE TO DISPLAY-SQLCODE
DISPLAY "COBOL, sqlcode is: " DISPLAY-SQLCODE
CALL "CONNECT_DEFAULT" USING SQLCODE
MOVE SQLCODE TO DISPLAY-SQLCODE
DISPLAY "COBOL, sqlcode is: " DISPLAY-SQLCODE
STOP RUN
.
END PROGRAM MSQLTST5_COBHELPER.
Move the variable to the display field and then just display the field
01 WS-FIELD-FOR-DISPLAY PIC -(9)9.

Moving hexadecimal to a comp declared variable in cobol

Is it possible to assign a string of hexadecimal to a comp or binary declared variable?
Example:
01 COMP-VAR PIC 9(4) COMP.
MOVE X'04D2' TO COMP-VAR.
should output +1234.
Edited:
Sorry for the lack of the information, I just gave an example. The real scenario is that the data will come from an external source, a dataset. I need to store the data in an alphanumeric variable before I move it to a comp declared variable. My problem is that the data is incorrect when I move the alphanumeric data to the comp variable. Your help is very much appreciated.
I think you are looking for REDEFINES. Redefine the binary value as character, do the assignment
which will not violate any of the assignment rules and then use the binary representation in
subsequent operations. This program illustrates your example:
IDENTIFICATION DIVISION.
PROGRAM-ID. EXAMPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01.
02 COMP-VAR PIC 9(4) COMP.
02 COMP-X REDEFINES COMP-VAR PIC X(2).
PROCEDURE DIVISION.
MOVE X'04D2' TO COMP-X
DISPLAY COMP-VAR
GOBACK
.
This displays 1234.
The larger question is why would you need to do this? I suspect that you are attempting to
read a file with multiple record formats in it. Based on some common record identifier you
need to read part of the record as character or as binary. Typically this is done a little
differently in COBOL.
Here is a larger example of what I mean. Suppose you have an input
record that is 3 bytes long. When the first byte is a 'B' it is telling you that the next two bytes should be
treated as a binary (COMP) value. When the first byte is an 'X' you need to read the next two
bytes as text (X) data. As an example this is what two records might look like:
X'E7C1C2'
X'C204D2'
The first record is a text record containing the value 'AB' (EBCDIC). The second record is binary containing
the value 1234. The program to process these records might look something like:
IDENTIFICATION DIVISION.
PROGRAM-ID. EXAMPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 INPUT-RECORD.
02 REC-TCD PIC X.
88 REC-TCD-BIN VALUE 'B'.
88 REC-TCD-CHAR VALUE 'X'.
02 REC-DUMMY PIC X(2).
02 REC-COMP-VAR REDEFINES REC-DUMMY PIC 9(4) BINARY.
02 REC-CHAR-VAR REDEFINES REC-DUMMY PIC X(2).
PROCEDURE DIVISION.
*
* THIS IS A CHARACTER RECORD
*
MOVE X'E7C1C2' TO INPUT-RECORD
PERFORM DISPLAY-INPUT-RECORD
*
* THIS IS A BINARY RECORD
*
MOVE X'C204D2' TO INPUT-RECORD
PERFORM DISPLAY-INPUT-RECORD
GOBACK
.
DISPLAY-INPUT-RECORD.
EVALUATE TRUE
WHEN REC-TCD-BIN
DISPLAY 'REC TYPE: ' REC-TCD
' BINARY DATA: ' REC-COMP-VAR
WHEN REC-TCD-CHAR
DISPLAY 'REC TYPE: ' REC-TCD
' CHAR DATA : ' REC-CHAR-VAR
WHEN OTHER
DISPLAY 'UNKNOWN RECORD TYPE: ' REC-TCD
END-EVALUATE
.
The output from this program is:
******************************** Top of Data ***********************************
REC-TYPE: X CHAR DATA : AB
REC-TYPE: B BINARY DATA: 1234
******************************* Bottom of Data *********************************
Look at the INPUT-RECORD definition. The first byte determines how the rest of the
record is to be intrepreted. REC-DUMMY is generally defined as a "generic" buffer area
to be subsequently redefined. In the case of variable length input records, REC-DUMMY
is defined to be as long as the longest record variant so the subsequent REDEFINEs of it
do not "upset" the compiler. All data items following REC-DUMMY begin with the same level
number (02 in the example) and REDEFINE it to the the appropriate format. Subsequent
processing uses whatever record redefinition is appropaiate based on the value in REC-TCD.

Is there a way to use INSPECT TALLYING with a check for multiple characters?

I have a string for which I wish to tally the count of characters till a certain pattern of characters is found.
For example:
Give a string: askabanskarkartikrockstar
I would like to know how many characters are there before the kartik in the string.
In a normal scenario where I need to find the number of characters before, say k, in the given string, I would write the code somewhat as:
INSPECT WS-INPUT-STRING TALLYING CT-COUNTER FOR CHARACTERS BEFORE LT-K
Where
WS-INPUT-STRING is alphanumeric with a value of
askabanskarkartikrockstar,
CT-COUNTER is the counter used to count the number of characters
LT-K is a literal with the value k.
But here, if I wish to do the same for a sub-string, like kartik in the above example, would replacing the value of LT-K with kartik instead of just k work? If yes, is the same applicable for alphanumeric literals that have values in the form of hexadecimal numbers (for example, in a literal X(02) one stores a new-line character as x'0D25')?
I'm trying to implement the above code in zOS IBM mainframe v10. Thanks.
You have pretty much answered your own question... The answer is yes you can do this. Here is a working example program:
IDENTIFICATION DIVISION.
PROGRAM-ID. EXAMPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-INPUT-STRING PIC X(80).
01 WS-COUNTER PIC 9(4).
01 WS-TAG PIC X(10).
PROCEDURE DIVISION.
MAIN-PARAGRAPH.
MOVE 'askabanskarkartikrockstar' TO WS-INPUT-STRING
MOVE ZERO TO WS-COUNTER
MOVE 'kartik' TO WS-TAG
INSPECT WS-INPUT-STRING
TALLYING WS-COUNTER
FOR CHARACTERS BEFORE WS-TAG(1:6)
DISPLAY WS-COUNTER
GOBACK
.
WS-COUNTER displays as 11, there are 11 characters before the WS-TAG string.
Notice that I defined WS-TAG as PIC X(10). This variable is longer than the actual tag value you are looking for. To prevent the INSPECT verb from trying to match on trailing spaces introduced by:
MOVE 'kartik' TO WS-TAG
I had to specify a reference modified value for INSPECT to search for. Had I simply used:
FOR CHARACTERS BEFORE WS-TAG
without reference modification, WS-COUNTER would have been 80 - the length of WS-INPUT-STRING. This is because the string 'kartik ' is not found and the counter tallies the length of the entire input string.
Another approach would be to specify the tag as a literal:
FOR CHARACTERS BEFORE 'kartik'
You can move hexadecimal constants into PIC X fields as follows:
MOVE X'0D25' TO WS-TAG
This occupies 2 characters so you would use WS-TAG(1:2) when INSPECTing it.
If you want to do "a lot" of this at once, then you'll find a PERFORM VARYING will be faster. It is more typing, and you have to think more, and there is more chance for error. But once you have one working, you just have to copy the code to reuse it.

SUBSTRING for a String Literal in COBOL

Is there anyway to get a SUBSTRING of string literal in COBOL without using a temporary variable?
Let's say in the following code:
MOVE "HELLO" TO MY-VAR.
MOVE MY-VAR(1:3) TO SUB-STR.
Is there any way to do the same thing, but without MY-VAR?
EDIT:
I did tried following code, but it's failed.
MOVE "HELLO"(1:3) TO SUB-STR * COMPILE ERROR
You can accomplish what you are trying to do by type-laundering the literal through a function. You can then substring, or reference modify, the output of the function. Consider that calling reverse twice on the same data returns the original data.
Move function reverse
( function reverse(
'abcdefg'
)
) (3:1) to text-out
The above will result in a 'c' being moved to text-out.
Of course, the example code in your question does not make any sense, as why would you write "HELLO"(1:3) when you could just write "HEL".
So you must be wanting to use a variable (or 2) in the reference modifier field(s).
If you are wanting to get the first 'N' characters of the literal, you can do this by using the reference modifier on the destination item. For example, if you compile and run the following program:
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LEN PIC 99 VALUE 8.
01 SUB-STR PIC X(80).
PROCEDURE DIVISION.
MOVE "HELLO WORLD" TO SUB-STR(1:LEN).
DISPLAY SUB-STR.
STOP RUN.
You get the resulting output:
HELLO WO
Unfortunately this method only works if you want the first 'N' characters of the literal string.
Also, the destination string must be empty before you start. In the above program, if you changed the definition of SUB-STR to be:
01 SUB-STR PIC X(80) VALUE "BLAH BLAH BLAH".
Then the result of running the program becomes:
HELLO WOH BLAH
Put the "literal" into a field, like a constant.
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LITERAL-HELLO PIC X(5) VALUE 'HELLO'.
PROCEDURE DIVISION.
DISPLAY LITERAL-HELLO(1:3).
STOP RUN.

Resources