# COBOL numeric semantics that break naive translations

> Canonical: https://stratamend.com/cobol-numeric-semantics-that-break-naive-translations/
> Last updated: 2026-10-08

Published 14 May 2026 by Stratamend.

If you want to know where a COBOL-to-Java migration will go wrong, look at the numbers. COBOL’s numeric model was designed for business arithmetic on fixed-point decimals, and several of its rules have no direct equivalent in Java. Translations that ignore them produce code that compiles, passes a few happy-path tests and then disagrees with the old system by a cent on one record in ten thousand.

## Fixed-point, not floating-point

A field declared as `PIC S9(7)V99` holds a signed number with seven integer digits and two decimal digits. The `V` is an implied decimal point; it is not stored. Arithmetic on such fields is exact decimal arithmetic.

Translating these fields to `double` is the most common mistake. Binary floating-point cannot represent most decimal fractions exactly, so sums drift. The right target is `BigDecimal` with an explicit scale, and every operation needs a deliberate rounding mode.

## Truncation on overflow

When a result is too large for its receiving field, COBOL truncates the high-order digits silently, unless the statement has an `ON SIZE ERROR` clause. Moving 12345678.00 into a `PIC 9(7)V99` field gives 2345678.00. That is almost certainly a latent bug in the original program, but it is the behaviour other systems have seen for years. A faithful migration has to reproduce it, and a good migration also flags it for review.

## Truncation of decimals

Without the `ROUNDED` phrase, extra decimal places in a result are truncated, not rounded. `COMPUTE FEE = AMOUNT * 0.015` into a two-decimal field drops the remaining digits. With `ROUNDED`, the default rounding is half away from zero, which corresponds to `RoundingMode.HALF_UP` in Java, not to banker’s rounding.

## Intermediate precision

COBOL compilers calculate intermediate results in `COMPUTE` expressions with a precision determined by compiler rules and options, which may be more or less than the target field. Expressions that divide early can lose digits that a Java translation would keep, or the other way round. This is one of the places where golden-master tests are indispensable, because the rule depends on the compiler and its settings, not only on the source.

## Storage formats

The same `PIC` clause can be stored in different ways depending on `USAGE`:

- `DISPLAY`: one character per digit, with the sign often overpunched into the last byte;
- `COMP-3`: packed decimal, two digits per byte with a sign nibble;
- `COMP` or `BINARY`: binary integers whose range may be limited by the `PIC` digits, depending on compiler options.

When migrated code exchanges files with systems that still run on the mainframe, these layouts must be read and written exactly.

## REDEFINES

`REDEFINES` lets two fields share the same storage. A record may hold a date as eight digits in one view and as year, month and day in another, or switch layout entirely depending on a record type code. A naive object-oriented translation creates separate fields and loses the link between them. The migration needs an explicit view of the underlying bytes.

## Collating sequence

EBCDIC orders characters differently from ASCII and Unicode: in EBCDIC, lowercase letters sort before uppercase letters, and letters sort before digits. A report sorted on the mainframe and the same report sorted in Java will differ unless the comparison is made explicit.

## What we do about it

We plan to handle these rules in three ways. The Atlas flags fields and statements that use them. Agents migrate with a small, reviewed library of decimal and record-layout helpers rather than reinventing arithmetic in every module. And the golden-master inputs deliberately include values at the edges: maximum digits, negative zero, half-cent results and records of every layout variant.

