Packed-Decimal COBOL: Exact Money Math Without Floating-Point Drift
Use COBOL USAGE COMP-3 to store currency as packed decimal and avoid binary floating-point rounding in ledgers and interest calculations.
07 Aug 2026, 18:39 UTC

The problem: cents that disappear after midnight
A nightly interest batch reconciles to the penny on day one, but after three hundred iterations the ledger is off by several cents. The formula is correct. The culprit is usually binary floating-point arithmetic: in IEEE 754, 0.1 plus 0.2 does not equal 0.30 exactly. For balances, interest accruals, and regulatory reporting, that drift is unacceptable.
Takeaway: declare monetary fields with USAGE COMP-3, also called packed-decimal. It stores each decimal digit in four bits, with a sign nibble in the final byte, so arithmetic stays in base ten and rounding is explicit rather than accidental.
How COMP-3 stores a number
A PICTURE clause declares precision and scale. For example, PIC S9(7)V99 COMP-3 defines a signed field with seven digits before an implied decimal point and two after it. The V is not stored; it tells the compiler where the decimal point sits. The S requests a sign, which COBOL encodes in the last nibble of the packed field.
Packing stores two digits per byte, so a seven-digit field needs roughly four bytes. The compiler enforces alignment and truncation rules, making overflow behavior predictable and auditable. Compiler defaults vary: IBM Enterprise COBOL typically defaults to ARITH(COMPAT) with a 31-digit maximum, while Micro Focus Visual COBOL often defaults to ARITH(EXTEND) with 38 digits. Specify the mode explicitly in your CBL options or compiler flags so the program behaves the same when it moves between environments.
Controlling rounding and overflow
COBOL arithmetic verbs—ADD, SUBTRACT, MULTIPLY, DIVIDE, and COMPUTE—support ROUNDED and ON SIZE ERROR. Use ROUNDED when you want the result rounded to the field’s scale instead of truncated. Use ON SIZE ERROR to branch when a result exceeds the declared PICTURE, rather than letting high-order digits vanish silently.
Mixing packed-decimal with binary (COMP-5) or floating-point (COMP-1) triggers implicit conversion. If the compatibility mode is not set correctly, that conversion can silently truncate fractional digits. Treat mixed-type expressions as a code-review trigger.
Worked example: adding two monetary amounts
Run this on a system with a COBOL compiler installed—GnuCOBOL on Linux, IBM Enterprise COBOL on z/OS, or Micro Focus Visual COBOL on Windows. You need read/write permission for the source directory and execute permission for the compiled program.
IDENTIFICATION DIVISION.
PROGRAM-ID. MoneyAdd.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 Amount-A PIC S9(5)V99 COMP-3 VALUE 0.10.
01 Amount-B PIC S9(5)V99 COMP-3 VALUE 0.20.
01 Total PIC S9(5)V99 COMP-3.
PROCEDURE DIVISION.
COMPUTE Total = Amount-A + Amount-B
ON SIZE ERROR DISPLAY "Result too large"
END-COMPUTE.
DISPLAY Total.
STOP RUN.
Compile with an explicit arithmetic mode. On Linux with GnuCOBOL, a command might look like:
cobc -x -std=default moneyadd.cob
./moneyadd
Replace moneyadd.cob with your source filename. The DISPLAY statement should emit 0.30, not 0.30000000000000004. If you change Total to USAGE COMP-1 (binary floating-point) and rerun, you will likely see binary representation noise instead.
Expected checks: the program returns zero exit status, prints exactly two decimal places, and never emits the ON SIZE ERROR message for these values. Risk: do not overlay this field with REDEFINES as alphanumeric text; the sign-nibble encoding differs between EBCDIC and ASCII platforms, making the overlay non-portable.
Trade-offs and how to verify
Packed-decimal costs more storage than binary integers—roughly n/2 + 1 bytes for n digits—and it does not vectorize like SIMD-friendly binary math. For most ledger workloads that is an acceptable trade, but high-throughput batch jobs on x86 without hardware decimal assistance may run slower than equivalent database-side math.
Practical verification steps:
- Compile with the arithmetic mode you intend to ship, then inspect the compiler listing for decimal instructions where applicable.
- Dump storage with a hex tool or
HEX OFin COBOL to confirm the sign nibble and digit packing match yourPICTUREclause. - Run the same calculation in Python using
decimal.Decimalwith a fixed precision, and compare cent-level results over hundreds of periods. - If the field maps to a database column, confirm the host variable matches
DECIMAL(11,2)or equivalent through DCLGEN or SQL precompile output.
Closing
Audit your numeric working-storage fields. Any field that holds currency, tax, or interest should probably carry USAGE COMP-3 with an explicit ARITH setting and ON SIZE ERROR handling. Avoid REDEFINES on packed fields, verify storage with a hex dump, and compare against a decimal reference implementation before promoting the code to production.