Signed Numbers and Overpunch

COBOL GnuCOBOL 3.2.0 · ✓ verified by execution on 2026-08-16

In many modern languages, all integers are automatically signed (capable of holding negative numbers). In COBOL, every numeric variable is strictly unsigned by default.

If you move a negative number into a standard PIC 9 variable, COBOL simply strips the minus sign away!

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. UNSIGNED-DEMO.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 RAW-NEG PIC S99 VALUE -42.
       01 UNSIGNED-NUM PIC 99.
       PROCEDURE DIVISION.
           MOVE RAW-NEG TO UNSIGNED-NUM.
           DISPLAY UNSIGNED-NUM.
           STOP RUN.
Output
42

To tell COBOL that a variable can hold negative numbers, you must add an S (for Signed) to the front of the PICTURE clause.

The Danger of Unsigned Math

A common beginner mistake is using unsigned variables for everything, assuming that “since the inputs are positive, the variables can be unsigned.”

But what happens if an intermediate calculation temporarily dips below zero? Let’s find out. Can you predict what this program will print?

Predict the output cobol

Read the code. What exactly will it print? Commit to an answer before you look.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. MATH-BUG.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 BALANCE PIC 99 VALUE 10.
       01 WITHDRAWAL PIC 99 VALUE 15.
       01 RESULT PIC 99.
       PROCEDURE DIVISION.
           SUBTRACT WITHDRAWAL FROM BALANCE GIVING RESULT.
           DISPLAY 'RESULT SHOULD BE -5, BUT IS: ' RESULT.
           STOP RUN.
Output
RESULT SHOULD BE -5, BUT IS: 05

Because RESULT is defined as PIC 99 (unsigned), it cannot store -5. COBOL silently strips the sign and stores 05 (the absolute value), causing a devastating logic error where an overdrawn account suddenly appears to have a positive balance!

Always use PIC S9 for any variables involved in math or intermediate calculations.

Overpunching: How COBOL Saves Memory

You might think that changing PIC 99 to PIC S99 adds an extra byte to store the negative sign. But in the 1960s, memory was extremely expensive, and adding a byte to every number was unacceptable.

Instead, COBOL uses a clever trick called Overpunching. By default, COBOL combines the sign with the last digit and stores them together in a single byte!

Unsigned (PIC 99) Value: 42 4 Byte 1 2 Byte 2 Signed (PIC S99) Value: -42 4 Byte 1 r Byte 2 (Overpunch) In ASCII, 'r' or similar letters encode BOTH the digit '2' and the '-' sign! Both variables take EXACTLY 2 bytes of memory.

Let’s prove this by inspecting the memory directly. Notice that FUNCTION LENGTH returns 2 bytes for both variables, and when we redefine the signed memory as raw text, -42 looks like 4r (because the ASCII character r happens to be the bitwise combination of - and 2).

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. RAW-BYTES.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 NUM-GROUP.
           05 SIGNED-NUM PIC S99 VALUE -42.
           05 UNSIGNED-NUM PIC 99 VALUE 42.
       01 BYTE-VIEW REDEFINES NUM-GROUP.
           05 SIGNED-BYTES PIC X(2).
           05 UNSIGNED-BYTES PIC X(2).
       PROCEDURE DIVISION.
           DISPLAY 'SIGNED SIZE: ' FUNCTION LENGTH(SIGNED-NUM).
           DISPLAY 'UNSIGNED SIZE: ' FUNCTION LENGTH(UNSIGNED-NUM).
           DISPLAY 'SIGNED BYTES: ' SIGNED-BYTES.
           DISPLAY 'UNSIGNED BYTES: ' UNSIGNED-BYTES.
           STOP RUN.
Output
SIGNED SIZE: 2
UNSIGNED SIZE: 2
SIGNED BYTES: 4r
UNSIGNED BYTES: 42

SIGN IS SEPARATE

Overpunching is incredibly efficient, but it creates problems when you need to send a text file to a modern system (like a Python or Node.js server) that doesn’t understand overpunched characters like 4r.

If you need the sign to be stored as a literal - or + in its own dedicated byte, you can append SIGN IS LEADING SEPARATE to the declaration. This will increase the memory footprint by 1 byte.

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. SEPARATE-SIGN.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 NUM-GROUP.
           05 SIGNED-SEP PIC S99 SIGN IS LEADING SEPARATE VALUE -42.
       01 BYTE-VIEW REDEFINES NUM-GROUP.
           05 SEP-BYTES PIC X(3).
       PROCEDURE DIVISION.
           DISPLAY 'SEPARATE BYTES: ' SEP-BYTES.
           STOP RUN.
Output
SEPARATE BYTES: -42

Displaying Signed Numbers

Because standard PIC S9 fields use overpunching, displaying them directly will often output bizarre characters like 4r.

To print a negative number legibly for a human to read, you must move the raw S9 variable into a numeric-edited field (using the - symbol). The - symbol acts like a formatting instruction.

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. DISPLAY-FMT.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 SIGNED-NUM PIC S99 VALUE -42.
       01 DISP-NUM PIC -99.
       PROCEDURE DIVISION.
           MOVE SIGNED-NUM TO DISP-NUM.
           DISPLAY 'DISPLAY FIELD: ' DISP-NUM.
           STOP RUN.
Output
DISPLAY FIELD: -42

(Note: The internal SIGNED-NUM still only takes 2 bytes, while the human-readable DISP-NUM takes 3 bytes.)

Check yourself

Which PIC clause symbol is used to make a numeric variable signed?

Reveal answer

S — The 'S' symbol makes a field signed, while '-' is used for display formatting.

Does changing 'PIC 99' to 'PIC S99' increase the variable's memory size?

Reveal answer

No, both take exactly 2 bytes. — By default, COBOL uses overpunching to combine the sign with the last digit, so PIC S99 and PIC 99 take the exact same amount of memory.

What happens if you subtract 15 from an unsigned PIC 99 variable that holds 10?

Reveal answer

The variable silently stores 05 (the absolute value). — An unsigned variable cannot hold negative numbers. COBOL will silently store the absolute value (05) instead, leading to silent bugs.

Challenges

Challenge 1 +20 XP

This program tries to calculate your bank balance after a withdrawal, but it says you have a positive balance of 5 instead of a negative one! Fix the 'BALANCE' variable's PIC clause to allow negative numbers.

cobol ✓ solution verified at build time

COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the fixed code should be, then open the solution and compare. The expected output below is real — it came from compiling the solution with GnuCOBOL when this page was built.

  • Test 1 — expects "-005\n"
Need a hint? (−25% XP)

Add the 'S' symbol to the front of the PIC clause for BALANCE.

Show solution (0 XP)
IDENTIFICATION DIVISION.
PROGRAM-ID. OVERDRAFT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 DEPOSIT PIC 9(2) VALUE 10.
01 WITHDRAWAL PIC 9(2) VALUE 15.
01 BALANCE PIC S9(3).
01 DISPLAY-BAL PIC -999.
PROCEDURE DIVISION.
    SUBTRACT WITHDRAWAL FROM DEPOSIT GIVING BALANCE.
    MOVE BALANCE TO DISPLAY-BAL.
    DISPLAY DISPLAY-BAL.
    STOP RUN.

Challenge 2 +20 XP

You are generating a text file for an external system that cannot read overpunched letters. Change 'EXPORT-TEMP' so that its sign is explicitly stored as a separate byte at the front.

cobol ✓ solution verified at build time

COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the fixed code should be, then open the solution and compare. The expected output below is real — it came from compiling the solution with GnuCOBOL when this page was built.

  • Test 1 — expects "-15\n"
Need a hint? (−25% XP)

Add 'SIGN IS LEADING SEPARATE' to the end of the EXPORT-TEMP definition.

Show solution (0 XP)
IDENTIFICATION DIVISION.
PROGRAM-ID. FIX-EXPORT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 TEMPERATURE PIC S9(2) VALUE -15.
01 NUM-GROUP.
    05 EXPORT-TEMP PIC S9(2) SIGN IS LEADING SEPARATE.
01 BYTE-VIEW REDEFINES NUM-GROUP.
    05 EXPORT-BYTES PIC X(3).
PROCEDURE DIVISION.
    MOVE TEMPERATURE TO EXPORT-TEMP.
    DISPLAY EXPORT-BYTES.
    STOP RUN.