COBOL numeric semantics that break naive translations
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;COMPorBINARY: binary integers whose range may be limited by thePICdigits, 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.