MOVE and Data Movement

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an edition 4 is upcoming, in DIS ballot), GnuCOBOL 3.2 (28 July 2023, the latest release) and IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026), and was checked on 2026-10-20. Every portable example below was compiled with cobc -x -Wall (GnuCOBOL 3.2.0, fixed reference format) and run, without a warning except in the ones labelled deliberately wrong; the output shown is real, and where the text says that an example was also run with -std=ibm or another option, the differences are given. The ISO standard is paywalled and was not read: the rules come from the vendor manuals. The IBM statements, including everything about S0C7, NUMCHECK and INVDATA, come from the Enterprise COBOL documentation and were not run here (z/OS; not run here).

MOVE is the most used statement in COBOL and the one with the most rules hidden behind its two-word syntax. It copies data from a sender to one or more receivers, and what "copies" means depends on the categories of both sides: digits are lined up on the decimal point, text is lined up on the left, numeric-edited senders are turned back into numbers, group items are copied byte for byte, and nothing at all is reported when the receiver is too small. This page covers the statement itself, the table of which category may be moved to which, the alignment and truncation rules, sign handling, group moves, MOVE CORRESPONDING, figurative constants and MOVE ALL, de-editing, reference modification, and the bugs that come from numeric receivers that do not hold valid numbers, including the S0C7 abend and the compiler options that detect it. The data categories themselves are defined in PICTURE Clause, the storage formats in USAGE and Numeric Representation, and the arithmetic that moves a computed value into a receiver in Arithmetic and COMPUTE.

The MOVE statement

MOVE has two formats. Format 1 moves one sender to one or more receivers; format 2, MOVE CORRESPONDING (abbreviated MOVE CORR), moves the elementary items that two group items have in common.

Format Written as Effect

1

MOVE identifier-1 TO identifier-2 ... (or a literal instead of identifier-1)

The sending area is moved to each receiver in the order the receivers are written. Every receiver is an identifier; a receiver cannot be an intrinsic function.

2

MOVE CORRESPONDING group-1 TO group-2

Both operands must be group items. The result is the same as one separate MOVE for each pair of corresponding items.

The rules that apply to every MOVE:

  • After the statement the sender holds the same data as before, unless the same item is also a receiver.

  • With several receivers the sender is evaluated once and the receivers are filled from left to right. A reference-modified or subscripted sender, or a function that returns text, is evaluated once, immediately before the first receiver is filled; the length, subscripts and reference modification of each receiver are evaluated just before that receiver is filled (see Reference modification as sender and receiver).

  • Items of USAGE INDEX, POINTER, FUNCTION-POINTER, PROCEDURE-POINTER and OBJECT REFERENCE cannot be named in a MOVE. They can sit inside a group that is moved as a group, and SET is the statement that assigns them (see Pointers and Dynamic Storage).

  • Data may be moved to or from a file record area only while the file is open: the record area becomes available when OPEN succeeds and disappears at CLOSE.

  • Overlapping operands give unpredictable results (see below for GnuCOBOL’s warning).

  • When the receiver is a strongly typed group (a TYPEDEF STRONG item, Enterprise COBOL 6.5), the sender must be a group of the same type; see REDEFINES, RENAMES and TYPEDEF.

A MOVE is either an elementary move (the receiver is an elementary item and the sender is an elementary item, a literal or a figurative constant) or a group move (an alphanumeric group is involved on either side). The two follow completely different rules, and most of the surprises in this page come from using one while thinking of the other.

Categories and the legality table

Every operand of an elementary move belongs to one of the data categories that PICTURE Clause defines: alphabetic, alphanumeric, alphanumeric-edited, numeric, numeric-edited, external floating point (USAGE DISPLAY or NATIONAL floating point), internal floating point (COMP-1, COMP-2), DBCS, national, national-edited and UTF-8. Literals and figurative constants count by what they look like: an alphanumeric literal is alphanumeric, ZERO and an integer literal are numeric integers, a literal with a decimal point is a numeric non-integer, and SPACE counts as alphabetic.

The table below is a rewritten summary of Enterprise COBOL’s table of valid and invalid elementary moves (rows are the sender, columns the receiver; "Yes" means the move is valid).

Sender \ receiver Alpha-betic Alpha-numeric Alnum-edited Numeric Numeric-edited Ext. float Int. float DBCS National Nat.-edited UTF-8

Alphabetic, SPACE

Yes

Yes

Yes

No

No

No

No

No

Yes

Yes

Yes

Alphanumeric (a)

Yes

Yes

Yes

Yes (b)

Yes (b)

Yes (c)

Yes (c)

No

Yes

Yes

Yes

Alphanumeric-edited

Yes

Yes

Yes

No

No

No

No

No

Yes

Yes

Yes

Numeric integer, ZERO (d)

No

Yes

Yes

Yes

Yes

Yes

Yes

No

Yes

Yes

No

Numeric non-integer (e)

No

No

No

Yes

Yes

Yes

Yes

No

No

No

No

Numeric-edited

No

Yes

Yes

Yes

Yes

Yes

Yes

No

Yes

Yes

Yes

Floating point (f)

No

No

No

Yes

Yes

Yes

Yes

No

No

No

No

DBCS (g)

No

No

No

No

No

No

No

Yes

Yes

Yes

No

National (h)

No

No

No

Yes

Yes

Yes

Yes

No

Yes

Yes

Yes

National-edited

No

No

No

No

No

No

No

No

Yes

Yes

Yes

UTF-8 (i)

No

No

No

Yes

Yes

Yes

Yes

No

Yes

Yes

Yes

Notes to the table:

  • (a) includes alphanumeric literals and alphanumeric functions.

  • (b) and (c): a figurative constant or alphanumeric literal moved to a numeric, numeric-edited or floating-point receiver must consist only of digits and is treated as a numeric integer; the ALL literal cannot be the sender of a move to a floating-point receiver. A data item of category alphanumeric may be the sender, but its content is then taken on trust (see Moving numbers that are not valid).

  • (d) includes integer literals; (e) includes non-integer literals; (f) includes floating-point literals and the external and internal floating-point data items; (g) includes DBCS items, DBCS literals and SPACE; (h) includes national items, national literals, national functions and the figurative constants ZERO, SPACE, QUOTE and ALL national-literal; (i) the same with UTF-8.

Three consequences are worth memorising, because they are the ones that surprise people:

  • A numeric item with decimals cannot be moved to an alphanumeric or alphabetic item. To show or store 18.99 as text, move it to a numeric-edited item first (PIC Z(3)9.99) and move that to the text item, or use a numeric-edited receiver and STRING.

  • An integer can be moved to an alphanumeric item (the digits are copied, left-aligned) but not to an alphabetic one.

  • Numeric-edited senders are valid wherever a numeric one is, because the compiler de-edits them (see De-editing), but moving one to an alphanumeric receiver copies the edited characters, comma and sign letters included.

The rules for the code-page conversions of national, UTF-8 and DBCS receivers are in National, UTF-8 and Code Pages.

The program below makes one legal move from each of the interesting sender categories, using the Circulation book price and fine fields. Note the alphanumeric "001250" moved to PIC 9(4)V99: an alphanumeric sender is treated as an unsigned integer, so the digits 001250 are the number 1250, not 12.50, and the implied decimal point plays no part in the move.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CATS.
      * One legal move from each interesting sender category.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-DIGITS          PIC X(6)   VALUE "001250".
       01  W-LETTERS         PIC A(6)   VALUE "ABCDEF".
       01  W-AN              PIC X(8).
       01  W-AN-EDITED       PIC XXBXX/XX.
       01  W-COPIES          PIC 9(3)   VALUE 42.
       01  W-NUMERIC         PIC 9(4)V99.
       01  W-NUM-EDITED      PIC Z,ZZ9.99.
       01  W-FLOAT           COMP-2.
       01  W-PRICE           PIC 9(4)V99.
       01  W-SHOW            PIC Z(3)9.99.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE W-DIGITS TO W-NUMERIC
           MOVE W-NUMERIC TO W-SHOW
           DISPLAY "X(6) digits to 9(4)V99:     " W-SHOW
           MOVE W-LETTERS TO W-AN
           DISPLAY "A(6) to X(8):               [" W-AN "]"
           MOVE W-LETTERS TO W-AN-EDITED
           DISPLAY "A(6) to alphanumeric-edited [" W-AN-EDITED "]"
           MOVE W-COPIES TO W-AN
           DISPLAY "9(3) integer to X(8):       [" W-AN "]"
           MOVE W-NUMERIC TO W-NUM-EDITED
           DISPLAY "9(4)V99 to numeric-edited:  [" W-NUM-EDITED "]"
           MOVE W-NUM-EDITED TO W-SHOW
           DISPLAY "numeric-edited to numeric:  [" W-SHOW "]"
           COMPUTE W-FLOAT = 18.99
           MOVE W-FLOAT TO W-PRICE
           MOVE W-PRICE TO W-SHOW
           DISPLAY "COMP-2 18.99 to 9(4)V99:    [" W-SHOW "]"
           MOVE W-COPIES TO W-FLOAT
           MOVE W-FLOAT TO W-PRICE
           MOVE W-PRICE TO W-SHOW
           DISPLAY "integer to COMP-2 and back: [" W-SHOW "]"
           STOP RUN.

Compiled with cobc -x -Wall cats.cob and run it prints (the same lines with -std=ibm):

X(6) digits to 9(4)V99:     1250.00
A(6) to X(8):               [ABCDEF  ]
A(6) to alphanumeric-edited [AB CD/EF]
9(3) integer to X(8):       [042     ]
9(4)V99 to numeric-edited:  [1,250.00]
numeric-edited to numeric:  [1250.00]
COMP-2 18.99 to 9(4)V99:    [  18.98]
integer to COMP-2 and back: [  42.00]

The last two lines show another category rule. A COMP-2 item moved to a fixed-point receiver is converted to a decimal value, and 18.99 is not exactly representable in binary floating point: GnuCOBOL 3.2 printed 18.98, so the conversion truncated the binary value 18.9899999... instead of rounding it. The Enterprise COBOL Programming Guide says that when a COMP-1, COMP-2 or external floating-point item or a floating-point literal is moved to a fixed-point item, rounding occurs in the low-order position of the receiver, and that a COMP-2 item moved to a fixed-point item of more than 18 digits keeps only 18 significant digits. The two compilers are not guaranteed to agree on the last digit, which is one more reason to keep money in fixed-point fields and never in floating point.

Illegal moves and what the compiler says

GnuCOBOL enforces the table at compile time for the clear-cut cases, and warns about literals that cannot be numeric. This program is deliberately wrong: a non-integer to an alphanumeric item, a non-integer to an alphabetic item, non-numeric text and literals to a numeric item, and SPACES to a numeric item.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ILLEGAL.
      * Deliberately wrong: moves that the category rules forbid.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-PRICE           PIC 9(4)V99  VALUE 18.99.
       01  W-NAME            PIC X(10)    VALUE "DUNE".
       01  W-LETTERS         PIC A(10).
       01  W-COPIES          PIC 9(3).
       01  W-ALPHA           PIC X(10).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE W-PRICE TO W-ALPHA
           MOVE W-PRICE TO W-LETTERS
           MOVE W-NAME TO W-COPIES
           MOVE "ABC" TO W-COPIES
           MOVE "12A" TO W-COPIES
           MOVE ALL "AB" TO W-COPIES
           MOVE SPACES TO W-COPIES
           MOVE W-NAME TO W-LETTERS
           STOP RUN.

With cobc -x -Wall illegal.cob the compiler reports the following (the lines that echo the source are left out), and produces no executable. The move on line 16, from the alphanumeric data item W-NAME to the numeric W-COPIES, is not reported: whether it makes sense depends on the content of a data item, not on a literal, and the compiler cannot see that.

illegal.cob: in paragraph 'MAIN-PARAGRAPH':
illegal.cob:14: error: invalid MOVE statement
illegal.cob:15: error: invalid MOVE statement
illegal.cob:17: warning: numeric value is expected [-Wtyping]
illegal.cob:9: note: 'W-COPIES' defined here as PIC 9(3) [-Wtyping]
illegal.cob:18: warning: numeric value is expected [-Wtyping]
illegal.cob:9: note: 'W-COPIES' defined here as PIC 9(3) [-Wtyping]
illegal.cob:19: warning: numeric value is expected [-Wtyping]
illegal.cob:9: note: 'W-COPIES' defined here as PIC 9(3) [-Wtyping]
illegal.cob:20: error: MOVE of figurative constant SPACE to numeric item used

Under -std=ibm the first message becomes a warning, illegal.cob:14: warning: MOVE of non-integer to alphanumeric [-Wothers], and the move is accepted; the other lines are unchanged. Enterprise COBOL treats a non-integer to alphanumeric move as invalid, so the IBM dialect setting of GnuCOBOL is more lenient there than the real compiler.

Alignment and truncation

The receiver, not the sender, decides how the data is lined up.

Receiver Rule

Numeric, numeric-edited

The data is aligned on the (implied) decimal point and padded with zeros or truncated at either end. A receiver without a decimal point is treated as if the point were just to its right, so decimals of the sender are dropped, not rounded.

Alphanumeric, alphanumeric-edited, alphabetic

The data is aligned at the left and padded with spaces on the right, or truncated on the right. JUSTIFIED RIGHT reverses this: the data is aligned at the right and a too-long sender loses its left-most characters (see PICTURE Clause).

National, national-edited, UTF-8

Aligned at the left, padded with national or UTF-8 spaces on the right, and truncated only at a character boundary; the programmer is responsible for not splitting a character made of several encoding units.

Internal floating point

A decimal point is assumed at the left; the data is aligned on the first digit position with the exponent adjusted.

DBCS

Padded or truncated on the right with DBCS spaces.

Truncation is silent. A numeric move that does not fit loses its high-order (left) digits and, if there are more decimals than the receiver holds, its low-order ones; a text move loses its trailing characters. No SIZE ERROR exists for MOVE (only the arithmetic statements have it), and no condition is raised. The program below moves a PIC 9(5)V99 value to smaller and larger receivers and a 21-character title to shorter and longer text receivers.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ALIGN.
      * MOVE alignment and truncation with Circulation fields.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-AMOUNT          PIC 9(5)V99  VALUE 12345.67.
       01  W-FINE            PIC 9(3)V9.
       01  W-FINE-INT        PIC 9(3).
       01  W-BIG-FINE        PIC 9(7)V999.
       01  W-TITLE-LONG      PIC X(30)
                             VALUE "THE HITCHHIKERS GUIDE".
       01  W-TITLE-SHORT     PIC X(12).
       01  W-TITLE-WIDE      PIC X(20).
       01  W-COPIES          PIC 9(3)     VALUE 7.
       01  W-COPIES-X        PIC X(5).
       01  W-COPIES-SHORT    PIC X(2).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE W-AMOUNT TO W-FINE
           DISPLAY "12345.67 to 9(3)V9:     " W-FINE
           MOVE W-AMOUNT TO W-FINE-INT
           DISPLAY "12345.67 to 9(3):       " W-FINE-INT
           MOVE W-AMOUNT TO W-BIG-FINE
           DISPLAY "12345.67 to 9(7)V999:   " W-BIG-FINE
           MOVE W-TITLE-LONG TO W-TITLE-SHORT
           DISPLAY "X(30) to X(12): [" W-TITLE-SHORT "]"
           MOVE "DUNE" TO W-TITLE-WIDE
           DISPLAY "DUNE to X(20):  [" W-TITLE-WIDE "]"
           MOVE W-COPIES TO W-COPIES-X
           DISPLAY "9(3) to X(5):   [" W-COPIES-X "]"
           MOVE W-COPIES TO W-COPIES-SHORT
           DISPLAY "9(3) to X(2):   [" W-COPIES-SHORT "]"
           STOP RUN.

Compiled with cobc -x -Wall align.cob, it prints:

12345.67 to 9(3)V9:     345.6
12345.67 to 9(3):       345
12345.67 to 9(7)V999:   0012345.670
X(30) to X(12): [THE HITCHHIK]
DUNE to X(20):  [DUNE                ]
9(3) to X(5):   [007  ]
9(3) to X(2):   [00]

12345.67 moved to PIC 9(3)V9 keeps the three lowest integer digits and one decimal: 345.6, not 345.7, because the dropped decimal is cut off and not rounded. Moved to PIC 9(3) it becomes 345. The 21-character title in X(12) loses its last nine characters; the three-digit copies count 007 moved to X(2) leaves 00, the first two digits, because a numeric integer sent to an alphanumeric receiver is treated as text. With -std=ibm the same program prints 3456, 345 and 0012345670: the values are identical, but that dialect’s DISPLAY writes implied-decimal digits without the decimal point (see the notes on DISPLAY in PICTURE Clause). To compare dialects, print through a numeric-edited item as most examples on this page do.

The compiler can warn about a literal that is certain to be truncated. This program is deliberately wrong; the literals do not fit:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TRUNCLIT.
      * Deliberately wrong: literals that do not fit their receivers.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-FINE            PIC 9(3)V9.
       01  W-CODE            PIC X(3).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE 1234.5 TO W-FINE
           MOVE "ABCDE" TO W-CODE
           DISPLAY W-FINE " " W-CODE
           STOP RUN.

With cobc -x -Wall trunclit.cob GnuCOBOL warns for both moves and still produces an executable that prints the truncated values (the source-echo lines are left out):

trunclit.cob: in paragraph 'MAIN-PARAGRAPH':
trunclit.cob:10: warning: value size exceeds data size [-Wtruncate]
trunclit.cob:10: note: value is 12345 [-Wtruncate]
trunclit.cob:6: note: 'W-FINE' defined here as PIC 9(3)V9 [-Wtruncate]
trunclit.cob:11: warning: value size exceeds data size [-Wtruncate]
trunclit.cob:11: note: value size is 5 [-Wtruncate]
trunclit.cob:7: note: 'W-CODE' defined here as PIC X(3) [-Wtruncate]
234.5 ABC

The note value is 12345 shows the digit string of the literal 1234.5 without its decimal point: the receiver PIC 9(3)V9 holds four digits, the literal has five. Whether Enterprise COBOL reports a literal that does not fit is not something this page verifies; a sender that is a data item and not a literal is truncated without a diagnostic by GnuCOBOL and without a condition by MOVE on z/OS.

Binary receivers and TRUNC

A DISPLAY or packed-decimal receiver always keeps exactly as many digits as its PICTURE allows. A binary (COMP, BINARY, COMP-4) receiver is different, because a halfword, fullword or doubleword can hold more than the picture’s digits. Which happens is decided by the compiler option TRUNC in Enterprise COBOL: with TRUNC(STD) (the default) the value is truncated to the digits of the PICTURE, with TRUNC(OPT) the compiler assumes the data fits and the result of a value that does not is unpredictable, and with TRUNC(BIN) the item is handled like COMP-5 and keeps the whole binary content. COMP-5 is not affected by TRUNC. Binary integers are exact; what goes wrong with them is truncation and decimal scaling, not rounding. GnuCOBOL’s equivalent is the pair of dialect settings binary-truncate and binary-size.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. BINTRUNC.
      * High-order truncation in DISPLAY, packed and binary receivers.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-BIG             PIC 9(5)  VALUE 12345.
       01  W-DISPLAY         PIC 9(4).
       01  W-PACKED          PIC 9(4)  COMP-3.
       01  W-BINARY          PIC 9(4)  COMP.
       01  W-BINARY5         PIC 9(4)  COMP-5.
       01  W-SHOW            PIC Z(4)9.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE W-BIG TO W-DISPLAY
           MOVE W-DISPLAY TO W-SHOW
           DISPLAY "PIC 9(4) DISPLAY: " W-SHOW
           MOVE W-BIG TO W-PACKED
           MOVE W-PACKED TO W-SHOW
           DISPLAY "PIC 9(4) COMP-3:  " W-SHOW
           MOVE W-BIG TO W-BINARY
           MOVE W-BINARY TO W-SHOW
           DISPLAY "PIC 9(4) COMP:    " W-SHOW
           MOVE W-BIG TO W-BINARY5
           MOVE W-BINARY5 TO W-SHOW
           DISPLAY "PIC 9(4) COMP-5:  " W-SHOW
           STOP RUN.

Compiled with cobc -x -Wall binary.cob (default dialect), the program prints:

PIC 9(4) DISPLAY:  2345
PIC 9(4) COMP-3:   2345
PIC 9(4) COMP:     2345
PIC 9(4) COMP-5:  12345

Under -std=ibm the COMP line changes, because that dialect sets binary-truncate to no and so behaves like TRUNC(BIN), not like the Enterprise COBOL default TRUNC(STD):

PIC 9(4) DISPLAY:  2345
PIC 9(4) COMP-3:   2345
PIC 9(4) COMP:    12345
PIC 9(4) COMP-5:  12345

Adding -fbinary-truncate to -std=ibm brings it back to 2345 (the output equals the default-dialect run). The full effect of each option, with the bytes, is in USAGE and Numeric Representation.

Sign handling

A sign is part of the data only when the receiver has one. The rules for a numeric or numeric-edited receiver are:

  • If the receiver is signed, it receives the sender’s sign, converted to the receiver’s sign representation. If the sender is unsigned, a positive sign is generated.

  • If the receiver is unsigned, no sign is stored and the absolute value of the sender is moved. A negative amount moved to an unsigned field silently becomes positive.

  • A numeric-edited receiver without a sign symbol behaves like an unsigned one: -12.50 is shown as 12.50.

  • A numeric sender moved to an alphanumeric (or national or UTF-8) receiver contributes its unsigned value; a separate sign character is not moved and the size of the sender counts as one less.

  • An alphanumeric, alphanumeric-edited, national or UTF-8 sender moved to a numeric receiver is treated as an unsigned integer.

The representation of signs in zoned and packed-decimal data, the preferred signs C, D and F, and the effect of NUMPROC are in USAGE and Numeric Representation. On z/OS an unsigned zoned sender moved to an alphanumeric receiver keeps its bytes unchanged, even with NUMPROC(NOPFD).

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SIGNS.
      * Sign handling in numeric and numeric-to-text moves.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-CREDIT          PIC S9(5)V99 VALUE -12.50.
       01  W-FINE-U          PIC 9(5)V99.
       01  W-FINE-S          PIC S9(5)V99.
       01  W-FINE-PACKED     PIC S9(5)V99 COMP-3.
       01  W-SHOW            PIC -(5)9.99.
       01  W-ADJUST          PIC S9(4)    VALUE -125.
       01  W-ADJUST-SEP      PIC S9(4)    SIGN IS LEADING SEPARATE.
       01  W-ADJUST-TEXT     PIC X(8).
       01  W-INT-TEXT        PIC X(3)     VALUE "042".
       01  W-INT-NUM         PIC S9(3).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE W-CREDIT TO W-FINE-U
           MOVE W-FINE-U TO W-SHOW
           DISPLAY "signed -12.50 to unsigned: " W-SHOW
           MOVE W-FINE-U TO W-FINE-S
           MOVE W-FINE-S TO W-SHOW
           DISPLAY "unsigned to signed:        " W-SHOW
           MOVE W-CREDIT TO W-FINE-PACKED
           MOVE W-FINE-PACKED TO W-SHOW
           DISPLAY "signed to COMP-3 and back: " W-SHOW
           MOVE W-ADJUST TO W-ADJUST-TEXT
           DISPLAY "S9(4) -125 to X(8):        [" W-ADJUST-TEXT "]"
           MOVE W-ADJUST TO W-ADJUST-SEP
           MOVE W-ADJUST-SEP TO W-ADJUST-TEXT
           DISPLAY "SIGN SEPARATE to X(8):     [" W-ADJUST-TEXT "]"
           MOVE W-INT-TEXT TO W-INT-NUM
           MOVE W-INT-NUM TO W-SHOW
           DISPLAY "X(3) '042' to S9(3):       " W-SHOW
           STOP RUN.

Compiled with cobc -x -Wall signs.cob (the same output with -std=ibm), it prints:

signed -12.50 to unsigned:     12.50
unsigned to signed:            12.50
signed to COMP-3 and back:    -12.50
S9(4) -125 to X(8):        [0125    ]
SIGN SEPARATE to X(8):     [0125    ]
X(3) '042' to S9(3):           42.00

The first two lines are the unsigned rule: -12.50 becomes 12.50 in PIC 9(5)V99 and stays positive when moved on to a signed item. The third line passes the signed value through a packed-decimal item and keeps the sign. The fourth and fifth lines show -125 moved to a text item: the digits 0125 appear, left-aligned and space-filled, and the minus sign is gone in both the overpunched and the SIGN IS LEADING SEPARATE forms.

Group moves

A move is a group move when the sender or the receiver is an alphanumeric group item (a group item that is not a national or UTF-8 group). The compiler then treats that group as plain alphanumeric text. The group move rules are short and unforgiving:

  • The move is treated as an alphanumeric-to-alphanumeric elementary move, except that nothing is converted. The receiving area is filled without regard to the elementary items inside either side.

  • The sender is aligned at the left. If it is shorter than the receiver, the rest of the receiver is filled with spaces, whatever the items there are; if it is longer, the excess on the right is cut off.

  • A group may be moved to an elementary item and an elementary item or literal to a group, in both cases as alphanumeric text. A national group and a UTF-8 group may be moved to each other, and either may be moved to an alphanumeric group.

  • A dynamic-length group cannot be moved to or from another group.

  • The OCCURS clause is the one exception to "regardless of the elementary items": the length of a table governed by OCCURS DEPENDING ON is evaluated before the move.

The classic accident is a numeric item that is part of the receiving group but lies beyond the end of the sending group. The program below moves a 30-byte LOAN-RECORD into a 33-byte working group whose last field is a numeric WORK-RETRIES that was just set to zero.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. GROUPS.
      * Group moves are byte copies: no conversion, space padding.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       COPY LOANREC.
       01  LOAN-WORK.
           05  WORK-TYPE         PIC X.
           05  WORK-MEMBER-ID    PIC 9(6).
           05  WORK-BOOK-ID      PIC 9(6).
           05  WORK-DATE         PIC 9(8).
           05  WORK-RESERVED     PIC X(9).
           05  WORK-RETRIES      PIC 9(3).
       01  LOAN-TEXT             PIC X(30).
       01  LOAN-SHORT            PIC X(12).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE "O" TO LOAN-TYPE
           MOVE 100 TO LOAN-MEMBER-ID
           MOVE 1 TO LOAN-BOOK-ID
           MOVE 20261020 TO LOAN-DATE
           MOVE ZERO TO WORK-RETRIES
           MOVE LOAN-RECORD TO LOAN-WORK
           DISPLAY "group to longer group: [" LOAN-WORK "]"
           IF WORK-RETRIES IS NUMERIC
               DISPLAY "WORK-RETRIES is numeric"
           ELSE
               DISPLAY "WORK-RETRIES is NOT numeric"
           END-IF
           MOVE LOAN-RECORD TO LOAN-TEXT
           DISPLAY "group to X(30):        [" LOAN-TEXT "]"
           MOVE LOAN-RECORD TO LOAN-SHORT
           DISPLAY "group to X(12):        [" LOAN-SHORT "]"
           MOVE "R000100000001" TO LOAN-RECORD
           DISPLAY "X to group:  " LOAN-TYPE "/" LOAN-MEMBER-ID "/"
               LOAN-BOOK-ID "/[" LOAN-DATE "]"
           STOP RUN.

Compiled with cobc -x -Wall -I. groups.cob, the program prints:

group to longer group: [O00010000000120261020            ]
WORK-RETRIES is NOT numeric
group to X(30):        [O00010000000120261020         ]
group to X(12):        [O00010000000]
X to group:  R/000100/000001/[        ]

The record is copied as 30 characters and the three bytes of WORK-RETRIES become spaces: the field that held 000 is not numeric any more. The short literal moved to LOAN-RECORD last spaces out LOAN-DATE the same way. Moving a group to a shorter text item cuts it after 12 bytes, in the middle of LOAN-BOOK-ID. Under -std=ibm the output is the same except that the last line shows [00000000], because that dialect’s DISPLAY of a numeric item holding spaces writes the low digit of the space (the zero); the stored bytes are still spaces and the class test still fails.

The usual remedies are to build the receiver field by field (the elementary moves convert and edit), to use MOVE CORRESPONDING when the names match, to move between groups that are exactly the same length, or to set the trailing numeric fields again after the group move (with MOVE ZERO or INITIALIZE). INITIALIZE is covered with the VALUE clause in VALUE, INITIALIZE and Condition Names. A group move is the right tool when two groups really have the same layout, as with MOVE LOAN-RECORD TO LOAN-SAVE, and the copybook is shared so they cannot drift apart (see Copybooks and REPLACE).

MOVE CORRESPONDING

MOVE CORRESPONDING group-1 TO group-2 moves every pair of subordinate items that correspond, as if each pair had its own MOVE. Two items correspond when all of these hold:

  • They have the same name and the same qualifiers, up to but not including group-1 and group-2.

  • At least one of the two is an elementary item, and a MOVE between them is allowed by the table above. In ADD and SUBTRACT CORRESPONDING both must be elementary numeric items; in MOVE the numeric-edited, text and numeric categories mix freely under the move rules.

  • Neither is FILLER, a level 66, 77 or 88 item, an index data item or a pointer-like item (POINTER, FUNCTION-POINTER, PROCEDURE-POINTER, OBJECT REFERENCE).

  • Neither has a REDEFINES, RENAMES or OCCURS clause, nor is it a USAGE INDEX or pointer item. The two group operands themselves may contain or be subordinate to items with REDEFINES or OCCURS.

  • Neither group operand is reference-modified, and the name of each matching item is unique after implicit qualification.

Items without a counterpart are left alone, and so are the filler areas of the receiver. The statement is a compile-time shorthand: if no pair corresponds it does nothing at run time. GnuCOBOL warns about that case (warning: no CORRESPONDING items found [-Wcorresponding]); this page does not verify whether Enterprise COBOL does.

The first program builds the Circulation member report line from the member master record. The two groups share the names MEMBER-ID, MEMBER-NAME, MEMBER-FINES and MEMBER-STATUS (names are qualified with OF where the program refers to the master record); MEMBER-JOINED exists only in the master and LINE-NOTE only in the line.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CORR.
      * MOVE CORRESPONDING from the member master to a report line.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       COPY MEMBREC.
       01  MEMBER-LINE.
           05  MEMBER-ID         PIC Z(5)9.
           05  FILLER            PIC X(2)  VALUE SPACES.
           05  MEMBER-NAME       PIC X(30).
           05  FILLER            PIC X(2)  VALUE SPACES.
           05  MEMBER-FINES      PIC -(5)9.99.
           05  FILLER            PIC X(2)  VALUE SPACES.
           05  MEMBER-STATUS     PIC X.
           05  LINE-NOTE         PIC X(10) VALUE "NOT MOVED".
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE 101 TO MEMBER-ID OF MEMBER-RECORD
           MOVE "GRACE HOPPER" TO MEMBER-NAME OF MEMBER-RECORD
           MOVE 20210301 TO MEMBER-JOINED
           MOVE 12.50 TO MEMBER-FINES OF MEMBER-RECORD
           SET MEMBER-ACTIVE TO TRUE
           MOVE CORRESPONDING MEMBER-RECORD TO MEMBER-LINE
           DISPLAY "[" MEMBER-LINE "]"
           STOP RUN.

Compiled with cobc -x -Wall -I. corr.cob (and with -std=ibm), it prints one line; the numeric MEMBER-ID and MEMBER-FINES are edited by their receivers, MEMBER-JOINED has no counterpart so it is not shown, LINE-NOTE keeps its initial value and the FILLER items stay spaces:

[   101  GRACE HOPPER                        12.50  ANOT MOVED ]

The second program shows what is skipped: a FILLER, an item that has a REDEFINES clause on one side, a table with OCCURS, and an item that exists in only one group. Everything else, including the nested group LOAN-DATE with its three children, is moved.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CORR2.
      * What MOVE CORRESPONDING skips: FILLER, OCCURS, REDEFINES,
      * and items that exist in only one group.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  LOAN-IN.
           05  LOAN-MEMBER-ID    PIC 9(6)  VALUE 100.
           05  LOAN-DATE.
               10  LOAN-YEAR     PIC 9(4)  VALUE 2026.
               10  LOAN-MONTH    PIC 99    VALUE 10.
               10  LOAN-DAY      PIC 99    VALUE 20.
           05  FILLER            PIC X(4)  VALUE "FILL".
           05  LOAN-FLAG         PIC X     VALUE "Y".
           05  LOAN-FLAG-N REDEFINES LOAN-FLAG PIC 9.
           05  LOAN-BOOK         PIC 9(6)  OCCURS 2 TIMES VALUE 7.
           05  LOAN-ONLY-IN      PIC X(4)  VALUE "ONLY".
       01  LOAN-OUT.
           05  LOAN-MEMBER-ID    PIC 9(6)  VALUE ZERO.
           05  LOAN-DATE.
               10  LOAN-YEAR     PIC 9(4)  VALUE ZERO.
               10  LOAN-MONTH    PIC 99    VALUE ZERO.
               10  LOAN-DAY      PIC 99    VALUE ZERO.
           05  FILLER            PIC X(4)  VALUE "keep".
           05  LOAN-FLAG         PIC X     VALUE "N".
           05  LOAN-FLAG-N       PIC 9     VALUE 5.
           05  LOAN-BOOK         PIC 9(6)  OCCURS 2 TIMES VALUE ZERO.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE CORRESPONDING LOAN-IN TO LOAN-OUT
           DISPLAY "LOAN-OUT: [" LOAN-OUT "]"
           STOP RUN.

Compiled with cobc -x -Wall corr2.cob (the same with -std=ibm), it prints:

LOAN-OUT: [00010020261020keepY5000000000000]

Reading the result: LOAN-MEMBER-ID (000100), the date 20261020 and LOAN-FLAG (Y) were moved; the FILLER still holds keep; LOAN-FLAG-N, which has a REDEFINES, still holds 5; and the OCCURS 2 table LOAN-BOOK still holds zeros, although the sender’s values were 7. The nested group matched by name, not by position: the children LOAN-YEAR, LOAN-MONTH and LOAN-DAY correspond because they have the same names and the same qualifier LOAN-DATE.

Why MOVE CORRESPONDING is used with care:

  • A name that is added to either group later starts to correspond with no change to the statement, and a name that is renamed stops corresponding, silently. Review every CORRESPONDING when a copybook changes.

  • Each pair follows the elementary move rules, so a numeric item moved to a smaller one of the same name truncates without a warning.

  • The qualification of the operands has to be spelled out when both groups come from copybooks with identical names, as the example does with OF.

  • GnuCOBOL’s manual describes the effect the same way: as if a series of individual moves were done, one for each match.

Figurative constants and MOVE ALL

A figurative constant is allowed wherever a literal is, except where the syntax forbids it. Its length is that of the associated item, or one when it stands alone. In a MOVE:

Sender Result

SPACE, SPACES

Spaces in every position of an alphabetic, alphanumeric, group, national or edited-text receiver. Not allowed with a numeric receiver (the table above).

ZERO, ZEROS, ZEROES

Numeric zero in a numeric receiver (no sign problem); the character 0 in every position of a text or group receiver.

HIGH-VALUE, LOW-VALUE

The highest and lowest character of the collating sequence (hexadecimal FF and 00 unless PROGRAM COLLATING SEQUENCE changes them) in every position of a text or group receiver. Not allowed with a numeric receiver.

QUOTE, QUOTES

The quotation mark character, repeated.

ALL literal

The string repeated and cut to the receiver’s length, for example ALL "-=". For a numeric receiver the literal must consist only of digits. When the literal is itself a figurative constant, ALL is only for readability: MOVE ALL SPACES equals MOVE SPACES.

Singular and plural spellings of SPACE, ZERO, QUOTE, HIGH-VALUE and LOW-VALUE are interchangeable. MOVE ZERO TO group fills a group with the character 0, including its packed and binary items, which makes them invalid; use INITIALIZE for groups that mix categories. The words and literals themselves are in Words, Literals and Figurative Constants.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. FIGS.
      * MOVE with figurative constants and ALL literals.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-RULE            PIC X(12).
       01  W-CODE            PIC X(5).
       01  W-COPIES          PIC 9(5).
       01  W-FINE            PIC 9(3)V99.
       01  W-SHOW            PIC ZZ9.99.
       01  W-BUFFER          PIC X(4).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE ALL "-=" TO W-RULE
           DISPLAY "ALL ""-="" to X(12):  [" W-RULE "]"
           MOVE ALL "*" TO W-RULE
           DISPLAY "ALL ""*"" to X(12):   [" W-RULE "]"
           MOVE ZERO TO W-CODE
           DISPLAY "ZERO to X(5):       [" W-CODE "]"
           MOVE SPACES TO W-CODE
           DISPLAY "SPACES to X(5):     [" W-CODE "]"
           MOVE ALL "12" TO W-COPIES
           DISPLAY "ALL ""12"" to 9(5):   [" W-COPIES "]"
           MOVE ZERO TO W-COPIES
           DISPLAY "ZERO to 9(5):       [" W-COPIES "]"
           MOVE ZEROS TO W-FINE
           MOVE W-FINE TO W-SHOW
           DISPLAY "ZEROS to 9(3)V99:   [" W-SHOW "]"
           MOVE HIGH-VALUES TO W-BUFFER
           DISPLAY "HIGH-VALUES bytes:  "
               FUNCTION HEX-OF(W-BUFFER)
           MOVE LOW-VALUES TO W-BUFFER
           DISPLAY "LOW-VALUES bytes:   "
               FUNCTION HEX-OF(W-BUFFER)
           STOP RUN.

Compiled with cobc -x -Wall figs.cob (and with -std=ibm), it prints:

ALL "-=" to X(12):  [-=-=-=-=-=-=]
ALL "*" to X(12):   [************]
ZERO to X(5):       [00000]
SPACES to X(5):     [     ]
ALL "12" to 9(5):   [21212]
ZERO to 9(5):       [00000]
ZEROS to 9(3)V99:   [  0.00]
HIGH-VALUES bytes:  FFFFFFFF
LOW-VALUES bytes:   00000000

The line ALL "12" to 9(5) is where the compilers can differ: GnuCOBOL printed 21212, which is the pattern aligned at the right as for any integer sent to a numeric receiver. The Enterprise COBOL text read for this page says that the literal "must be numeric characters" but does not state the alignment, so test it on the target compiler before relying on a multi-character pattern in a numeric receiver; a single digit (MOVE ALL "9") is unambiguous. FUNCTION HEX-OF prints the bytes that HIGH-VALUES and LOW-VALUES put in the item.

De-editing

When the sender of an elementary move is numeric-edited and the receiver is numeric or numeric-edited, the compiler de-edits the sender: it works out the unedited value (the sign included) that the edited text stands for, and moves that value under the usual numeric rules. So 1,234.50CR becomes -1234.50 in a PIC S9(5)V99 receiver, 1234.50 in a PIC 9(5)V99 one (the sign is dropped) and $1,234.50 in another edited item whose picture has no sign position. When the receiver is a text item there is nothing to de-edit: the edited characters are copied as they are.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. DEEDIT.
      * De-editing: a numeric-edited sender gives back its value,
      * but only when the receiver is numeric or numeric-edited.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-FINE-EDITED     PIC Z,ZZ9.99CR.
       01  W-FINE-NUM        PIC S9(5)V99.
       01  W-FINE-UNSIGNED   PIC 9(5)V99.
       01  W-FINE-REPORT     PIC $$$,$$9.99.
       01  W-FINE-TEXT       PIC X(10).
       01  W-JOINED          PIC 9(8)   VALUE 20210301.
       01  W-JOINED-EDITED   PIC 9999/99/99.
       01  W-JOINED-BACK     PIC 9(8).
       01  W-SHOW            PIC -(5)9.99.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE -1234.5 TO W-FINE-EDITED
           DISPLAY "edited sender:           [" W-FINE-EDITED "]"
           MOVE W-FINE-EDITED TO W-FINE-NUM
           MOVE W-FINE-NUM TO W-SHOW
           DISPLAY "de-edited to S9(5)V99:   [" W-SHOW "]"
           MOVE W-FINE-EDITED TO W-FINE-UNSIGNED
           MOVE W-FINE-UNSIGNED TO W-SHOW
           DISPLAY "de-edited to 9(5)V99:    [" W-SHOW "]"
           MOVE W-FINE-EDITED TO W-FINE-REPORT
           DISPLAY "to another edited item:  [" W-FINE-REPORT "]"
           MOVE W-FINE-EDITED TO W-FINE-TEXT
           DISPLAY "to X(10), no de-editing: [" W-FINE-TEXT "]"
           MOVE W-JOINED TO W-JOINED-EDITED
           DISPLAY "date edited:             [" W-JOINED-EDITED "]"
           MOVE W-JOINED-EDITED TO W-JOINED-BACK
           DISPLAY "date de-edited:          [" W-JOINED-BACK "]"
           STOP RUN.

Compiled with cobc -x -Wall deedit.cob (the same with -std=ibm), it prints:

edited sender:           [1,234.50CR]
de-edited to S9(5)V99:   [ -1234.50]
de-edited to 9(5)V99:    [  1234.50]
to another edited item:  [ $1,234.50]
to X(10), no de-editing: [1,234.50CR]
date edited:             [2021/03/01]
date de-edited:          [20210301]

The fifth line is the trap: the edited text, including CR, ends up in the alphanumeric item, and an IF ... IS NUMERIC test or an arithmetic statement on it would find the comma and the letters. The last two lines show the same de-editing for a yyyymmdd date with / insertion: the slashes are removed and the number comes back. De-editing is exact only for what the picture preserved, so an edited field that truncated, rounded or suppressed digits gives back the damaged value. Prefer the original numeric field when it is still available. The editing symbols and the other uses of the rule are in PICTURE Clause. Intrinsic functions such as NUMVAL and NUMVAL-C parse numbers from free text and are in Intrinsic Functions.

Reference modification as sender and receiver

Reference modification name(start:length) selects a substring of an alphanumeric (or national) item and can be used on either side of a MOVE. The start and length may be arithmetic expressions. A reference-modified operand of a USAGE DISPLAY item is category alphanumeric, whatever the category of the original item was: moving LOAN-DATE-TEXT(5:2) to a PIC 99 receiver is the alphanumeric-to-numeric move with the digits taken as an unsigned integer.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. REFMOD.
      * Reference modification as source and target of MOVE, and the
      * evaluation order of subscripts in a multi-receiver MOVE.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  LOAN-DATE-TEXT    PIC X(8)  VALUE "20261020".
       01  W-YEAR            PIC X(4).
       01  W-MONTH           PIC 99.
       01  W-ISBN-PLAIN      PIC X(13) VALUE "9780140449136".
       01  W-ISBN-DASHED     PIC X(17) VALUE SPACES.
       01  W-LEN             PIC 99    VALUE 5.
       01  W-TITLE           PIC X(20) VALUE "DUNE".
       01  W-ITEMS.
           05  W-A           PIC 9     OCCURS 3 TIMES.
           05  W-C           PIC 9     OCCURS 3 TIMES VALUE ZERO.
       01  W-B               PIC 9     VALUE 2.
       01  W-SHOW-C          PIC X(3).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE LOAN-DATE-TEXT(1:4) TO W-YEAR
           MOVE LOAN-DATE-TEXT(5:2) TO W-MONTH
           DISPLAY "year [" W-YEAR "] month [" W-MONTH "]"
           MOVE W-ISBN-PLAIN(1:3) TO W-ISBN-DASHED(1:3)
           MOVE W-ISBN-PLAIN(4:1) TO W-ISBN-DASHED(5:1)
           MOVE W-ISBN-PLAIN(5:2) TO W-ISBN-DASHED(7:2)
           MOVE W-ISBN-PLAIN(7:6) TO W-ISBN-DASHED(10:6)
           MOVE W-ISBN-PLAIN(13:1) TO W-ISBN-DASHED(17:1)
           MOVE "-" TO W-ISBN-DASHED(4:1) W-ISBN-DASHED(6:1)
               W-ISBN-DASHED(9:1) W-ISBN-DASHED(16:1)
           DISPLAY "ISBN [" W-ISBN-DASHED "]"
           MOVE ALL "*" TO W-TITLE(1:W-LEN)
           DISPLAY "title [" W-TITLE "]"
           MOVE 3 TO W-A(2)
           MOVE W-A(W-B) TO W-B, W-C(W-B)
           MOVE W-C(1) TO W-SHOW-C(1:1)
           MOVE W-C(2) TO W-SHOW-C(2:1)
           MOVE W-C(3) TO W-SHOW-C(3:1)
           DISPLAY "W-B now " W-B ", W-C = " W-SHOW-C
           STOP RUN.

Compiled with cobc -x -Wall refmod.cob (the same with -std=ibm), it prints:

year [2026] month [10]
ISBN [978-0-14-044913-6]
title [*****               ]
W-B now 3, W-C = 003

The first line splits the yyyymmdd text into a year and a month. The second builds a dashed ISBN from the thirteen digits by moving each group of digits to its position, and the third fills the first five characters of a title with asterisks, the length held in a data item. The last line is the evaluation-order rule from the beginning of the page: in MOVE W-A(W-B) TO W-B, W-C(W-B) the sender W-A(2) (value 3) is evaluated once, before the first receiver; the subscript W-B of the second receiver is evaluated only when that receiver is reached, after W-B has become 3, so the value lands in W-C(3), not in W-C(2). Enterprise COBOL’s manual states the same rule with the equivalent statements MOVE A(B) TO TEMP, MOVE TEMP TO B, MOVE TEMP TO C(B).

A reference-modified receiver that goes outside the item, or a start or length that is zero or negative, overwrites storage or reads garbage unless range checking is on: SSRANGE in Enterprise COBOL and -debug (or -fec=bound-ref-mod) in GnuCOBOL; see Debugging and Troubleshooting.

Overlapping operands

When the sending and receiving areas overlap, the result of a MOVE is unpredictable in Enterprise COBOL; the usual overlap is a reference-modified item moved onto itself with a shift. GnuCOBOL notices the shift at compile time. This program is deliberately risky:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. OVERLAP.
      * Deliberately risky: sending and receiving areas overlap.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-T PIC X(10) VALUE "ABCDEFGHIJ".
       PROCEDURE DIVISION.
           MOVE W-T(1:5) TO W-T(2:5)
           DISPLAY W-T
           MOVE "ABCDEFGHIJ" TO W-T
           MOVE W-T(2:5) TO W-T(1:5)
           DISPLAY W-T
           STOP RUN.

With cobc -x -Wall overlap.cob the compiler warns twice and the program runs:

overlap.cob:8: warning: overlapping MOVE may produce unpredictable results [-Woverlap]
overlap.cob:11: warning: overlapping MOVE may produce unpredictable results [-Woverlap]
AABCDEGHIJ
BCDEFFGHIJ

GnuCOBOL happened to behave like a memory move that is safe for overlaps (AABCDEGHIJ and BCDEFFGHIJ). Do not rely on that, and do not rely on the opposite on z/OS either: shift text with a separate work item, as in MOVE W-T(1:5) TO W-WORK, MOVE W-WORK TO W-T(2:5).

Moving numbers that are not valid

A numeric receiver does not guarantee that the bytes in it are a number. The compiler trusts the PICTURE and USAGE and generates code that assumes valid digits and signs; if they are not, the behaviour depends on the compiler and the options, and ranges from wrong results to an abend.

Invalid numeric data enters a program in a few well-known ways:

  • A record read from a file or message whose numeric column is blank, shorter than the record layout, or contains letters or an edited value.

  • A group move that spaces out a numeric item (the group move example above) or MOVE SPACES to a group that contains numeric items.

  • A REDEFINES of text over numeric storage, or a numeric field that was never given a value (see VALUE, INITIALIZE and Condition Names).

  • An alphanumeric sender moved to a numeric receiver: the compiler cannot check the content.

  • Packed or zoned data produced by another program, or converted wrongly from ASCII to EBCDIC and back.

  • Truncation (for example of a binary field with TRUNC(OPT)) that leaves a value outside the picture.

What GnuCOBOL does

GnuCOBOL does not abend on invalid data in the default build: spaces count as zero digits, letters contribute their low four bits, and class tests report the truth. The program below puts a book record with a blank copies column into BOOK-RECORD (the short-record case), tests it, and uses it.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. BADNUM.
      * How spaces reach a numeric field, and what the class test says.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       COPY BOOKREC.
       01  W-INPUT-LINE      PIC X(60).
       01  W-TOTAL           PIC 9(5)    VALUE ZERO.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
      * A short input line: the copies column is blank.
           MOVE SPACES TO W-INPUT-LINE
           MOVE "000001" TO W-INPUT-LINE(1:6)
           MOVE W-INPUT-LINE TO BOOK-RECORD
           DISPLAY "copies field: [" BOOK-COPIES "]"
           IF BOOK-COPIES IS NUMERIC
               DISPLAY "BOOK-COPIES is numeric"
           ELSE
               DISPLAY "BOOK-COPIES is NOT numeric"
           END-IF
           MOVE BOOK-COPIES TO W-TOTAL
           DISPLAY "after MOVE to 9(5): " W-TOTAL
           ADD BOOK-COPIES TO W-TOTAL
           DISPLAY "after ADD:          " W-TOTAL
           STOP RUN.

Compiled with cobc -x -Wall -I. invalid.cob, it prints:

copies field: [   ]
BOOK-COPIES is NOT numeric
after MOVE to 9(5): 00000
after ADD:          00000

The class test is the only reliable signal: IS NUMERIC is false for a blank field, and MOVE and ADD carry on with the value zero. Under -std=ibm the program is silent in the same way, except that the first DISPLAY writes [000] for the blank field.

Compiling with the runtime checks on turns the silent zero into a stop. With -fec=data-incompatible (or -debug, which turns on every check) the first statement that uses the invalid field as a sender stops the program with exit status 1, here the MOVE on line 21:

copies field: [   ]
BOOK-COPIES is NOT numeric
libcob: invalid.cob:21: error: 'BOOK-COPIES' (Type: NUMERIC DISPLAY) not numeric: '   '

 Last statement of "BADNUM" was MOVE
	MAIN-PARAGRAPH at invalid.cob:21
 Started by ./invalid

EC-DATA-INCOMPATIBLE is one of the exception conditions that the GnuCOBOL manual lists as fatal. It is the closest GnuCOBOL equivalent of NUMCHECK and, like it, adds run-time cost.

The same program with a packed-decimal and a zoned field made invalid through a REDEFINES over text:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. BADPACK.
      * Invalid packed and zoned data, and what GnuCOBOL does with it.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-RAW             PIC X(4)   VALUE X"12AC3C4C".
       01  W-PACKED          REDEFINES W-RAW PIC S9(7) COMP-3.
       01  W-ZONED-RAW       PIC X(4)   VALUE "12A4".
       01  W-ZONED           REDEFINES W-ZONED-RAW PIC 9(4).
       01  W-RESULT          PIC S9(7).
       01  W-TOTAL           PIC 9(5)   VALUE ZERO.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           IF W-PACKED IS NUMERIC
               DISPLAY "packed field is numeric"
           ELSE
               DISPLAY "packed field is NOT numeric"
           END-IF
           IF W-ZONED IS NUMERIC
               DISPLAY "zoned field is numeric"
           ELSE
               DISPLAY "zoned field is NOT numeric"
           END-IF
           MOVE W-PACKED TO W-RESULT
           DISPLAY "packed moved to S9(7): " W-RESULT
           ADD W-ZONED TO W-TOTAL
           DISPLAY "zoned added to total:  " W-TOTAL
           STOP RUN.

Compiled with cobc -x -Wall badpack.cob, it prints:

packed field is NOT numeric
zoned field is NOT numeric
packed moved to S9(7): +12:<3<4
zoned added to total:  01214

Both class tests fail, yet the packed field moved to a display field comes out as +12:<3<4, the nibbles A and C in digit positions shown as the characters : and < (the characters that follow 9 in the character set), and the zoned field with the letter A is added as if it were a digit. With -debug the program stops at the move (exit status 1):

packed field is NOT numeric
zoned field is NOT numeric
libcob: badpack.cob:24: error: 'W-PACKED' (Type: PACKED-DECIMAL) not numeric: '0x12ac3c4c'

 Last statement of "BADPACK" was MOVE
	MAIN-PARAGRAPH at badpack.cob:24
	ENTRY BADPACK at badpack.cob:13
 Started by ./badpack

The numeric class test

The class condition identifier IS NUMERIC checks the content against what is valid for the item’s PICTURE and USAGE: digits 0 to 9 in every digit position, a valid sign in the sign position, and for a zoned item a valid zone code in the upper four bits of every byte. Leading or trailing spaces make a zoned item non-numeric; a negative sign makes an unsigned item non-numeric. The valid sign values depend on the NUMPROC option and the installation’s NUMCLS setting. The condition is explained with the other tests in Conditions, IF and EVALUATE. The guarded move below applies it to five three-character texts from a transaction file before moving them to PIC 9(3).

       IDENTIFICATION DIVISION.
       PROGRAM-ID. GUARD.
      * Guarding a MOVE to a numeric field with the class test.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  W-TEXTS.
           05  FILLER        PIC X(3) VALUE "012".
           05  FILLER        PIC X(3) VALUE "  7".
           05  FILLER        PIC X(3) VALUE "1A3".
           05  FILLER        PIC X(3) VALUE SPACES.
           05  FILLER        PIC X(3) VALUE "-05".
       01  W-TABLE REDEFINES W-TEXTS.
           05  W-TEXT        PIC X(3) OCCURS 5 TIMES.
       01  W-IX              PIC 9.
       01  W-COPIES          PIC 9(3).
       01  W-ACCEPTED        PIC 9      VALUE ZERO.
       01  W-REJECTED        PIC 9      VALUE ZERO.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           PERFORM VARYING W-IX FROM 1 BY 1 UNTIL W-IX > 5
               IF W-TEXT(W-IX) IS NUMERIC
                   MOVE W-TEXT(W-IX) TO W-COPIES
                   ADD 1 TO W-ACCEPTED
                   DISPLAY "[" W-TEXT(W-IX) "] accepted: " W-COPIES
               ELSE
                   ADD 1 TO W-REJECTED
                   DISPLAY "[" W-TEXT(W-IX) "] rejected"
               END-IF
           END-PERFORM
           DISPLAY "accepted " W-ACCEPTED ", rejected " W-REJECTED
           STOP RUN.

Compiled with cobc -x -Wall guard.cob (the same with -std=ibm), it prints:

[012] accepted: 012
[  7] rejected
[1A3] rejected
[   ] rejected
[-05] rejected
accepted 1, rejected 4

Only 012 passes. Right-justified ` 7` is not numeric, which surprises people who think of it as the number 7, 1A3 contains a letter, the blank field is not a number, and -05 fails because the field is an alphanumeric item (PIC X(3)) with a sign character in it, which is not a digit. The remedy for the first and last is to convert with a function (NUMVAL) or to use UNSTRING/INSPECT (see STRING, UNSTRING and INSPECT) and then move the result.

What z/OS does: S0C7

On z/OS invalid data in a numeric item typically surfaces as the system completion code S0C7, a data exception program check. The hardware’s decimal instructions, which Enterprise COBOL uses for zoned and packed-decimal arithmetic and comparisons, raise it when an operand has an invalid digit or an invalid sign (general z/Architecture behaviour, which the Enterprise COBOL manuals read for this page do not restate). Which statement fails, and whether any does, depends on the generated code: a MOVE between two zoned items may be a byte copy that the processor never checks, so the abend often appears later, in the ADD or COMPUTE that finally uses the field, and the faulty statement is not the one that stored the spaces. The compiler is allowed to assume valid data (that is the price of NUMPROC(PFD) and NOINVDATA), and the Programming Guide says the results of using an item with invalid data are undefined.

In the Circulation nightly run the typical story is a LOANTRAN record whose date or member id was blank or shifted by a column: the validation step does an unguarded MOVE to a numeric working item, and the update step abends with an S0C7 at 2 a.m. The usual steps are to find the failing statement from the dump (offset or statement number in the compile listing, and the contents of the field in the CEEDUMP), find who put the bad bytes there, and then add the guard shown above. See Debugging and Troubleshooting for reading the dump and Error Handling and Declaratives for what the program can do on a bad record.

NUMCHECK

NUMCHECK makes the Enterprise COBOL compiler generate extra checks so that invalid data is reported where it is used and not only where an instruction later fails. The default is NONUMCHECK. For each statement it checks the sending operands, not the receivers (a receiver that is also a sender, as B in ADD A TO B, is checked as a sender). Zoned and packed senders get an implicit numeric class test, binary senders a size check, and an alphanumeric sender moved to a numeric receiver is treated as a numeric integer and tested too.

Suboption Meaning

ZON(ALPHNUM|NOALPHNUM, LAX|STRICT), NOZON

An implicit numeric class test for zoned decimal (USAGE DISPLAY) senders. ALPHNUM includes the senders compared with an alphanumeric item, literal or figurative constant; NOALPHNUM excludes those comparisons. LAX tolerates the valid layouts that come from a REDEFINES (an unsigned overlay of a signed item, spaces in the leading bytes of an overlay of a numeric-edited item, a zoned item just moved to a zoned or alphanumeric item); STRICT does not.

PAC, NOPAC

An implicit class test for packed-decimal (COMP-3) senders; for an even number of digits the unused bits are checked as well.

BIN(TRUNCBIN|NOTRUNCBIN), NOBIN

A check, similar to ON SIZE ERROR, that the content of a binary sender does not exceed its PICTURE. COMP-5 items are not checked. TRUNCBIN generates the check even under TRUNC(BIN); NOTRUNCBIN skips it for modules compiled with TRUNC(BIN).

MSG, ABD

MSG issues a run-time warning message with the line number, the data item name, the data item content and the program name, and the statement still runs; ABD issues a terminating message that ends in an abend.

NUMCHECK alone means NUMCHECK(ZON(ALPHNUM,STRICT),PAC,BIN(TRUNCBIN),MSG). If you name only some data types, the others are off: NUMCHECK(BIN) is NUMCHECK(NOZON,NOPAC,BIN(TRUNCBIN),MSG), and if every data type is off the listing shows NONUMCHECK. For CALL, BY CONTENT zoned and packed arguments are checked and BY REFERENCE ones are not. When the compiler can see at compile time that a check will always find invalid data it issues a compile-time message and drops the run-time check. ZONECHECK is deprecated; it is replaced by NUMCHECK(ZON(ALPHNUM)). NUMCHECK(ZON(LAX)) cannot be used together with NUMPROC(PFD). The Programming Guide warns that NUMCHECK is much slower than NONUMCHECK, and that more of the redundant checks are removed at higher OPT levels, so fewer messages appear there. It is a testing option, not a production setting.

The option is given in the PARM of the compile step or in a CBL (PROCESS) statement at the start of the program. These two fragments are z/OS; not run here:

//COBOL    EXEC IGYWCL,PARM.COBOL='NUMCHECK(ZON,PAC,MSG),TEST'
       CBL NUMCHECK(ZON,PAC,ABD)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. NIGHTLY.

Use MSG while hunting for the records that carry bad data, and ABD in a test where you want the run to stop at the first one. The procedures and the way compiler options are specified are in Compiling, Linking and Language Environment and Performance and Compiler Options.

INVDATA

INVDATA is a compatibility option for programs that were written for COBOL 4 or earlier versions and that depend on what those compilers did with invalid zoned and packed data. The default is NOINVDATA: the compiler assumes all data in USAGE DISPLAY and PACKED-DECIMAL items is valid and generates the most efficient code, for example a string comparison where it can. The Programming Guide recommends NOINVDATA even for programs that process invalid data, and recommends fixing the programs to avoid the invalid data once NUMCHECK shows where it comes from.

INVDATA without suboptions means INVDATA(NOFORCENUMCMP,CLEANSIGN). The suboptions are:

  • FORCENUMCMP makes comparisons of zoned decimal items ignore the zone bits of every digit (the value is converted to packed decimal first); NOFORCENUMCMP generates comparisons the way the older compilers did, with NUMPROC(NOPFD) or NUMPROC(PFD) as before, so the setting of NUMPROC in the new program must match the one used with the old compiler.

  • CLEANSIGN cleans the sign nibble of zoned and packed items on input to comparisons and to ADD, SUBTRACT, MULTIPLY and DIVIDE; NOCLEANSIGN does not, which raises the probability of an S0C7 when an operand has an invalid sign nibble. CLEANSIGN does not apply to SIGN IS SEPARATE items.

INVDATA affects MOVE statements, comparisons and computations on items that may hold invalid digits, signs or zone bits. The Programming Guide gives a migration table: from Enterprise COBOL 4.2 or earlier with valid data use NOINVDATA and the old NUMPROC (NUMPROC(MIG) becomes NOPFD); with invalid data and NUMPROC(MIG) use INVDATA(FORCENUMCMP,NOCLEANSIGN) and NUMPROC(NOPFD); with invalid data and NUMPROC(NOPFD) or PFD use INVDATA with the same NUMPROC. The table speaks of "COBOL 6.2 or later" while some summaries date the option to 6.3, so check the What’s New topic of your release before naming one. A zoned item with an invalid zone code is still non-numeric to the class test even when INVDATA tolerates the zone in comparisons. The wider migration is in Legacy Features and Migration.

Common MOVE bugs at a glance

Bug Symptom Fix

Receiver too small

High-order digits or trailing text vanish; no message

Size the receiver from the copybook, compare lengths in review, use ON SIZE ERROR arithmetic when the value is computed

Decimals lost

12345.67 into PIC 9(3) gives 345

Match the decimals, or round first with COMPUTE ... ROUNDED

Sign lost

A credit becomes a debit in an unsigned receiver

Declare the receiver S9(...), or test the sign before the move

Alphanumeric to numeric with spaces or letters

IS NUMERIC is false; S0C7 on z/OS, zeros or garbage on GnuCOBOL

Validate with the class test before the move; check NUMVAL-style conversion for free text

Short group moved to a longer group

Trailing numeric items become spaces

Move field by field, MOVE CORRESPONDING, or INITIALIZE first

Edited field used as a number

Arithmetic or class test fails on commas and CR

Use the unedited source item, or de-edit into a numeric item first

Non-integer to alphanumeric

Compile error (invalid MOVE)

Move through a numeric-edited item

Binary field above its PICTURE

2345 instead of 12345, or a large value with TRUNC(BIN)

Choose TRUNC deliberately; use COMP-5 for interface fields

MOVE CORRESPONDING after a copybook change

A new matching name is moved silently

Re-check every CORRESPONDING statement when either group changes

Overlapping reference modification

Unpredictable text

Use a work item

Standard, Enterprise COBOL and GnuCOBOL at a glance

Topic Enterprise COBOL for z/OS 6.5 GnuCOBOL 3.2

Non-integer numeric to alphanumeric

Invalid

Error by default; only a warning with -std=ibm

Integer to alphabetic

Invalid

Invalid (invalid MOVE statement)

MOVE SPACES to a numeric item

Invalid

Error: MOVE of figurative constant SPACE to numeric item used

Literal that does not fit the receiver

Not verified on this page

warning: value size exceeds data size [-Wtruncate]

Binary PIC 9(4) COMP receiving 12345

TRUNC(STD) truncates to 2345; TRUNC(BIN) keeps the binary value

Default dialect 2345; -std=ibm keeps 12345; -fbinary-truncate restores 2345

Floating point to fixed point

Rounding in the low-order position (Programming Guide)

COMP-2 18.99 gave 18.98 (truncated)

Invalid numeric data at run time

Typically S0C7; NUMCHECK reports, INVDATA tolerates

No abend; spaces count as zero; -debug or -fec=data-incompatible stops the program

Overlapping MOVE

Unpredictable

-Woverlap warning

Notes on the book

Tom Taulli’s Modern Mainframe Development (O’Reilly, 2022) gives MOVE a short section in its COBOL chapter. It presents MOVE as the COBOL replacement for assignment, shows that a text sender shorter than its receiver is padded with spaces and a longer one is cut off, that a number is aligned on the decimal point and truncated without rounding, and that several receivers can follow one MOVE. It lists three category combinations (alphanumeric to numeric, alphanumeric to numeric-edited, numeric to alphanumeric) as the ones allowed when the sender is an unsigned integer. That is too narrow for the current manuals: the full table above allows many more combinations (signed and non-integer numeric senders, numeric-edited senders that are de-edited, national and floating-point items), and the "unsigned integer" rule applies to an alphanumeric sender moved to a number, not to every pair. The book also points to ON SIZE ERROR as protection against truncation; that phrase belongs to the arithmetic statements, and MOVE has no SIZE ERROR phrase. The book was written against Enterprise COBOL 6.3 with examples run under GnuCOBOL, and it does not cover MOVE CORRESPONDING, de-editing, the group-move padding rule or the invalid-data topics at the end of this page: the names NUMCHECK, INVDATA and S0C7 do not appear in it. Its remark that binary data is risky for arithmetic "because of rounding" is wrong in the sense that matters here: binary integers are exact, and the real issues are TRUNC truncation and the placement of decimals, as shown above. For the current rules use the Enterprise COBOL 6.5 Language Reference and Programming Guide and the GnuCOBOL manual listed below.