COMPUTE, ROUNDED, ON SIZE ERROR: COBOL arithmetic without overflow
One GnuCOBOL program: COMPUTE with and without ROUNDED, ROUNDED MODE IS NEAREST-EVEN on a tie, an overflow caught by ON SIZE ERROR, and a trapped divide by zero.
COBOL arithmetic is decimal, exact, and silent about overflow unless you ask. This lesson is about the asking. One program computes an invoice total with and without ROUNDED, rounds a tie two different ways with ROUNDED MODE IS, overflows a three-digit field once with ON SIZE ERROR and once without, and divides by zero without crashing. At the end you will know which of those the language protects you from by default (none of them) and what each clause costs to add (one line).
The listing is desk-checked against the GnuCOBOL 3.2 Programmer's Guide, sections 7.8.9, 7.6.6 and 7.6.7, and against IBM's pages on the ROUNDED and SIZE ERROR phrases. The expected values were first worked by hand and checked with a short Python calculation, both shown below; on 29 October 2026 we compiled and ran the program with GnuCOBOL 3.2, and it printed exactly that output. The storage rules it relies on are in the PICTURE lesson, and the truncation behaviour it contrasts with is in the MOVE lesson.
The program
>>SOURCE FORMAT IS FREE
*> cobol-compute-rounded.cob - COMPUTE with and without
*> ROUNDED, ROUNDED MODE IS NEAREST-EVEN, an overflow trapped by
*> ON SIZE ERROR, the same overflow untrapped, and a divide by zero
*> 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-29
*> Inputs : none
*> Outputs: nine lines on standard output
*> Build : cobc -x cobol-compute-rounded.cob
*> Run : ./cobol-compute-rounded
*> (Windows: cobol-compute-rounded.exe)
IDENTIFICATION DIVISION.
PROGRAM-ID. COMPUTE-DEMO.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-Price PIC 9(3)V99 VALUE 19.99.
01 WS-Qty PIC 9(3) VALUE 3.
01 WS-Tax-Rate PIC V999 VALUE 0.075.
01 WS-Total-Trunc PIC 9(5)V99.
01 WS-Total-Round PIC 9(5)V99.
01 WS-In-A PIC 9V999 VALUE 2.345.
01 WS-In-B PIC 9V999 VALUE 2.355.
01 WS-Out-Default PIC 9V99.
01 WS-Out-Even PIC 9V99.
01 WS-Small PIC 9(3) VALUE 5.
01 WS-Big PIC 9(5) VALUE 12345.
01 WS-Divisor PIC 9 VALUE 0.
01 WS-Result PIC 9(5)V99 VALUE 0.
PROCEDURE DIVISION.
MAIN-PARAGRAPH.
*> 1. The same expression, truncated and rounded.
COMPUTE WS-Total-Trunc = WS-Price * WS-Qty * (1 + WS-Tax-Rate)
COMPUTE WS-Total-Round ROUNDED
= WS-Price * WS-Qty * (1 + WS-Tax-Rate)
DISPLAY "Total, no ROUNDED : " WS-Total-Trunc
DISPLAY "Total, ROUNDED : " WS-Total-Round
*> 2. Rounding modes on a tie: 2.345 and 2.355 into two decimals.
COMPUTE WS-Out-Default ROUNDED = WS-In-A
COMPUTE WS-Out-Even ROUNDED MODE IS NEAREST-EVEN = WS-In-A
DISPLAY "2.345 default mode : " WS-Out-Default
DISPLAY "2.345 NEAREST-EVEN : " WS-Out-Even
COMPUTE WS-Out-Default ROUNDED = WS-In-B
COMPUTE WS-Out-Even ROUNDED MODE IS NEAREST-EVEN = WS-In-B
DISPLAY "2.355 default mode : " WS-Out-Default
DISPLAY "2.355 NEAREST-EVEN : " WS-Out-Even
*> 3. Overflow trapped: the receiver keeps its old value.
COMPUTE WS-Small = WS-Big
ON SIZE ERROR
DISPLAY "Size error trapped, WS-Small still " WS-Small
NOT ON SIZE ERROR
DISPLAY "No size error, WS-Small now " WS-Small
END-COMPUTE
*> 4. The same overflow untrapped: leading digits vanish silently.
COMPUTE WS-Small = WS-Big
DISPLAY "Untrapped overflow, WS-Small now " WS-Small
*> 5. Division by zero, trapped instead of aborting the run.
COMPUTE WS-Result = WS-Price / WS-Divisor
ON SIZE ERROR
DISPLAY "Divide by zero trapped, WS-Result still " WS-Result
END-COMPUTE
STOP RUN.
COMPUTE and the intermediate result
COMPUTE evaluates an arithmetic expression, with the usual operators and parentheses plus ** for exponentiation, and stores the result in one or more receiving items. The Programmer's Guide describes it as the alternative to chains of ADD, SUBTRACT, MULTIPLY and DIVIDE, and for anything beyond a single operation it is also clearer. The expression is evaluated in an intermediate result with enough decimal places to be exact for these operands, and only the final store into the receiver truncates or rounds. That last step is where the clauses in this lesson apply.
Step 1 computes 19.99 * 3 * 1.075. The exact value is 64.46775. Stored into PIC 9(5)V99 without ROUNDED, the digits beyond two decimals are simply dropped, giving 64.46. The guide's rule is blunt: absence of a ROUNDED clause "is the same as specifying ROUNDED MODE IS TRUNCATION". With ROUNDED, the result is 64.47. IBM's page on the phrase states the classic rule: the least significant digit of the receiver "is increased by 1 whenever the most significant digit of the excess is greater than or equal to 5".
Eight rounding modes
The 2002 standard added ROUNDED MODE IS, and GnuCOBOL implements all eight modes; the guide lists them with worked examples. IBM's ROUNDED page for Enterprise COBOL 6.4 documents only the classic round-half-away behaviour and says nothing of MODE IS, so treat the modes as portable to standard-conforming compilers and check before relying on them elsewhere. The table condenses the guide's examples for an integer receiver.
| Mode | Rule (Programmer's Guide 7.6.7) | +2.5 | -2.5 | +3.5 |
|---|---|---|---|---|
AWAY-FROM-ZERO |
nearest value of larger magnitude | 3 | -3 | 4 |
NEAREST-AWAY-FROM-ZERO |
nearest; ties go to larger magnitude (the default for ROUNDED) |
3 | -3 | 4 |
NEAREST-EVEN |
nearest; ties go to the even digit ("banker's rounding") | 2 | -2 | 4 |
NEAREST-TOWARD-ZERO |
nearest; ties go to smaller magnitude | 2 | -2 | 3 |
PROHIBITED |
no rounding allowed; sets the EC-SIZE-TRUNCATION exception, result undefined | undefined | undefined | undefined |
TOWARD-GREATER |
toward the algebraically larger value | 3 | -2 | 4 |
TOWARD-LESSER |
toward the algebraically smaller value | 2 | -3 | 3 |
TRUNCATION |
toward zero (the default when ROUNDED is absent) |
2 | -2 | 3 |
Step 2 puts the two modes that matter for money side by side. ROUNDED on its own means NEAREST-AWAY-FROM-ZERO, so 2.345 becomes 2.35 and 2.355 becomes 2.36. NEAREST-EVEN sends a tie to whichever neighbour ends in an even digit: 2.345 becomes 2.34 (4 is even) and 2.355 becomes 2.36 (6 is even). Over thousands of line items the two policies drift apart by a few cents, which is exactly why a ledger specification names its rounding rule and why the mode clause is worth knowing.
ON SIZE ERROR
A size error, in the guide's words, is either "a result too large for the receiving field" or an attempt to divide by zero. IBM's page adds the precise definition: the absolute value of the result, after decimal-point alignment, exceeds the largest value the receiver can hold. Steps 3 and 4 move the five-digit value 12345 into the three-digit WS-Small twice. With ON SIZE ERROR, the guide and IBM agree on the outcome: the receiver is not changed, and the imperative statement after the clause runs. So WS-Small still holds 5, and the NOT ON SIZE ERROR branch is skipped. Without the clause, IBM says "truncation rules apply and the value of the affected resultant identifier is computed", which means the high-order digits are lost and WS-Small becomes 345. The guide's own words for the untrapped case are that overflow conditions "occur silently, usually without any runtime messages".
Step 5 divides by a zero divisor. The guide is specific about the difference from overflow: without ON SIZE ERROR, a division by zero prints a runtime error message on standard error and aborts the program. With the clause, the division is trapped, the receiver is untouched, and the run continues. IBM's rule is the same, stated as "the program is terminated abnormally" when the clause is absent. If a batch job has ever died halfway through a file with a divide-by-zero message, this one clause is the fix.
Compile and run
cobc -x cobol-compute-rounded.cob
./cobol-compute-rounded
Worked example
The output, derived by hand from the rules above and identical to what the compiled program printed (with GnuCOBOL's default pretty-display setting inserting the decimal point in numeric displays):
Total, no ROUNDED : 00064.46
Total, ROUNDED : 00064.47
2.345 default mode : 2.35
2.345 NEAREST-EVEN : 2.34
2.355 default mode : 2.36
2.355 NEAREST-EVEN : 2.36
Size error trapped, WS-Small still 005
Untrapped overflow, WS-Small now 345
Divide by zero trapped, WS-Result still 00000.00
Two lines that could have appeared do not: the NOT ON SIZE ERROR branch never runs, and no runtime message is printed, because the divide is trapped. We checked the arithmetic of steps 1 and 2 in Python with exact decimals, which we did run:
from decimal import Decimal, ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN
total = Decimal("19.99") * 3 * (1 + Decimal("0.075")) # 64.46775
print(total.quantize(Decimal("0.01"), ROUND_DOWN)) # 64.46
print(total.quantize(Decimal("0.01"), ROUND_HALF_UP)) # 64.47
for v in ("2.345", "2.355"):
d = Decimal(v)
print(v, d.quantize(Decimal("0.01"), ROUND_HALF_UP),
d.quantize(Decimal("0.01"), ROUND_HALF_EVEN)) # 2.35 2.34 / 2.36 2.36
Python's ROUND_HALF_UP is the same policy as COBOL's NEAREST-AWAY-FROM-ZERO for positive values, and ROUND_HALF_EVEN matches NEAREST-EVEN.
Eight modes, one tie table, compiled
Step 2 tests two modes on two values. To see all eight at once, a second program, cobol-rounded-mode-ties.cob, stores twelve values into a PIC S9V99 receiver under every ROUNDED MODE, plus plain ROUNDED and no ROUNDED at all. The values are four exact ties (plus and minus 2.345 and 2.355), four near-ties one ten-thousandth either side of a tie (plus and minus 2.3449 and 2.3451), a tie at the smallest scale (plus and minus 0.005), a value that needs no rounding (2.34, held as 2.3400 in a four-decimal item), and a tie that rounds past the receiver's capacity (9.995). Every statement has ON SIZE ERROR, and a cell reads SIZE when that branch ran; the receiver is reset to zero before each statement.
On 30 October 2026 we compiled and ran it with GnuCOBOL 3.2 (cobc (GnuCOBOL) 3.2.0, the MSYS2 MinGW-w64 build on Windows) three times: cobc -x, cobc -x -std=ibm and cobc -x -std=mf. The three runs printed the same bytes, so this one block is the output of all three:
value none ROUND AWAY N-AWAY N-EVEN N-TOW PROHIB T-GRT T-LSR TRUNC
2.3450 2.34 2.35 2.35 2.35 2.34 2.34 SIZE 2.35 2.34 2.34
2.3550 2.35 2.36 2.36 2.36 2.36 2.35 SIZE 2.36 2.35 2.35
-2.3450 -2.34 -2.35 -2.35 -2.35 -2.34 -2.34 SIZE -2.34 -2.35 -2.34
-2.3550 -2.35 -2.36 -2.36 -2.36 -2.36 -2.35 SIZE -2.35 -2.36 -2.35
2.3449 2.34 2.34 2.35 2.34 2.34 2.34 SIZE 2.35 2.34 2.34
2.3451 2.34 2.35 2.35 2.35 2.35 2.35 SIZE 2.35 2.34 2.34
-2.3449 -2.34 -2.34 -2.35 -2.34 -2.34 -2.34 SIZE -2.34 -2.35 -2.34
-2.3451 -2.34 -2.35 -2.35 -2.35 -2.35 -2.35 SIZE -2.34 -2.35 -2.34
0.0050 0.00 0.01 0.01 0.01 0.00 0.00 SIZE 0.01 0.00 0.00
-0.0050 0.00 -0.01 -0.01 -0.01 0.00 0.00 SIZE 0.00 -0.01 0.00
2.3400 2.34 2.34 2.34 2.34 2.34 2.34 SIZE 2.34 2.34 2.34
9.9950 9.99 SIZE SIZE SIZE SIZE 9.99 SIZE SIZE 9.99 9.99
PROHIBITED, 2.34 from S9V99 : 2.34
PROHIBITED, 2.3400, no clause : 0.00
The column headings are short for: no ROUNDED; ROUNDED with no mode; AWAY-FROM-ZERO; NEAREST-AWAY-FROM-ZERO; NEAREST-EVEN; NEAREST-TOWARD-ZERO; PROHIBITED; TOWARD-GREATER; TOWARD-LESSER; TRUNCATION. What the table shows:
- The three nearest modes split only on exact ties. On 2.345 they give 2.35, 2.34 and 2.34; on 2.355 they give 2.36, 2.36 and 2.35.
NEAREST-EVENsides with one neighbour on the first tie and the other on the second, which is why it does not drift over many ties. On the near-ties (2.3449 and 2.3451) all three agree, because the tie rule never fires. AWAY-FROM-ZEROis not a nearest mode. It takes 2.3449 to 2.35: any inexact value moves outward, however small the excess.- Only the two
TOWARDmodes care about the sign.TOWARD-GREATERtakes 2.345 to 2.35 and minus 2.345 to minus 2.34;TOWARD-LESSERdoes the reverse. Every other column is the same with the sign flipped. - Plain
ROUNDEDisNEAREST-AWAY-FROM-ZERO, column for column, and noROUNDEDisTRUNCATION, as the guide says. - Rounding alone can cause a size error. 9.995 fits the receiver's one integer digit, but every mode that rounds it up needs 10.00, so the
ON SIZE ERRORbranch ran. The modes that round it down stored 9.99. PROHIBITEDtook the size-error branch on every row, including 2.3400, whose two dropped digits are zeros. The two lines after the table show what decided it in this run: the same 2.34 from an item declared with two decimal places stored without complaint, and with noON SIZE ERRORphrase the receiver was left as it was (0.00) and the run went on with no message. So in GnuCOBOL 3.2 aPROHIBITEDstore from a source with more decimal places than the receiver is a size error whatever the digits are; code the phrase whenever you use the mode.
We also computed the 108 cells outside the PROHIBITED column with Python's decimal module (ROUND_DOWN for no ROUNDED and TRUNCATION, ROUND_HALF_UP for ROUNDED and NEAREST-AWAY-FROM-ZERO, ROUND_UP for AWAY-FROM-ZERO, ROUND_HALF_EVEN, ROUND_HALF_DOWN for NEAREST-TOWARD-ZERO, ROUND_CEILING and ROUND_FLOOR for the two TOWARD modes, and a size check against 9.99). All 108 agree with the compiled output.
The lesson's own program behaves the same way across dialects, apart from how it prints. Under -std=mf it printed the worked example above byte for byte; under -std=ibm, whose dialect file turns pretty-display off, the same values came out without decimal points:
Total, no ROUNDED : 0006446
Total, ROUNDED : 0006447
2.345 default mode : 235
2.345 NEAREST-EVEN : 234
2.355 default mode : 236
2.355 NEAREST-EVEN : 236
Size error trapped, WS-Small still 005
Untrapped overflow, WS-Small now 345
Divide by zero trapped, WS-Result still 0000000
-std=cobol2014 refused both programs at their first NEAREST-EVEN statement, with code/cobol-rounded-mode-ties.cob:87: error: syntax error, unexpected Identifier for the tie table and code/cobol-compute-rounded.cob:48: error: syntax error, unexpected Identifier for this lesson's listing. The 3.2 word list for that dialect, cobol2014.words, reserves NEAREST-AWAY-FROM-ZERO, NEAREST-TOWARD-ZERO and NEAREST-EVEN-INTERMEDIATE but has no NEAREST-EVEN entry, so in that dialect the word is read as a data name.
Limitations
The programs were run on GnuCOBOL 3.2 only, under its default, -std=ibm, -std=mf and -std=cobol2014 dialects; IBM Enterprise COBOL itself was not run, and its ROUNDED page documents no MODE phrase at all. The untrapped overflow in step 4 relies on standard truncation for a USAGE DISPLAY receiver; for binary receivers GnuCOBOL's binary-truncate configuration option decides whether truncation follows the picture or the binary field size, and the result can differ. The mode table reproduces the guide's examples for an integer receiver; the tie table above exercises every mode, PROHIBITED, TOWARD-GREATER and TOWARD-LESSER included, on a two-decimal receiver only. ON SIZE ERROR is available on ADD, SUBTRACT, MULTIPLY and DIVIDE as well as COMPUTE, but not on MOVE, so the silent truncation described in the MOVE lesson has no equivalent guard. Finally, ROUNDED applies once, on the store; it does not change how the intermediate expression is evaluated, and the number of decimal places kept in that intermediate is a compiler matter that neither guide pins down for every case.
Correction, 29 October 2026: the build command shown before named the source cobol-compute-rounded-on-size-error.cob, and GnuCOBOL 3.2's cobc refuses a source file whose base name is longer than 31 characters ("invalid file base name ... length exceeds maximum"), even with -o, so it failed as printed. The listing is now shipped as cobol-compute-rounded.cob, and the old download address redirects there. The same day we compiled and ran the program with GnuCOBOL 3.2, and its output matched the output shown here line for line, so the page now says that instead of calling the listing desk-checked only.
Update, 30 October 2026: added the compiled tie table for all eight rounding modes, the -std=ibm, -std=mf and -std=cobol2014 results, and the finding on PROHIBITED; the limitations above now say what was and was not run.
Index terms cobol, gnucobol, compute, rounded, on size error