This question already has answers here:
compute length string of variable with cobol
(4 answers)
Closed 6 years ago.
Suppose I have a variable PIC X(20).
The variable contains some value e.g. ABC WXYZ.
How to find out the length of the string excluding the space in between?
If you're really sure that you don't want the space in between you can define test-val and use this in PERFORM:
77 some-len PIC 9(02). *> make sure to use appropriate length, if "big" use COMP-5
77 string-ptr PIC 9(02). *> make sure to use appropriate length
77 some-var PIC X(20) VALUE ' ABC WXYZ'.
77 var-len PIC 9(02) VALUE LENGTH OF some-var.
01 test-char PIC X.
88 no-spaces values 'A' THROUGH 'Z'
'a' THROUGH 'z'
'0' THROUGH '9'
'-', '.'.
PROCEDURE DIVISION.
MOVE 0 TO some-len
PERFORM VARYING string-ptr FROM 1 BY 1
UNTIL string-ptr > var-len
MOVE some-var (string-ptr) TO test-char
IF no-spaces ADD 1 TO some-len END-IF
END-PERFORM
As Bill pointed out if you ONLY want to know the "not spaces" characters there's a faster option that needs less vars (make sure you comment this letting you and others know what this does:
*> set numbers of all non-spaces in some-len
MOVE 0 TO some-len
INSPECT some-var TALLYING some-len FOR ALL SPACES
*> add more counts here if you want to remove more chars like x'0D'/x'0A'/x'00', ...
*> INSPECT some-var TALLYING some-len FOR ALL x'00'
*> INSPECT some-var TALLYING some-len FOR ALL x'0D'
*> INSPECT some-var TALLYING some-len FOR ALL x'0A'
*> and finally subtract it from the fields length
SUBTRACT some-len FROM LENGTH OF some-var GIVING some-len
*>
Related
I was practicing array processing and table handling and there's this output of my program that I don't understand.
01 TABLE-VALUES.
05 TABLE-ELEMENTS OCCURS 2 TIMES.
10 A-A PIC X(5).
10 A-B PIC S9(5)V99 COMP-3.
01 WS-STRING PIC X(10).
01 S PIC 99.
01 WS-COUNT PIC 99.
...
PROCEDURE DIVISION.
0000-MAIN.
DISPLAY 'HELLO WORLD'
MOVE '1234567890ABCDEFGHI' TO TABLE-VALUES
DISPLAY 'TABLE VALUES ' TABLE-VALUES
MOVE 0 TO WS-COUNT
INSPECT TABLE-VALUES TALLYING WS-COUNT FOR CHARACTERS
DISPLAY WS-COUNT
PERFORM 1000-PARA VARYING S FROM 1 BY 1 UNTIL S > 2
STOP RUN.
1000-PARA.
MOVE 'A-A(&&) = ' TO WS-STRING
INSPECT WS-STRING REPLACING FIRST '&&' BY S
DISPLAY WS-STRING A-A(S)
MOVE 'A-B(&&) = ' TO WS-STRING
INSPECT WS-STRING REPLACING FIRST '&&' BY S
DISPLAY WS-STRING A-B(S).
The output turned out to be:
HELLO WORLD
TABLE VALUES 1234567890ABCDEFGHI
18
A-A(01) = 12345
A-B(01) = 6 7 8
A-A(02) = 0ABCD
A-B(02) = 5 6 7
I don't understand how A-B(1) and A-B(2) turned out like that. Why are there spaces in between? Where did digit 9 go to?
Try removing the COMP-3 from the A-B definition, it should work better.
Cobol comp-3
Comp-3 is cobol's binary coded decimal format. Each 4 bytes (1/2 byte or nyble) represents a decimal digit with the sign held in the last digit. Moving a character string to a comp-3 value (like you do) will result in a invalid comp-3 value.
Normally the value 1234 would be stored as
`01234C`x
In your case (if using an EBCDIC machine) you are moving 6789 hex string 'f6f7f8f9'x
to the variable A-B(01). The F's are not valid decimal digits and 9 is not a valid comp-3 sign.
Move to Table Explanation
Explanation of
MOVE '1234567890ABCDEFGHI' TO TABLE-VALUES
in the above TABLE-VALUES is treated as a pic x(18). The move completely ignores the definitions of A-A and A-B.
Assuming a definition of
01 TABLE-VALUES.
05 TABLE-ELEMENTS OCCURS 2 TIMES.
10 A-A PIC X(5).
10 A-B PIC S9(5)V99.
The following would make more sense
MOVE 'A-A 100234567A-A 200234567' TO TABLE-VALUES
When doing a move to a group level, The string has to exactly match the
map of the fields in the Group.
For the above Cobol Layout, the fields are aligned like
Field Position Length
A-A(1) 1 5
A-B(1) 6 7
A-A(2) 13 5
A-B(2) 18 7
I don’t know which compiler and directives you are using, but I would have expected the original DISPLAY of the comp-3 usage to ABEND with a numeric exception.
As for the follow up question about the letters in the numeric field, you valued the field by moving a literal to a group item, TABLE-VALUES. Group items always are USAGE ALPHANUMERIC.
How would I add characters to the beginning of a string based on a variable? For example a vendor we use for telephone numbers converts out string to a numeric which drops the leading 0's off of the phone number. When they send us the report back we convert the number back into a string but now it doesn't have the correct amount of numbers. I'm trying the following:
IF LENGTH(TO-NUM) < 10
SUBTRACT LENGTH(TO-NUM) FROM 10 GIVING ADD-NUM-ZERO
Now I need to figure out how to add ADD-NUM-ZERO number of 0's to the beginning of the string TO-NUM without overwriting the characters already at the beginning.
Assuming that TO-NUM contains only digits followed by spaces (or spaces followed by digits), then
1 TO-NUM PIC X(10).
1 temp-x.
5 temp-9 pic 9(10).
if to-num not numeric
compute temp-9 = function numval (to-num)
move temp-x to to-num
end-if
will, if necessary, replace the previous content with the same value but with leading zeros.
The IF TO-NUM NOT NUMERIC statement is equivalent to asking if the number of digits in TO-NUM is less than 10.
For example, if to-num was '5551212 ' before, then to-num will be '0005551212' after.
If to-num contains non-digits, then it would be necessary to extract the digits by parsing to-num into temp-9.
Working-storage section.
01 NUM PIC 9(10) COMP-5.
01 TELNUM-G.
03 TELNUM PIC 9(10).
03 TELNUM-S REDEFINES TELNUM.
05 AREACODE PIC 999.
05 THREEDIGIT PIC 999.
05 FOURDIGIT PIC 9999.
01 TELOUTPUT PIC X(13) VALUE '(AAA)TTT-NNNN'.
Procedure division.
Move 31234 to NUM.
MOVE NUM TO TELNUM.
INSPECT TELOUTPUT
REPLACING ALL 'AAA' BY AREACODE
ALL 'TTT' BY THREEDIGIT
ALL 'NNNN' BY FOURDIGIT
This code assume the number is in "NUM", and it can be in any of ZONE/PACK/BINARY/COMP-3/COMP-5 formats. I've used COMP-5 in this example code.
Record has name in this format:
01 LAST-NAME PIC X(18).
01 FIRST-NAME PIC X(12).
01 MI-NAME PIC X(01).
The last and first names can of course be any size when they come in.
I need to put the name fields together (with no trailing spaces - but with a SPACE between each name) into a single working storage field 42 spaces in length:
01 WS-FULL-NAME PIC X(42)
Example:
LAST-NAME = Smithington
FIRST-NAME = Edward
MI-NAME = H
Desired result:
Edward H Smithington
If Middle initial is blank I of course don't include it and desired result would be:
Edward Smithington
I have a routine to determine the actual length of each name field already that takes care of the trailing spaces (*I don't need to worry about leading spaces) (SEE BELOW):
01 W-SUB PIC 9(02) VALUE 0.
PROCESS-LAST-NAME.
PERFORM VARYING W-SUB FROM LENGTH OF LAST-NAME BY -1
UNTIL W-SUB LESS THAN 1
OR LAST-NAME(W-SUB:1) NOT = ' '
END-PERFORM
IF W-SUB > ZERO
MOVE LAST-NAME(1:W-SUB) TO ?????
'MOVE LAST-NAME(1:W-SUB) TO ?????' is where I am 'stuck'.
i.e. How to 'STRING' the three name fields together to produce DESIRED RESULT = Edward H Smithington (* Including a space between F, MI + L).
You were asked to do it with STRING. The argument against using STRING for names is that names can have embedded spaces. DE LA HAYE for instance as a last-name, 'ST JOHN' (pronounced sinjun) as a first-name. There can also be typos giving leading blanks and embedded blanks. You seem to know that you have no leading blanks, but genuine or false embedded blanks make the simple use of STRING more tricky.
If you can guarantee no leading or embedded blanks (absolutely guarantee) then
IF MI-NAME EQUAL TO SPACE
STRING FIRST-NAME
DELIMITED BY SPACE
' '
DELIMITED BY SIZE
LAST-NAME
DELIMITED BY SPACE
INTO FULL-NAME
ELSE
STRING FIRST-NAME
DELIMITED BY SPACE
' '
DELIMITED BY SIZE
MI-NAME
DELIMITED BY SIZE
' '
DELIMITED BY SIZE
LAST-NAME
DELIMITED BY SPACE
INTO FULL-NAME
END-IF
Before the IF you need to MOVE SPACE to FULL-NAME, else you'll end up with part of the previous name when the current name is shorter than the previous.
If you can't guarantee the lack of leading and embedded blanks, you have to use reference-modified fields for the first-name and last-name (one length each) and use DELIMITED BY SIZE for those. Then what's the point?
01 W-FULL-NAME.
05 W-FIRST-NAME.
10 FILLER OCCURS 0 TO 12 TIMES
DEPENDING ON length-of-first-name.
15 FILLER PIC X.
05 SPACE-AFTER-FIRST-NAME PIC X.
05 W-MI-NAME.
10 FILLER OCCURS 0 TO 2 TIMES
DEPENDING ON length-for-mi.
15 FILLER PIC X.
05 W-LAST-NAME.
10 FILLER OCCURS 0 TO 18 TIMES
DEPENDING ON length-of-last-name.
15 FILLER PIC X.
You have your two PERFORMs to get the length of the first-name and last-name (as already). You MOVE ZERO to length-for-mi if WS-MI-NAME is space, else make it two.
Then:
MOVE SPACE TO SPACE-AFTER-FIRST-NAME
MOVE FIRST-NAME TO W-FIRST-NAME
MOVE LAST-NAME TO W-LAST-NAME
MOVE MI-NAME TO W-MI-NAME
The W-FULL-NAME, a variable-length field, contains the data you want in the format you want, and then you do what you like with it.
By shifting the "complexity" into the DATA DIVISION with the definition of the data, the code in the PROCEDURE DIVISION becomes very simple.
Try it out, and to see the formatted data, do this:
DISPLAY
">"
W-FULL-NAME
"<"
The > and < are just to show you exactly what the data is (I always do that when DISPLAYing data). You'll see the length of the field changing with different test-data (assuming to test with different length data...).
Test with all the input as space, you'd see this on your SYSOUT spool dataset:
><
A zero-length field.
With reference-modification you'd do it something like this:
do your PERFORM to count the first-name trailing spaces
MOVE FIRST-NAME TO WS-FULL-NAME
ADD 1 WS-SUB
GIVING next-available-output
IF MI-NAME NOT EQUAL TO SPACE
MOVE MI-NAME TO WS-FULL-NAME
( next-available-output : 1 )
ADD 2 TO next-available-output
END-IF
do your PERFORM for the last-name
MOVE LAST-NAME TO WS-FULL-NAME
( next-available-output : W-SUB )
The first MOVE to the output will copy the first name to the full-name. Neither needs to be reference-modified, the effect will be that other than the characters of the name the rest of the target field will be blank.
The number of characters present are known, so the next available position for data in the target field can be calculated by adding one.
The second MOVE is made conditional on whether there is any middle-initial data, and if so it is MOVEd to the next available output position calculated above. The number of bytes (one) MOVEd are limited in the target.
The third MOVE is like the second MOVE, except unconditional and the limit for the bytes MOVEd is variable.
Variations of the above are possible. There's a much neater way, but depends somewhat on your compiler. So which one are you using?
You calculation of the number of trailing blanks can be simplified:
01 W-SUB COMP PIC 9(4).
PROCESS-LAST-NAME.
MOVE ZERO TO W-SUB
IF LAST-NAME NOT EQUAL TO SPACE
MOVE FUNCTION LENGTH ( LAST-NAME )
TO W-SUB
PERFORM
UNTIL LAST-NAME ( W-SUB : 1 )
NOT EQUAL TO SPACE
SUBTRACT 1 FROM W-SUB
END-PERFORM
END-IF
That way, you don't have to have the double termination-conditions. I'd REDEFINES the field, and use subscripting not reference-modification. I'd also rename W-SUB to indicate that it contains the length of the data.
For eg., i have alphanumeric string 'ABCDEF 0 0.450' and i need to get '0.450' as numeric decimal and do arithmetic on it. Do we have a way? Please suggest.
I assume the format is not fixed, since then you could just pick that part of the string and move it to a field defined with pic 9.999 and use it from there.
This assumes the string value is made up of 3 parts separated by spaces. First define some fields:
1 part1 pic x(14).
1 part2 pic x(14).
1 part3 pic x(07).
1 digitsAn.
2 digits pic 9(03).
1 decimalsAn.
2 decimals pic .9(03).
1 theValue pic 9(03)v9(3).
The An suffix is from my naming convention, and indicates a alphanumeric value. If the string input may be longer increase the sizes as needed.
The rest of the code parses theString into theValue.
* Extract the third part.
initialize part3
unstring theString delimited by all spaces into part1 part2 part3
* make sure the receiving field contains numeric data.
inspect part3 replacing spaces by zeroes
* extract digits before and after the decimal point.
unstring part3 delimited by "." into digits decimalsAn(2:)
* Combine parts before and after decimal points into a numeric value.
compute theValue = digits + decimals
Beware, I haven't run this through a compiler!
Asusually, I could find out a way to achieve it!
As said above, UNSTRINGing and combining didnt work, but REDEFINES works!
Get alphanumeric string redefined into two numeric fields to hold and process decimal part and integer part individually. Atlast divide the decimal-total by 1000 and add it to integer-part. Thats it! Code snippets follow...
01 WS-NUM-STR PIC X(14) VALUE 'ABCDEF 0 0.450'.
01 WS-NUM-STR-MOD REDEFINES WS-NUM-STR.
05 FILLER PIC X(9).
05 WS-INT-PART PIC 9.
05 FILLER PIC X.
05 WS-DEC-PART PIC 999.
----------
----------
* CODE TO ADD ALL INTEGER PARTS
* CODE TO ADD ALL DECIMAL PARTS
COMPUTE = INTEGER-TOTAL + (DECIMAL-TOTAL / 1000)
Note: As I already knew that decimal part has 3 digits, I divided DECIMAL-TOTAL by 1000. Hope you could figure out.
And Please suggest any other way to achieve the same!
In most modern Cobol compilers, you can move a numeric-edited field to a numeric field.
Borrowing from your example below:
01 WS-NUM-STR PIC X(14) VALUE 'ABCDEF 0 0.450'.
01 WS-NUM-STR-MOD REDEFINES WS-NUM-STR.
05 FILLER PIC X(9).
05 WS-EDITED-NUMBER PIC 9.999.
01 WS-NUMBER PIC 9V999.
----------
MOVE WS-EDITED-NUMBER TO WS-NUMBER
And then, do the calculation with WS-NUMBER.
For eg., i have alphanumeric string
'ABCDEF 0 0.450' and i need to get
'0.450' as numeric decimal and do
arithmetic on it. Do we have a way?
Please suggest.
If you are running in CICS, you could use the BIF DEEDIT function -- that will leave you with "00.450".
If you are not running in CICS, assume you have your string in a field called WS-STR:
Move 0 to JJ
Perform varying II from 1 by 1
until II > Length of WS-STR
Evaluate WS-STR (II:1)
when '0'
when '1'
when '2'
when '3'
when '4'
when '5'
when '6'
when '7'
when '8'
when '9'
when '.'
Add 1 to JJ
Move WS-STR (II:1) to WS-NUM (JJ:1)
* ---- Do nothing
when other
continue
End-Perform
Compute REAL-DEC-NUM = Function NUM-VAL (WS-NUM)
I'm just learning COBOL; I'm writing a program that simply echos back user input. I have defined a variable as:
User-Input PIC X(30).
Later when I ACCEPT User-Input, then DISPLAY User-Input " plus some extra text", it has a bunch of spaces to fill the 30 characters. Is there a standard way (like Ruby's str.strip!) to remove the extra spaces?
One would hope for a more elegant way of simply trimming text strings
but this is pretty much the standard solution... The trimming part
is done in the SHOW-TEXT paragraph.
*************************************
* TRIM A STRING... THE HARD WAY...
*************************************
IDENTIFICATION DIVISION.
PROGRAM-ID. TESTX.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 USER-INPUT PIC X(30).
01 I PIC S9(4) BINARY.
PROCEDURE DIVISION.
MOVE SPACES TO USER-INPUT
PERFORM SHOW-TEXT
MOVE ' A B C' TO USER-INPUT
PERFORM SHOW-TEXT
MOVE 'USE ALL 30 CHARACTERS -------X' TO USER-INPUT
PERFORM SHOW-TEXT
GOBACK
.
SHOW-TEXT.
PERFORM VARYING I FROM LENGTH OF USER-INPUT BY -1
UNTIL I LESS THAN 1 OR USER-INPUT(I:1) NOT = ' '
END-PERFORM
IF I > ZERO
DISPLAY USER-INPUT(1:I) '# OTHER STUFF'
ELSE
DISPLAY '# OTHER STUFF'
END-IF
.
Produces the following output:
# OTHER STUFF
A B C# OTHER STUFF
USE ALL 30 CHARACTERS -------X# OTHER STUFF
Note that the PERFORM VARYING statement relies on the left to
right evaluation of the UNTIL clause to avoid out-of-bounds
subscripting on USER-INPUT in the case where it contains only
blank spaces.
Use OpenCOBOL 1.1 or greater.
Identification division.
Program-id. 'trimtest'.
*> Compile:
*> cobc -x -free -ffunctions-all TrimTest.cbl
*>
Data division.
Working-Storage Section.
1 myBigStr Pic X(32768) Value Spaces.
Procedure Division.
Display "Enter Something? " With no advancing.
Accept myBigStr.
Display "[" Trim(myBigStr) "]".
Goback.
The trim function also has the options; Leading or Trailing.
cobc -h formore info.
Here's a solution if you work on OpenVMS:
01 WS-STRING-LENGTH PIC S9(04) COMP.
CALL "STR$TRIM" USING BY DESCRIPTOR user_output,
user_input,
BY REFERENCE WS-STRING-LENGTH.
a more general solution:
01 length pic 99.
perform varying length from 1 by 1
until length > 30 or user-input[length] = space
end-perform.
if length > 30
display user-input 'plus some extra text'
else
display user-input[1:length] 'plus some extra text'
end-if.
untested, I don't have a compiler at hand at the moment
There are three ways you can do this.
Use the COBOL functions to determine the string's "length". This is a mix of a couple functions. This is my preferred method, but requires declaring extra variables.
Write your own function to get the "length".
Use knowledge of a "terminating" string. You have to know what key characters indicates an end-of-string, like three spaces or a low-value character.
This example code demonstrates all three.
IDENTIFICATION DIVISION.
PROGRAM-ID. TESTPROG.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 ONE-A PIC X(20) VALUE 'RALPH WIGGAM'.
01 ONE-A-TLY PIC 9(02) VALUE ZERO.
01 ONE-A-LEN PIC 9(02) VALUE ZERO.
01 ONE-B PIC X(20) VALUE 'LIKES LEARNDING'.
01 ONE-B-TLY PIC 9(02) VALUE ZERO.
01 ONE-B-LEN PIC 9(02) VALUE ZERO.
01 TWO-A PIC X(20) VALUE 'RALPH WIGGAM'.
01 TWO-A-LEN PIC 9(02) VALUE ZERO.
01 TWO-B PIC X(20) VALUE 'LIKES LEARNDING'.
01 TWO-B-LEN PIC 9(02) VALUE ZERO.
01 THREE-A PIC X(20) VALUE 'RALPH WIGGAM'.
01 THREE-B PIC X(20) VALUE 'LIKES LEARNDING'.
01 THREE-C PIC X(80) VALUE SPACES.
PROCEDURE DIVISION.
DISPLAY ' -- METHOD ONE -- '
INSPECT FUNCTION REVERSE(ONE-A)
TALLYING ONE-A-TLY FOR LEADING SPACES.
SUBTRACT ONE-A-TLY FROM LENGTH OF ONE-A GIVING ONE-A-LEN.
INSPECT FUNCTION REVERSE(ONE-B)
TALLYING ONE-B-TLY FOR LEADING SPACES.
SUBTRACT ONE-B-TLY FROM LENGTH OF ONE-A GIVING ONE-B-LEN.
DISPLAY ONE-A(1:ONE-A-LEN)
' ' ONE-B(1:ONE-B-LEN)
'.'.
DISPLAY ' -- METHOD TWO -- '
PERFORM VARYING TWO-A-LEN FROM LENGTH OF TWO-A BY -1
UNTIL TWO-A-LEN < 1 OR TWO-A(TWO-A-LEN:1) > SPACE
END-PERFORM.
PERFORM VARYING TWO-B-LEN FROM LENGTH OF TWO-B BY -1
UNTIL TWO-B-LEN < 1 OR TWO-B(TWO-B-LEN:1) > SPACE
END-PERFORM.
DISPLAY TWO-A(1:TWO-A-LEN)
' ' TWO-B(1:TWO-B-LEN)
'.'.
DISPLAY ' -- METHOD THREE, NAIVE -- '
* DELIMITING BY JUST ANY SPACES ISN'T GOOD ENOUGH.
STRING THREE-A DELIMITED BY SPACES
' ' DELIMITED BY SIZE
THREE-B DELIMITED BY SPACES
'.' DELIMITED BY SIZE
INTO THREE-C.
DISPLAY THREE-C.
DISPLAY ' -- METHOD THREE, OK -- '
STRING THREE-A DELIMITED BY ' '
' ' DELIMITED BY SIZE
THREE-B DELIMITED BY ' '
'.' DELIMITED BY SIZE
INTO THREE-C.
DISPLAY THREE-C.
EXIT-PROG.
STOP RUN.
and the output looks like this:
-- METHOD ONE --
RALPH WIGGAM LIKES LEARNDING.
-- METHOD TWO --
RALPH WIGGAM LIKES LEARNDING.
-- METHOD THREE, NAIVE --
RALPH LIKES.
-- METHOD THREE, OK --
RALPH WIGGAM LIKES LEARNDING.