COBOL Module 3 – Numbers in storage, money arithmetic and dates
USAGE: zoned, packed decimal (COMP-3) and binary
How COBOL stores numbers as DISPLAY, COMP-3, COMP, COMP-5 and floating point: the bytes of each, the sign half-bytes, dialect sizes and which USAGE to choose.
What you will learn
- Predict the storage size of DISPLAY, COMP-3, COMP, COMP-5, COMP-1 and COMP-2 items
- Read the bytes of zoned and packed decimal values, including their sign half-bytes
- Explain how binary sizes and byte order differ between GnuCOBOL dialects and IBM Enterprise COBOL
- Choose a USAGE for counters, money, files and interfaces to other languages
Before you start
What you’ll learn · Before you start
What you will learn
- Predict the storage size of DISPLAY, COMP-3, COMP, COMP-5, COMP-1 and COMP-2 items
- Read the bytes of zoned and packed decimal values, including their sign half-bytes
- Explain how binary sizes and byte order differ between GnuCOBOL dialects and IBM Enterprise COBOL
- Choose a USAGE for counters, money, files and interfaces to other languages
Before you start
On this page
The value -1234 takes 4 bytes in USAGE DISPLAY, 3 in packed decimal and 2 in binary, and the three share no byte: 31323374, 01234D and FB2E. A program that reads a field with the wrong USAGE gets a wrong number and no error, and a copybook built in another dialect can describe a record one byte shorter than the file it reads.
Below you will size an item of every USAGE before you compile it, read zoned and packed decimal bytes with their sign half-bytes, see where GnuCOBOL’s dialects and IBM Enterprise COBOL lay out binary items differently, and choose a USAGE for counters, money, files and calls to other languages.
USAGE decides how many bytes a number takes, and which bytes
The USAGE clause: The clause that says how an item's value is stored: DISPLAY as characters, PACKED-DECIMAL or COMP-3 as packed digits, BINARY, COMP or COMP-5 as binary integers, and COMP-1 or COMP-2 as floating point. says how the value of an item is kept in storage. An item that names none is USAGE DISPLAY, the characters of the lessons before this one. This program puts one value, -1234, into six items that differ only in USAGE, then a nine-digit amount into three, and prints the size of each from BYTE-LENGTH and its bytes from HEX-OF:
IDENTIFICATION DIVISION.
PROGRAM-ID. STORAGE.
* One value, -1234, under six USAGEs (PIC S9999 for the four
* that take a PICTURE), then the nine-digit amount PIC S9(7)V99
* under three of them: the size of each item from BYTE-LENGTH
* and its bytes from HEX-OF.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 N-DISPLAY PIC S9999 VALUE -1234.
01 N-PACKED PIC S9999 COMP-3 VALUE -1234.
01 N-BINARY PIC S9999 COMP VALUE -1234.
01 N-NATIVE PIC S9999 COMP-5 VALUE -1234.
01 N-FLOAT-1 COMP-1 VALUE -1234.
01 N-FLOAT-2 COMP-2 VALUE -1234.
01 A-DISPLAY PIC S9(7)V99 VALUE 1234567.89.
01 A-PACKED PIC S9(7)V99 COMP-3 VALUE 1234567.89.
01 A-BINARY PIC S9(7)V99 COMP VALUE 1234567.89.
PROCEDURE DIVISION.
DISPLAY "-1234 in PIC S9999 bytes hex"
DISPLAY "DISPLAY "
FUNCTION BYTE-LENGTH(N-DISPLAY) " "
FUNCTION HEX-OF(N-DISPLAY)
DISPLAY "COMP-3 "
FUNCTION BYTE-LENGTH(N-PACKED) " "
FUNCTION HEX-OF(N-PACKED)
DISPLAY "COMP "
FUNCTION BYTE-LENGTH(N-BINARY) " "
FUNCTION HEX-OF(N-BINARY)
DISPLAY "COMP-5 "
FUNCTION BYTE-LENGTH(N-NATIVE) " "
FUNCTION HEX-OF(N-NATIVE)
DISPLAY "COMP-1, no PICTURE "
FUNCTION BYTE-LENGTH(N-FLOAT-1) " "
FUNCTION HEX-OF(N-FLOAT-1)
DISPLAY "COMP-2, no PICTURE "
FUNCTION BYTE-LENGTH(N-FLOAT-2) " "
FUNCTION HEX-OF(N-FLOAT-2)
DISPLAY " "
DISPLAY "1234567.89 in PIC S9(7)V99"
DISPLAY "DISPLAY "
FUNCTION BYTE-LENGTH(A-DISPLAY) " "
FUNCTION HEX-OF(A-DISPLAY)
DISPLAY "COMP-3 "
FUNCTION BYTE-LENGTH(A-PACKED) " "
FUNCTION HEX-OF(A-PACKED)
DISPLAY "COMP "
FUNCTION BYTE-LENGTH(A-BINARY) " "
FUNCTION HEX-OF(A-BINARY)
STOP RUN. from session import files, run
# Sizes and bytes of one value under six USAGEs, built with GnuCOBOL's default dialect.
files("storage.cob")
run("""
cobc -x storage.cob
./storage
""") Output
$ cobc -x storage.cob $ ./storage -1234 in PIC S9999 bytes hex DISPLAY 4 31323374 COMP-3 3 01234D COMP 2 FB2E COMP-5 2 2EFB COMP-1, no PICTURE 4 00409AC4 COMP-2, no PICTURE 8 00000000004893C0 1234567.89 in PIC S9(7)V99 DISPLAY 9 313233343536373839 COMP-3 5 123456789C COMP 4 075BCD15
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 storage_session.py
Read the sizes first. DISPLAY spends a byte on each digit, 4 bytes, and packed decimal, COMP-3, fits two digits into a byte, 3 bytes. COMP keeps the number as a base-2 integer in 2 bytes, and so does COMP-5. COMP-1 and COMP-2 take no PICTURE at all: they are floating point, 4 and 8 bytes whatever the value. The nine-digit amount repeats the pattern at a larger size, 9, 5 and 4 bytes.
| USAGE | Also written | PIC S9999 | PIC S9(7)V99 | Size rule |
|---|---|---|---|---|
| DISPLAY | the default | 4 bytes | 9 bytes | one byte a digit |
| COMP-3 | PACKED-DECIMAL | 3 | 5 | two digits a byte, plus a sign |
| COMP | BINARY, COMP-4 | 2 | 4 | 2, 4 or 8 bytes by the digits |
| COMP-5 | none | 2 | 4 | as COMP |
| COMP-1, COMP-2 | no PICTURE | 4, 8 | 4, 8 | floating point |
Then compare the bytes with IBM’s table of internal representations, which gives the same value on z/OS. Packed decimal and binary match byte for byte, X’01234D’ and X’FB2E’. The others do not. GnuCOBOL wrote DISPLAY as ASCII characters, 31323374, where z/OS keeps EBCDIC ones, F1F2F3D4; COMP-5 came out as 2EFB, the two bytes of the COMP value in the other order; and COMP-1 and COMP-2 are IEEE floating point here, 00409AC4 for COMP-1, where IBM’s table shows hexadecimal floating point, C34D2000. The next three sections take those differences one at a time.
Size two items before you print them
Add 01 N-TEN PIC S9(10) COMP VALUE 1. and 01 N-ODD PIC S9(5) COMP-3 VALUE 1. to storage.cob with a DISPLAY of
each BYTE-LENGTH. Write down your two sizes, then build it with cobc -x storage.cob && ./storage: 8 bytes for ten
binary digits and 3 for five packed ones.
Checkpoint
Check: Bytes for each USAGE
Zoned decimal keeps a digit in each byte and the sign in the last zone
A DISPLAY number is called zoned decimal: A number in USAGE DISPLAY: one digit a byte, kept as a character, with the sign of a signed item in the zone, the left half-byte, of its last byte unless SIGN ... SEPARATE gives the sign a byte of its own., after the two halves of each of its bytes. Written in hexadecimal, a byte is two half-byte: Four bits, written as one hexadecimal digit. A byte is two half-bytes, so X'4D' has the half-bytes 4 and D; packed decimal keeps one digit or the sign in each.: the character 1 is X’31’ in ASCII, a zone of 3 and a digit of 1, and X’F1’ in EBCDIC, a zone of F. A signed item has no byte for its sign, so the sign takes the zone of the last digit. This program stores four values in PIC S9999, prints the character each signed digit becomes, then reads the text 123M back as a number. The session builds it twice, with GnuCOBOL’s own convention for ASCII and with -fsign=EBCDIC:
IDENTIFICATION DIVISION.
PROGRAM-ID. ZONED.
* Signed zoned decimal: four values in PIC S9999 as bytes and as
* the characters a text editor shows, the character of each
* signed digit, then the text 123M read as a number.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 AMT PIC S9999.
01 AMT-TEXT REDEFINES AMT PIC X(4).
01 ONE-DIGIT PIC S9.
01 ONE-TEXT REDEFINES ONE-DIGIT PIC X.
01 D PIC 99.
01 PLUS-ROW PIC X(10).
01 MINUS-ROW PIC X(9).
01 AMT-OUT PIC -9999.
PROCEDURE DIVISION.
MOVE 1234 TO AMT
PERFORM SHOW-AMT
MOVE -1234 TO AMT
PERFORM SHOW-AMT
MOVE 1230 TO AMT
PERFORM SHOW-AMT
MOVE -1230 TO AMT
PERFORM SHOW-AMT
PERFORM VARYING D FROM 0 BY 1 UNTIL D > 9
MOVE D TO ONE-DIGIT
MOVE ONE-TEXT TO PLUS-ROW(D + 1:1)
END-PERFORM
PERFORM VARYING D FROM 1 BY 1 UNTIL D > 9
COMPUTE ONE-DIGIT = 0 - D
MOVE ONE-TEXT TO MINUS-ROW(D:1)
END-PERFORM
DISPLAY "+0 to +9 " PLUS-ROW
DISPLAY "-1 to -9 " MINUS-ROW
MOVE "123M" TO AMT-TEXT
MOVE AMT TO AMT-OUT
DISPLAY "123M as a number " AMT-OUT
STOP RUN.
SHOW-AMT.
DISPLAY FUNCTION HEX-OF(AMT) " [" AMT-TEXT "]". from session import files, run
# The same program built twice: with GnuCOBOL's own sign convention for ASCII, then with -fsign=EBCDIC, which stores
# the sign the way an EBCDIC record reads once its bytes are translated to ASCII.
files("zoned.cob")
run("""
cobc -x zoned.cob
./zoned
cobc -x -fsign=EBCDIC -o zoned-ebcdic zoned.cob
./zoned-ebcdic
""") Output
$ cobc -x zoned.cob
$ ./zoned
31323334 [1234]
31323374 [123t]
31323330 [1230]
31323370 [123p]
+0 to +9 0123456789
-1 to -9 qrstuvwxy
123M as a number 1230
$ cobc -x -fsign=EBCDIC -o zoned-ebcdic zoned.cob
$ ./zoned-ebcdic
31323344 [123D]
3132334D [123M]
3132337B [123{]
3132337D [123}]
+0 to +9 {ABCDEFGHI
-1 to -9 JKLMNOPQR
123M as a number -1234
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 zoned_session.py
Only the last byte ever changed. In the default build a positive value kept the plain digit, X’34’, and a negative one got the zone 7, X’74’, which a text editor shows as t, so -1234 read 123t. With -fsign=EBCDIC the last byte is the character that the mainframe’s signed digit becomes when a record is translated from EBCDIC to ASCII: an opening brace for +0 and a closing one for -0, A to I for the positive digits 1 to 9 and J to R for the negative ones, so -1234 read 123M and -1230 read 123}. A sign kept in the zone of a digit is called an overpunch: A sign kept in the zone of a number's last digit instead of a byte of its own. An EBCDIC record translated to ASCII shows it as a letter or a brace, so -1234 reads 123M.. Step through the bytes of -1234:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The digits. In USAGE DISPLAY each digit is one byte that holds the digit as a character, so PIC S9999 is four bytes for 1, 2, 3 and 4. The S adds no byte.
- Two halves. A byte is two half-bytes, written as two hexadecimal digits. In ASCII the character 1 is X'31': the left half, the zone, is 3, and the right half is the digit 1.
- The sign. A signed zoned item has no byte for its sign. The sign takes the zone of the last byte instead, so only the byte that holds the 4 changes when the value is negative.
- GnuCOBOL's default. GnuCOBOL's own convention for ASCII sets that zone to 7 for a negative value: the last byte is X'74', which a text editor shows as t, so the item reads 123t.
- The EBCDIC convention. Built with -fsign=EBCDIC, GnuCOBOL stores X'4D', the character M. That is the mainframe's signed byte X'D4' after a record is translated from EBCDIC to ASCII, and the same build reads 123M back as -1234.
- On z/OS. On z/OS the digits are EBCDIC characters, X'F1' to X'F4', and the zone of the last byte is D for a negative value and C for a positive one: IBM's table gives F1 F2 F3 D4 for -1234.
Treating 123M in a translated file as damage
Wrong: removing the letter at the end of each amount in a file that came from z/OS as text. The default build’s last line shows what it made of 123M: 1230, positive, without a word. Right: describe the field as signed zoned decimal and build with -fsign=EBCDIC, which read the same bytes as -1234, or, better, have the file sent with SIGN TRAILING SEPARATE or edited amounts.
On z/OS itself the digits are EBCDIC characters, X’F1’ to X’F9’, and the zone of the last byte holds the sign: IBM’s table gives F1 F2 F3 D4 for -1234. IBM’s guide lists X’C’, X’A’, X’E’ and X’F’ as valid positive signs and X’D’ and X’B’ as negative ones, and its compiler writes the preferred signs itself, C for positive, D for negative and F for unsigned data.
Key idea
A zoned decimal item is one character for each digit, and its sign lives in the zone of the last byte: read that byte, and the sign convention of the file, before you trust the amount.
Checkpoint
Check: Zoned decimal and its sign zone
Packed decimal keeps two digits in a byte and the sign in the last half
packed decimal: USAGE PACKED-DECIMAL or COMP-3: two decimal digits in each byte and a sign in the last half-byte, C for positive, D for negative and F for unsigned, so PIC S9(5) takes 3 bytes., USAGE COMP-3 or PACKED-DECIMAL, drops the zones. Each digit takes one half-byte, and one more half-byte at the right holds the sign: C for positive, D for negative and F for an unsigned item, the same in GnuCOBOL and on z/OS. Seven items show the rule:
IDENTIFICATION DIVISION.
PROGRAM-ID. PACKED.
* Packed decimal: two digits a byte and the sign in the last
* half-byte. Each line shows the PICTURE, the size and the bytes.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 P-AMOUNT PIC S9(3)V99 COMP-3 VALUE -42.50.
01 P-UNSIGNED PIC 9(5) COMP-3 VALUE 12345.
01 P-ODD PIC S9(5) COMP-3 VALUE 12345.
01 P-EVEN PIC S9(4) COMP-3 VALUE 1234.
01 P-BALANCE PIC S9(7)V99 COMP-3 VALUE 1234567.89.
01 P-ONE PIC S9 COMP-3 VALUE 7.
01 P-TWO PIC S99 COMP-3 VALUE 42.
PROCEDURE DIVISION.
DISPLAY "S9(3)V99 -42.50 "
FUNCTION BYTE-LENGTH(P-AMOUNT) " "
FUNCTION HEX-OF(P-AMOUNT)
DISPLAY "9(5) 12345 "
FUNCTION BYTE-LENGTH(P-UNSIGNED) " "
FUNCTION HEX-OF(P-UNSIGNED)
DISPLAY "S9(5) 12345 "
FUNCTION BYTE-LENGTH(P-ODD) " "
FUNCTION HEX-OF(P-ODD)
DISPLAY "S9(4) 1234 "
FUNCTION BYTE-LENGTH(P-EVEN) " "
FUNCTION HEX-OF(P-EVEN)
DISPLAY "S9(7)V99 1234567.89 "
FUNCTION BYTE-LENGTH(P-BALANCE) " "
FUNCTION HEX-OF(P-BALANCE)
DISPLAY "S9 7 "
FUNCTION BYTE-LENGTH(P-ONE) " "
FUNCTION HEX-OF(P-ONE)
DISPLAY "S99 42 "
FUNCTION BYTE-LENGTH(P-TWO) " "
FUNCTION HEX-OF(P-TWO)
STOP RUN. from session import files, run
# Seven packed-decimal items: their sizes and their bytes.
files("packed.cob")
run("""
cobc -x packed.cob
./packed
""") Output
$ cobc -x packed.cob $ ./packed S9(3)V99 -42.50 3 04250D 9(5) 12345 3 12345F S9(5) 12345 3 12345C S9(4) 1234 3 01234C S9(7)V99 1234567.89 5 123456789C S9 7 1 7C S99 42 2 042C
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 packed_session.py
-42.50 in PIC S9(3)V99 is X’04250D’: the digits 0 4 2 5 0 read straight off the hexadecimal, then D for minus, and no point, because the V stores nothing. 12345 is X’12345F’ without an S and X’12345C’ with one. To size an item, count its digits and one half-byte for the sign, then round up to whole bytes: 3 bytes for 5 digits and 5 bytes for the 9 digits of S9(7)V99, as the run printed. Step through the packing of -42.50:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- The digits. PIC S9(3)V99 holds five digits. For -42.50 they are 0, 4, 2, 5 and 0. The leading zero fills the third integer place, and the V marks where the point falls without storing anything.
- The sign. Packed decimal keeps every digit in one half-byte and adds one more half-byte after the last digit for the sign. Its value is D for a negative number, C for a positive one and F for an unsigned item.
- The pairs. The six half-bytes make three bytes, 0 and 4, 2 and 5, 0 and D. Two digits share every byte except the last, which holds one digit and the sign.
- The result. The item is X'04250D', three bytes where USAGE DISPLAY needs five. Read the digits straight off the hexadecimal, then put the point where the PICTURE says: -042.50.
- An even digit count. With four digits and a sign there are five half-bytes, so the byte at the left keeps one half spare and fills it with 0: 1234 in PIC S9(4) COMP-3 is X'01234C', the same three bytes that PIC S9(5) uses for one more digit.
Key idea
Packed decimal is the digits in hexadecimal followed by a sign half-byte, C, D or F, so X’04250D’ reads -042.50 once the PICTURE says where the point goes.
S9(4) holding 1234 also took 3 bytes, X’01234C’. With an even number of digits the half-byte at the far left holds no digit and is set to 0, so S9(4) and S9(5) cost the same. IBM’s programming guide advises an odd number of digits for packed items, so that an item fills whole bytes with no half-byte left over, and IBM’s RULES(NOEVENPACK) option warns about every packed item with an even count, because a spare half-byte whose bits are not zero “can lead to an unexpected program behavior”.
Choosing PIC S9(4) COMP-3 to save a byte
Wrong: S9(4) COMP-3 for amounts up to 9999, to keep the field small. It takes 3 bytes, the same as S9(5), and wastes a half-byte. Right: give packed items an odd number of digits, S9(5) here, and the extra digit costs nothing.
Checkpoint
Check: Packed decimal and its sign half-byte
Binary sizes follow the dialect, and COMP-5 follows the machine
Binary items, COMP, BINARY or COMP-4, keep the value as a base-2 integer and treat the decimal places of the PICTURE as a scale: 1234567.89 in PIC S9(7)V99 COMP was X’075BCD15’, the integer 123456789. Their size comes from the number of digits, and here the compilers disagree. IBM documents 2 bytes for 1 to 4 digits, 4 for 5 to 9 and 8 for 10 to 18. GnuCOBOL’s default dialect sets binary-size to 1-2-4-8, which gives 1 or 2 digits a single byte. This record of three counters is built both ways:
IDENTIFICATION DIVISION.
PROGRAM-ID. BINARY-SIZES.
* Binary items: the size of a record of three COMP counters,
* then the bytes of 1234 in COMP (big-endian) and in COMP-5
* (the byte order of the machine that runs the program).
DATA DIVISION.
WORKING-STORAGE SECTION.
01 BRANCH-COUNTS.
05 BC-BRANCH PIC S9(2) COMP VALUE 12.
05 BC-DAYS PIC S9(4) COMP VALUE 1234.
05 BC-ITEMS PIC S9(9) COMP VALUE 123456789.
01 N-BINARY PIC S9(4) COMP VALUE 1234.
01 N-NATIVE PIC S9(4) COMP-5 VALUE 1234.
PROCEDURE DIVISION.
DISPLAY "BRANCH-COUNTS size "
FUNCTION BYTE-LENGTH(BRANCH-COUNTS) " "
FUNCTION HEX-OF(BRANCH-COUNTS)
DISPLAY " PIC S9(2) size "
FUNCTION BYTE-LENGTH(BC-BRANCH) " "
FUNCTION HEX-OF(BC-BRANCH)
DISPLAY " PIC S9(4) size "
FUNCTION BYTE-LENGTH(BC-DAYS) " "
FUNCTION HEX-OF(BC-DAYS)
DISPLAY " PIC S9(9) size "
FUNCTION BYTE-LENGTH(BC-ITEMS) " "
FUNCTION HEX-OF(BC-ITEMS)
DISPLAY "1234 in COMP " FUNCTION HEX-OF(N-BINARY)
DISPLAY "1234 in COMP-5 " FUNCTION HEX-OF(N-NATIVE)
STOP RUN. from session import files, run
# The binary program built with GnuCOBOL's default dialect and with -std=ibm, then the DISPLAY of a COMP-5 amount.
files("binary.cob", "display5.cob")
run("""
cobc -x -o binary binary.cob
./binary
cobc -x -std=ibm -o binary-ibm binary.cob
./binary-ibm
cobc -x display5.cob
./display5
""") Output
$ cobc -x -o binary binary.cob $ ./binary BRANCH-COUNTS size 7 0C04D2075BCD15 PIC S9(2) size 1 0C PIC S9(4) size 2 04D2 PIC S9(9) size 4 075BCD15 1234 in COMP 04D2 1234 in COMP-5 D204 $ cobc -x -std=ibm -o binary-ibm binary.cob $ ./binary-ibm BRANCH-COUNTS size 8 000C04D2075BCD15 PIC S9(2) size 2 000C PIC S9(4) size 2 04D2 PIC S9(9) size 4 075BCD15 1234 in COMP 04D2 1234 in COMP-5 D204 $ cobc -x display5.cob $ ./display5 COMP +0000025000000.00 COMP-5 +00000000002500000000 edited 25,000,000.00
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 binary_session.py
BRANCH-COUNTS was 7 bytes in the default build and 8 with -std=ibm, whose configuration sets binary-size to 2-4-8, because BC-BRANCH, PIC S9(2) COMP, took 1 byte and then 2. Every field after it in a record moves by that byte, so a copybook shared with z/OS is built with -std=ibm, or with -fbinary-size=2-4-8.
| Digits in the PICTURE | GnuCOBOL default, 1-2-4-8 | IBM Enterprise COBOL and -std=ibm, 2-4-8 |
|---|---|---|
| 1 or 2 | 1 byte | 2 bytes |
| 3 or 4 | 2 bytes | 2 bytes |
| 5 to 9 | 4 bytes | 4 bytes |
| 10 to 18 | 8 bytes | 8 bytes |
Byte order is the second difference. COMP and BINARY are big-endian: A byte order that stores the most significant byte first, so 1234 in two bytes is X'04D2'. COBOL's BINARY and COMP items use it in GnuCOBOL and on z/OS; COMP-5 takes the order of the machine. in both compilers, the most significant byte first, so 1234 was X’04D2’ in both builds, as in IBM’s table. COMP-5 is native binary, kept in the byte order of the machine that runs the program. This Mac’s arm64 processor is little-endian, so 1234 in COMP-5 was X’D204’, the same two bytes reversed, where IBM’s table shows COMP-5 on z/OS in big-endian order, X’3039’ for 12345. COMP-5 is also not held to its PICTURE: IBM lets it hold values up to the capacity of its bytes, so PIC S9(4) COMP-5 takes -32,768 to 32,767, which the next lesson’s overflow tests rely on. The session’s last program, display5.cob, prints one amount kept in COMP and in COMP-5:
IDENTIFICATION DIVISION.
PROGRAM-ID. DISPLAY5.
* The same amount with two decimal places in COMP and in COMP-5,
* printed with DISPLAY and through an edited item.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LIMIT-BIN PIC S9(13)V99 COMP VALUE 25000000.00.
01 LIMIT-NAT PIC S9(13)V99 COMP-5 VALUE 25000000.00.
01 LIMIT-OUT PIC Z,ZZZ,ZZZ,ZZZ,ZZ9.99-.
PROCEDURE DIVISION.
DISPLAY "COMP " LIMIT-BIN
DISPLAY "COMP-5 " LIMIT-NAT
MOVE LIMIT-NAT TO LIMIT-OUT
DISPLAY "edited " LIMIT-OUT
STOP RUN. Printing a COMP-5 amount with DISPLAY
Wrong: DISPLAY of a COMP-5 item with decimal places, which GnuCOBOL 3.2 printed as +00000000002500000000 for 25,000,000.00, with no point, while COMP in the same build printed +0000025000000.00. Right: move the item to an edited item first, as display5.cob did, which printed 25,000,000.00.
A layout that crosses from one machine to another therefore pins down both differences: the size of every binary field and the byte order it is written in.
Key idea
Binary sizes depend on the dialect, 1-2-4-8 in GnuCOBOL’s default and 2-4-8 in IBM’s compiler, and COMP-5 bytes depend on the machine: build shared layouts with -std=ibm, and keep COMP-5 for data that stays on one machine.
Checkpoint
Check: Binary sizes and byte order
Choose the USAGE by what the field holds and who reads it
The choice follows from the sizes and bytes above, with one more fact about floating point. COMP-1 and COMP-2 cover a wide range in 4 or 8 bytes but hold most decimal fractions only approximately: Python’s decimal documentation shows 0.1 + 0.1 + 0.1 - 0.3 in binary floating point coming to 5.5511151231257827e-017 instead of zero. IBM’s guide adds the advice for the others: BINARY, signed and with eight digits or fewer, for items used mainly in arithmetic or as subscripts, because binary operations are faster than decimal ones, and PACKED-DECIMAL for items of more than eight digits or used with DISPLAY data. Answer the two questions in the table for a field of your own:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| USAGE | Bytes for nine digits | Exact decimal amounts | Readable as text | Same bytes on z/OS |
|---|---|---|---|---|
| DISPLAY Stores the value as: one character for each digit | Poor one a digit: PIC S9(7)V99 took 9 bytes | Good decimal digits, so 1234567.89 is kept exactly | Good digit characters, with the sign hidden in the last byte | Mixed same digits, other bytes: X'31' here, X'F1' in EBCDIC |
| COMP-3 Stores the value as: two digits a byte, then a sign | Good 5 bytes: X'123456789C' | Good decimal digits kept exactly | Poor half-bytes that a text conversion would corrupt | Good the same bytes: X'01234D' for -1234 in both |
| COMP or BINARY Stores the value as: a big-endian binary integer | Good 4 bytes: X'075BCD15' | Mixed exact, the decimals a scale of the PICTURE; IBM advises 8 digits or fewer | Poor binary bytes are not characters | Mixed big-endian in both, but 1 or 2 digits take 1 byte here and 2 on z/OS |
| COMP-5 Stores the value as: binary in the machine's byte order | Good 4 bytes, as COMP | Mixed exact, but values may pass the PICTURE up to the binary limit | Poor binary bytes are not characters | Poor machine order: X'D204' for 1234 here, big-endian on z/OS |
| COMP-2 Stores the value as: 8-byte floating point | Mixed 8 bytes, whatever the digits | Poor approximate: 0.10 has no exact binary floating-point value | Poor floating-point bytes are not characters | Poor IEEE here, hexadecimal floating point on z/OS |
How the answers decide
- “What does the field hold?” An amount of money: rules out COMP-2. Most decimal fractions have no exact floating-point value, so sums of amounts drift from what the ledger expects.
- “What does the field hold?” An amount of money: adds a caveat to COMP or BINARY and COMP-5. The amount is exact, but its decimals are only a scale of the PICTURE; IBM advises PACKED-DECIMAL above eight digits.
- “What does the field hold?” A count or a subscript: adds a caveat to DISPLAY and COMP-3. IBM advises USAGE BINARY for items used mainly in arithmetic or as subscripts: binary operations are faster than decimal ones.
- “What does the field hold?” A count or a subscript: rules out COMP-2. A count is a whole number, and floating point brings approximation and 8 bytes for no gain.
- “What does the field hold?” A measurement, where range matters more than decimals: adds a caveat to DISPLAY, COMP-3, COMP or BINARY and COMP-5. A decimal PICTURE has a fixed number of digits, so it must be sized for the largest and smallest value; COMP-2 covers a wide range in 8 bytes.
- “Who else reads the field's bytes?” People, a report or a text file: rules out COMP-3, COMP or BINARY, COMP-5 and COMP-2. Only DISPLAY holds characters. Binary and packed bytes are not text, and a text conversion of a file changes them.
- “Who else reads the field's bytes?” A COBOL program on z/OS: rules out COMP-5 and COMP-2. COMP-5 takes the byte order of the machine, and COMP-2 its floating-point format: neither has the same bytes on z/OS.
- “Who else reads the field's bytes?” A COBOL program on z/OS: adds a caveat to COMP or BINARY and DISPLAY. Build COMP layouts with -std=ibm so that 1- and 2-digit items take 2 bytes; DISPLAY arrives in EBCDIC, with the sign in the last zone.
- “Who else reads the field's bytes?” A C program on the same machine: rules out DISPLAY and COMP-3. A C program reads binary integers and floating point; digits kept as characters or as packed half-bytes need a conversion first.
- “Who else reads the field's bytes?” A C program on the same machine: adds a caveat to COMP or BINARY. COMP is big-endian. A C program on this little-endian machine reads its two bytes in the other order; COMP-5 uses the order C uses.
- “Who else reads the field's bytes?” Only this program: Nobody else reads the bytes, so decide by what the field holds: size and exactness.
In short: DISPLAY where people or text files read the digits, COMP-3 for amounts that are stored and calculated, COMP or BINARY for counters and subscripts, COMP-5 for numbers shared with C on the same machine, and COMP-2 for measurements, never for money.
Checkpoint
Check: Choosing a USAGE
Practise: read the bytes before you run them
Size each item and read each set of bytes before you look at the answer. The questions take their values from this lesson’s runs, start with the idea you know least, and explain each wrong choice by the byte it misread:
Practice that adapts to you
Practise this lesson
15 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Decode a record by hand
The bytes 000C 04D2 075BCD15 are BRANCH-COUNTS from the -std=ibm build. Split them by the table of binary sizes, convert each part with MySmartCoPilot’s number base converter, and check that you get 12, 1234 and 123456789.
Interview questions
Warm-up (fresher to 2–5 years): what is packed decimal, and how many bytes does PIC S9(7)V99 COMP-3 take?
Packed decimal, USAGE COMP-3 or PACKED-DECIMAL, stores two decimal digits in each byte, one in each half-byte, and adds one half-byte at the right for the sign: C for positive, D for negative and F for an unsigned item. The decimal point is not stored; the PICTURE says where it falls. PIC S9(7)V99 has nine digits, so with the sign it needs ten half-bytes, which is 5 bytes, against 9 in USAGE DISPLAY. The value 1234567.89 is stored as X’123456789C’. A useful rule is digits divided by 2, rounded down, plus 1. An even digit count wastes the leftmost half-byte, so PIC S9(4) and PIC S9(5) both take 3 bytes, which is why IBM recommends odd digit counts. Packed bytes are also the same in GnuCOBOL and on z/OS.
Two harder questions apply these ideas: amounts that turned to nonsense after a file transfer, and a copybook whose record came out one byte short. Work out your answer first, then open each first step:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A daily extract from z/OS arrives on a Linux server through a transfer set to text mode. Names and dates read correctly, but every amount, a PIC S9(7)V99 COMP-3 field, is nonsense, and some records are a byte longer. What happened, and how should the extract be sent and read?
-
Fresher to mid
A copybook of counters, PIC S9(2) COMP, PIC S9(4) COMP and PIC S9(9) COMP, builds cleanly with GnuCOBOL, but the record it describes is 7 bytes while the file written on z/OS has 8, and every field after the first one reads wrong. Why, and how do you fix and guard it?
Organisations named on this page
MySmartCoPilot is independent of IBM and the GnuCOBOL project. The storage formats cited here come from their documentation, listed under References. The z/OS bytes are quoted from IBM’s table, and every other byte shown was printed by a GnuCOBOL 3.2 run.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
How many bytes does -1234 in PIC S9999 take as DISPLAY, COMP-3 and COMP?
4 as DISPLAY, a byte a digit; 3 as packed decimal, two digits a byte plus a sign; 2 as binary COMP.
-
Where does a signed zoned decimal item keep its sign?
In the zone, the left half-byte, of its last byte. On z/OS -1234 is F1 F2 F3 D4, and a text copy shows that last byte as a letter, M: an overpunch.
-
What do the sign half-bytes C, D and F mean in packed decimal?
C positive, D negative and F unsigned, in the rightmost half-byte. -42.50 in PIC S9(3)V99 COMP-3 is X'04250D', and the PICTURE says where the point goes.
-
How do you size a packed decimal item?
Digits divided by 2, rounded down, plus 1: the nine digits of S9(7)V99 need 5 bytes packed, against 9 as DISPLAY.
-
Why do IBM's guides advise an odd digit count for packed items?
An even count leaves the leftmost half-byte without a digit: S9(4) COMP-3 takes 3 bytes, exactly as S9(5) does.
-
How many bytes does PIC S9(2) COMP take?
1 in GnuCOBOL's default dialect, whose binary-size is 1-2-4-8, and 2 under IBM's rules and -std=ibm, 2-4-8. A record shared with z/OS is built with -std=ibm.
-
How does COMP-5 differ from COMP?
COMP is big-endian in both compilers. COMP-5 is native binary in the byte order of the machine, so 1234 is X'D204' on a little-endian Mac, and it may hold values up to the capacity of its bytes.
-
Which USAGE suits amounts, counters and numbers passed to C?
Packed decimal, COMP-3, for amounts that are stored and calculated; binary COMP for counters and subscripts; COMP-5 for numbers shared with C on the same machine. Floating point never holds money.
Key takeaways
- USAGE sets the bytes of a number: -1234 as PIC S9999 took 4 bytes in DISPLAY, 3 in COMP-3 and 2 in COMP and COMP-5, and as floating point 4 bytes in COMP-1 and 8 in COMP-2.
- Zoned decimal keeps one digit a byte and the sign in the zone of the last byte, so -1234 read 123t in GnuCOBOL’s default build, 123M with -fsign=EBCDIC and F1F2F3D4 on z/OS.
- Packed decimal keeps two digits a byte and a sign half-byte, C, D or F, so -42.50 in S9(3)V99 is X’04250D’, and an odd digit count wastes no half-byte.
- Binary sizes follow the dialect, 1 byte for 1 or 2 digits in GnuCOBOL’s default and 2 under IBM’s rules and -std=ibm, and COMP-5 follows the machine’s byte order, X’D204’ for 1234 here.
- Choose DISPLAY for text readers, COMP-3 for amounts, BINARY for counters and subscripts, COMP-5 for C on the same machine, and floating point only where approximation is acceptable.
Terms in this lesson
- big-endian
- A byte order that stores the most significant byte first, so 1234 in two bytes is X'04D2'. COBOL's BINARY and COMP items use it in GnuCOBOL and on z/OS; COMP-5 takes the order of the machine.
- half-byte
- Four bits, written as one hexadecimal digit. A byte is two half-bytes, so X'4D' has the half-bytes 4 and D; packed decimal keeps one digit or the sign in each.
- overpunch
- A sign kept in the zone of a number's last digit instead of a byte of its own. An EBCDIC record translated to ASCII shows it as a letter or a brace, so -1234 reads 123M.
- packed decimal
- USAGE PACKED-DECIMAL or COMP-3: two decimal digits in each byte and a sign in the last half-byte, C for positive, D for negative and F for unsigned, so PIC S9(5) takes 3 bytes.
- USAGE clause
- The clause that says how an item's value is stored: DISPLAY as characters, PACKED-DECIMAL or COMP-3 as packed digits, BINARY, COMP or COMP-5 as binary integers, and COMP-1 or COMP-2 as floating point.
- zoned decimal
- A number in USAGE DISPLAY: one digit a byte, kept as a character, with the sign of a signed item in the zone, the left half-byte, of its last byte unless SIGN ... SEPARATE gives the sign a byte of its own.
References
- Enterprise COBOL for z/OS 6.5: USAGE clause (IBM)
- Enterprise COBOL for z/OS 6.5: Formats for numeric data (IBM)
- Enterprise COBOL for z/OS 6.5: Examples of numeric data and internal representation (IBM)
- Enterprise COBOL for z/OS 6.5: Sign representation of zoned and packed-decimal data (IBM)
- Enterprise COBOL for z/OS 6.5: RULES compiler option (IBM)
- Enterprise COBOL for z/OS 6.5: Choosing efficient computational data items (IBM)
- Enterprise COBOL for z/OS 6.5: HEX-OF (IBM)
- GnuCOBOL manual: compiler options (GnuCOBOL project (GNU))
- GnuCOBOL 3.2 dialect configuration: default.conf (GnuCOBOL project (GNU))
- GnuCOBOL 3.2 dialect configuration: ibm-strict.conf (GnuCOBOL project (GNU))
- Python documentation: decimal, decimal fixed-point and floating-point arithmetic (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress