COBOL MOVE statement and the truncation rules that surprise everyone
Seven MOVEs in one GnuCOBOL program show where COBOL truncates on the right, on the left, drops a sign, or copies bytes blindly in a group move, with a rules table.
MOVE is the most common statement in any COBOL program and the one most likely to lose data without a word of complaint. It never fails, never raises anything, and never rounds. It aligns, pads, and cuts according to a short set of rules that depend on the class and size of the receiving item. This lesson puts the seven cases that cause real bugs into one program, shows the expected result of each, and names the rule behind it.
The listing is desk-checked against the GnuCOBOL 3.2 Programmer's Guide and against IBM's elementary move rules for Enterprise COBOL, which state the alignment rules more precisely than the GnuCOBOL guide does. The expected output was first derived by hand from those rules, case by case, in the section after the listing. On 30 October 2026 we compiled and ran the program with GnuCOBOL 3.2, and under the default dialect it printed exactly that output. The same day we compiled it again under -std=ibm and -std=mf, with two short probe programs beside it; the section Truncation under three dialects, below, shows which results move and which setting moves them. If you have not read the PICTURE lesson, read it first: every rule here is a consequence of how a picture lays out storage.
The program
>>SOURCE FORMAT IS FREE
*> cobol-move-truncation-rules.cob - MOVE between items of different size
*> and class, showing where COBOL truncates, pads, and drops a sign
*> Dialect: GnuCOBOL 3.x, free format
*> Status : desk-checked against the GnuCOBOL 3.2 Programmer's Guide;
*> compiled and run with GnuCOBOL 3.2 on 2026-10-30
*> Inputs : none
*> Outputs: nine lines on standard output
*> Build : cobc -x cobol-move-truncation-rules.cob
*> Run : ./cobol-move-truncation-rules
*> (Windows: cobol-move-truncation-rules.exe)
IDENTIFICATION DIVISION.
PROGRAM-ID. MOVE-RULES.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-Long-Text PIC X(12) VALUE "ACCOUNTS-RCV".
01 WS-Short-Text PIC X(5).
01 WS-Wide-Text PIC X(8).
01 WS-Three-Digits PIC 9(3).
01 WS-Money PIC 9(3)V99.
01 WS-Unsigned PIC 9(3).
01 WS-Signed PIC S9(3).
01 WS-Num-As-Text PIC X(6).
*> Two group items with the same length but a different field order.
01 WS-Source-Rec.
05 WS-Src-Id PIC 9(4) VALUE 1234.
05 WS-Src-Name PIC X(6) VALUE "SMITH".
01 WS-Target-Rec.
05 WS-Tgt-Name PIC X(6).
05 WS-Tgt-Id PIC 9(4).
PROCEDURE DIVISION.
MAIN-PARAGRAPH.
*> 1. Alphanumeric to a shorter alphanumeric: right end is lost.
MOVE WS-Long-Text TO WS-Short-Text
DISPLAY "1 X(12) to X(5) : [" WS-Short-Text "]"
*> 2. Alphanumeric to a longer alphanumeric: padded with spaces.
MOVE "AB" TO WS-Wide-Text
DISPLAY "2 'AB' to X(8) : [" WS-Wide-Text "]"
*> 3. Integer too big for the receiver: leading digits are lost.
MOVE 12345 TO WS-Three-Digits
DISPLAY "3 12345 to 9(3) : " WS-Three-Digits
*> 4. Too many decimals: trailing decimals are lost, no rounding.
MOVE 123.456 TO WS-Money
DISPLAY "4 123.456 to 9(3)V99 : " WS-Money
*> 5. Negative value into an unsigned item: the sign is dropped.
MOVE -42 TO WS-Unsigned
MOVE -42 TO WS-Signed
DISPLAY "5 -42 to 9(3) : " WS-Unsigned
DISPLAY "5 -42 to S9(3) : " WS-Signed
*> 6. Numeric integer to alphanumeric: digits, left-justified.
MOVE WS-Three-Digits TO WS-Num-As-Text
DISPLAY "6 9(3) to X(6) : [" WS-Num-As-Text "]"
*> 7. Group to group: a byte copy that ignores both layouts.
MOVE WS-Source-Rec TO WS-Target-Rec
DISPLAY "7 group move : [" WS-Target-Rec "]"
IF WS-Tgt-Id IS NOT NUMERIC
DISPLAY "7 WS-Tgt-Id is not numeric after the group move"
END-IF
STOP RUN.
The rules, one per case
The Programmer's Guide's description of the simple MOVE is short: the receiving items are replaced by the value of the sending item, numeric data may only be moved to numeric or numeric-edited receivers, and any conversion between usages happens on the way. The detail of what happens when the sizes differ is in IBM's elementary move rules, and GnuCOBOL follows the same standard rules. Here they are, applied.
| Case | Sending | Receiving PIC | Result | Rule |
|---|---|---|---|---|
| 1 | ACCOUNTS-RCV (12) |
X(5) |
ACCOU |
alphanumeric: left-aligned, excess on the right truncated |
| 2 | AB |
X(8) |
AB + 6 spaces |
alphanumeric: left-aligned, space-filled on the right |
| 3 | 12345 |
9(3) |
345 |
numeric: aligned on the decimal point, excess high-order digits truncated |
| 4 | 123.456 |
9(3)V99 |
123.45 |
numeric: aligned on the point, excess low-order digits truncated, never rounded |
| 5 | -42 |
9(3) |
042 |
unsigned receiver: the absolute value is stored |
| 5 | -42 |
S9(3) |
-042 |
signed receiver: sign kept |
| 6 | 345 (9(3)) |
X(6) |
345 + 3 spaces |
numeric integer to alphanumeric: digits treated as characters, left-aligned |
| 7 | group of 10 bytes | group of 10 bytes | 1234SMITH |
group move: byte copy, no conversion, layouts ignored |
Case 1 and case 2 are the alphanumeric rule in both directions. IBM's page says it exactly: the sending data is aligned at the left, and "excess characters on the right are truncated after the receiving item is filled". Padding is with spaces. Nothing warns you; WS-Short-Text simply holds the first five characters of the account name.
Case 3 is the one that bites. A numeric move aligns the sending value on the decimal point of the receiver and then copies digits outward in both directions. 12345 into three integer positions keeps the three digits nearest the point, 345, and the 12 on the left is gone. There is no size error on a MOVE; only the arithmetic verbs offer ON SIZE ERROR, which the COMPUTE lesson covers. If you have ever seen a total of 345 that should have been 12345, this was probably the cause.
Case 4 is the same rule at the other end. 123.456 aligned on the V of 9(3)V99 keeps 123 and 45; the 6 is dropped. IBM's wording is "alignment by decimal point and any necessary zero filling", and the Programmer's Guide's PICTURE section describes the same outward copying with "truncating sending digits if there aren't enough positions". No rounding takes place. If you want rounding, you need COMPUTE ... ROUNDED or ADD ... ROUNDED, not MOVE.
Case 5 is the sign rule. The Programmer's Guide says that without an S in the picture, a negative value "will have the negative sign stripped from it (in effect becoming the absolute value)". IBM says the same in different words: for an unsigned receiver "the absolute value of the sending item is used". So -42 into PIC 9(3) is 042, and the same move into PIC S9(3) keeps the minus. Unsigned pictures are the right choice for quantities that cannot be negative, and the wrong choice for any balance.
Case 6 moves a numeric item to an alphanumeric one. The digits are copied as characters, left-aligned and space-padded, with no sign and no decimal point. The IBM rules restrict this to integer senders, which is why the program moves the three-digit integer rather than the money field; moving 9(3)V99 to X(6) is not a valid elementary move, and GnuCOBOL's default dialect refuses to compile it. Two other dialects accept it with a warning, as the dialect section below shows.
Case 7 is the group move, and it is the reason every record layout in a COBOL shop is treated with care. Both groups are ten bytes long, but the source has a four-digit id followed by a six-character name and the target has them the other way round. IBM's group-moves page states that a group move "is treated as though it were an alphanumeric-to-alphanumeric elementary move, except that there is no conversion of data from one form of internal representation to another". So the ten bytes 1234SMITH are copied as they are, and afterwards WS-Tgt-Name holds 1234SM and WS-Tgt-Id, a numeric field, holds ITH followed by a space. The program then uses a NUMERIC class test to prove it. There is a MOVE CORRESPONDING form, documented in the Programmer's Guide's MOVE section, that moves only the subordinate fields whose names match, one elementary move each; it would have moved nothing here because the names differ, but it is the tool for the case where you want field-by-field semantics.
Compile and run
cobc -x cobol-move-truncation-rules.cob
./cobol-move-truncation-rules
If you compile with warnings enabled (-Wall), GnuCOBOL 3.2 points at cases 3, 4 and 5, because the compiler can see that a literal will lose digits or its sign; case 1 is flagged only with -Wextra. The exact messages are in the dialect section below. Those warnings are the cheapest bug fix in the language and are worth leaving on.
Worked example
Expected output, derived by hand from the rules above, and identical, line for line, to what the compiled program printed under the default dialect on 30 October 2026 (GnuCOBOL's default pretty-display setting inserts the decimal point and a leading sign when displaying numeric items):
1 X(12) to X(5) : [ACCOU]
2 'AB' to X(8) : [AB ]
3 12345 to 9(3) : 345
4 123.456 to 9(3)V99 : 123.45
5 -42 to 9(3) : 042
5 -42 to S9(3) : -042
6 9(3) to X(6) : [345 ]
7 group move : [1234SMITH ]
7 WS-Tgt-Id is not numeric after the group move
Cases 3 and 4 can also be checked with a two-line Python calculation of the digit selection, which we ran:
print(str(12345)[-3:]) # 345 (keep the 3 digits nearest the point)
print("%.2f" % (int(123.456 * 100) / 100)) # 123.45 (truncate, do not round)
Truncation under three dialects
GnuCOBOL can compile a program as if it had been written for another compiler, and the dialect lesson reads, setting by setting, what each -std choice changes. This section asks the narrower question for MOVE: compile the rules above under the default dialect, under -std=ibm and under -std=mf, and see which results move. On 30 October 2026 we compiled and ran three programs with GnuCOBOL 3.2 (cobc (GnuCOBOL) 3.2.0, the MSYS2 MinGW-w64 build on Windows), once per dialect: this lesson's listing and two probes that ship beside it, cobol-move-binary-truncate.cob and cobol-move-noninteger.cob. The command is the same each time with one switch added:
cobc -x cobol-move-truncation-rules.cob
cobc -x -std=ibm cobol-move-truncation-rules.cob
cobc -x -std=mf cobol-move-truncation-rules.cob
The seven MOVEs
Under -std=mf the program printed the worked example above, byte for byte (so did -std=cobol2014). Under -std=ibm it printed this:
1 X(12) to X(5) : [ACCOU]
2 'AB' to X(8) : [AB ]
3 12345 to 9(3) : 345
4 123.456 to 9(3)V99 : 12345
5 -42 to 9(3) : 042
5 -42 to S9(3) : 042-
6 9(3) to X(6) : [345 ]
7 group move : [1234SMITH ]
7 WS-Tgt-Id is not numeric after the group move
Two lines differ, and neither difference is in the MOVE. -std=ibm reads ibm.conf, which includes ibm-strict.conf, and that file sets pretty-display: no. Two more builds pin it to that one setting: cobc -x -std=ibm -fpretty-display printed the default output exactly, and cobc -x -fno-pretty-display, the default dialect with only that setting turned off, printed the -std=ibm output exactly. So case 4 still stores 123.45 and case 5 still stores minus 42 under -std=ibm; what changes is how DISPLAY writes them. Without pretty-display, an item with an implied decimal point prints its digits with no point (12345), and this signed item prints its sign after the digits (042-). A test that compares printed output between a z/OS job and a GnuCOBOL build has to allow for that; the stored values are the same. The -std=ibm build also printed 16 warnings while compiling, one for each division, section, level-01 entry and paragraph name in this free-format listing, each saying that the name "should start in Area A"; none stopped the build.
Binary receivers: the dialects store different numbers
The seven MOVEs all go to USAGE DISPLAY items, where the picture limits the digits in every dialect. Binary items are where dialects part, because two settings meet there: binary-truncate (does a binary item keep only the digits its picture allows?) and pretty-display (does DISPLAY print the picture's digits or what the bytes hold?). The probe moves 12345 into binary items too small for it and prints each one twice: as DISPLAY shows it, then after copying it into a five-digit DISPLAY item, which shows what the item holds. Default dialect:
A 12345 to 9(3) DISPLAY : 345 holds 00345
A 12345 to 9(3) COMP : 345 holds 00345
A 12345 to 9(3) COMP-5 : 12345 holds 12345
A 12345 to 9(4) COMP : 2345 holds 02345
B -42 to 9(3) COMP : 042
C -42 in S9(3) : -042
C 123.45 in 9(3)V99 : 123.45
D bytes in 9(3) COMP : 02
-std=ibm:
A 12345 to 9(3) DISPLAY : 345 holds 00345
A 12345 to 9(3) COMP : 12345 holds 12345
A 12345 to 9(3) COMP-5 : 12345 holds 12345
A 12345 to 9(4) COMP : 12345 holds 12345
B -42 to 9(3) COMP : 00042
C -42 in S9(3) : 042-
C 123.45 in 9(3)V99 : 12345
D bytes in 9(3) COMP : 02
-std=mf:
A 12345 to 9(3) DISPLAY : 345 holds 00345
A 12345 to 9(3) COMP : 345 holds 12345
A 12345 to 9(3) COMP-5 : 12345 holds 12345
A 12345 to 9(4) COMP : 2345 holds 12345
B -42 to 9(3) COMP : 042
C -42 in S9(3) : -042
C 123.45 in 9(3)V99 : 123.45
D bytes in 9(3) COMP : 02
| Receiver | default: shows / holds | -std=ibm: shows / holds |
-std=mf: shows / holds |
|---|---|---|---|
9(3) DISPLAY |
345 / 345 | 345 / 345 | 345 / 345 |
9(3) COMP |
345 / 345 | 12345 / 12345 | 345 / 12345 |
9(3) COMP-5 |
12345 / 12345 | 12345 / 12345 | 12345 / 12345 |
9(4) COMP |
2345 / 2345 | 12345 / 12345 | 2345 / 12345 |
The settings behind the columns are in the dialect files that ship with GnuCOBOL 3.2. default.conf sets binary-truncate: yes and pretty-display: yes. ibm-strict.conf sets both to no. mf-strict.conf sets binary-truncate: no and pretty-display: yes. (ibm.conf and mf.conf read their strict files first and then lax.conf-inc, which sets neither.) All three put a three-digit binary item in two bytes, which line D confirms, and two unsigned bytes hold up to 65535, so 12345 fits whenever nothing truncates it.
- Default: the store truncates to the picture, so
9(3) COMPholds 345 and shows 345, the same as theDISPLAYitem. What you see is what is there. -std=ibm: nothing truncates, so the item holds 12345, andDISPLAYwithout pretty-display prints as many digits as two bytes can hold:12345, and00042for the minus 42 that line B moves into an unsigned item. Again, what you see is what is there, only wider.-std=mf: the dangerous one. Nothing truncates, so the item holds 12345, but pretty-display edits it into the picture's three digits and prints345. The screen and the storage disagree: a program debugged byDISPLAYunder-std=mfshows 345 for a field that compares, adds and writes as 12345.
The extra builds tie each column to its setting. cobc -x -std=mf -fbinary-truncate made 9(3) COMP hold 345 and 9(4) COMP hold 2345, the default's values; cobc -x -std=mf -fno-pretty-display made DISPLAY print the 12345 the items held; cobc -x -std=ibm -fbinary-truncate stored 345 and printed it as 00345. COMP-5 held 12345 in every build, with truncation on or off: in these runs GnuCOBOL limited COMP-5 by its bytes, not its picture. The sign rule from case 5 holds for binary receivers in all three dialects (line B). -std=cobol2014 refused this probe at the COMP-5 line, code/cobol-move-binary-truncate.cob:26: error: syntax error, unexpected Identifier, because the word list GnuCOBOL 3.2 uses for that dialect, cobol2014.words, has no COMP-5 entry.
The integer-only rule is a dialect setting
The probe cobol-move-noninteger.cob makes the move case 6 avoids: 9(3)V99 holding 123.45 into X(6). The default dialect refused it:
code/cobol-move-noninteger.cob:29: error: invalid MOVE statement
-std=ibm and -std=mf compiled it with this warning and ran it:
code/cobol-move-noninteger.cob:29: warning: MOVE of non-integer to alphanumeric [-Wothers]
9(3)V99 to X(6) : [12345 ]
The digits arrive without the decimal point. The setting is move-noninteger-to-alphanumeric: error in default.conf, ibm-strict.conf and cobol2014.conf, warning in mf-strict.conf, and +warning in lax.conf-inc, which relaxes IBM's error to a warning in the plain -std=ibm dialect. We did not build the -strict dialects.
What the warnings catch
-Wall on the default dialect printed three warnings for the lesson's listing, at the lines of cases 3, 4 and 5:
code/cobol-move-truncation-rules.cob:48: warning: value size exceeds data size [-Wtruncate]
code/cobol-move-truncation-rules.cob:52: warning: value size exceeds data size [-Wtruncate]
code/cobol-move-truncation-rules.cob:56: warning: ignoring sign [-Wtruncate]
-Wextra added code/cobol-move-truncation-rules.cob:40: warning: sending field larger than receiving field [-Wpossible-truncate], which is case 1, plus nine -Wterminator warnings about DISPLAY without END-DISPLAY. Neither option said anything about case 7, the group move that leaves a numeric field holding letters; the NUMERIC test in the listing is what catches it.
Limitations
The program has been compiled and run with GnuCOBOL 3.2 only, under its default, -std=ibm, -std=mf and -std=cobol2014 dialects; IBM Enterprise COBOL and Micro Focus COBOL themselves were not run, so the -std=ibm and -std=mf results show what GnuCOBOL does when it imitates them, not what those compilers do. Case 6 relies on the integer-only rule for numeric-to-alphanumeric moves; a fractional sender would be rejected or warned about rather than silently handled. Case 7's NUMERIC test is the only check the program makes; nothing in COBOL stops a group move from corrupting a numeric field, and a later arithmetic statement on WS-Tgt-Id would work on garbage. The lesson also leaves out moves involving COMP, COMP-3 and edited receivers, where conversion happens during the move; the edited case is the subject of the lesson on edited pictures.
Correction, 30 October 2026: two statements here were wrong when checked against a real compile. The page said that with -Wall GnuCOBOL would point at cases 3 and 5; GnuCOBOL 3.2 warns at cases 3, 4 and 5, and at case 1 only with -Wextra. It also said that moving a 9(3)V99 item to X(6) draws a compile-time warning; the default dialect refuses it with an error, and only -std=ibm and -std=mf accept it with a warning. The same day the program was compiled and run for the first time; its output matched the worked example line for line, and the page now says so instead of calling the listing desk-checked only.
Index terms cobol, gnucobol, move statement, truncation, group move