Case Study 1: Mortgage Payment Calculator
Background
Heartland Federal Credit Union (HFCU) serves approximately 185,000 members across seven branch locations in the upper Midwest. The credit union originates approximately 3,200 mortgage loans per year, ranging from modest first-time buyer homes at $120,000 to rural properties exceeding $750,000. HFCU offers both fixed-rate and adjustable-rate mortgages, and its loan officers rely on an IBM z/OS mainframe system to generate accurate payment calculations and amortization schedules at the point of sale.
The mortgage payment calculator is one of the most frequently executed programs in the credit union's COBOL application suite. Loan officers invoke it dozens of times per day during member consultations, and the nightly batch cycle uses the same calculation engine to generate disclosure documents for new loan applications. Accuracy is paramount: the Truth in Lending Act (Regulation Z) requires that disclosed payment amounts match actual billing amounts to the penny. A single rounding error that produces a payment amount even one cent different from the disclosed amount can trigger regulatory findings during NCUA examinations.
This case study presents a complete COBOL mortgage payment calculator that handles both fixed-rate and adjustable-rate mortgages, generates monthly amortization schedules with principal and interest splits, and demonstrates the proper use of COMP-3 arithmetic, ROUNDED, and ON SIZE ERROR for financial precision.
Business Requirements
The mortgage calculator must satisfy the following requirements:
-
Fixed-rate mortgage payment calculation: Given a loan amount, annual interest rate, and term in months, compute the exact monthly payment using the standard amortization formula.
-
Adjustable-rate mortgage (ARM) support: For 5/1 ARMs, calculate the initial payment at the fixed introductory rate, then recalculate the payment when the rate adjusts after the initial fixed period.
-
Amortization schedule generation: For each payment in the schedule, compute the interest portion, the principal portion, and the remaining balance after the payment. Accumulate totals for interest paid and principal paid.
-
Final payment adjustment: The last payment must be adjusted so the remaining balance reaches exactly zero. Due to rounding in each monthly calculation, the final payment is almost never equal to the regular monthly payment.
-
Penny-exact arithmetic: All monetary calculations must use packed decimal (COMP-3) fields with the ROUNDED phrase. Intermediate rate calculations must carry at least 10 decimal places.
The Amortization Formula
The standard fixed-rate mortgage payment formula is:
M = P * [r(1 + r)^n] / [(1 + r)^n - 1]
Where: - M = monthly payment - P = loan principal (amount borrowed) - r = monthly interest rate (annual rate / 12) - n = total number of monthly payments
Each monthly payment is then split into interest and principal components: - Interest portion = Remaining Balance * Monthly Rate - Principal portion = Monthly Payment - Interest Portion - New Balance = Previous Balance - Principal Portion
Complete COBOL Program
IDENTIFICATION DIVISION.
PROGRAM-ID. MORTCALC.
AUTHOR. HFCU MORTGAGE SYSTEMS.
DATE-WRITTEN. 2024-03-15.
*================================================================*
* PROGRAM: MORTCALC - MORTGAGE PAYMENT CALCULATOR *
* *
* PURPOSE: Calculate monthly mortgage payments for fixed-rate *
* and adjustable-rate mortgages. Generate amortization *
* schedules showing principal/interest split per month. *
* *
* PROCESSING: *
* 1. Read mortgage parameters from input file *
* 2. Calculate monthly payment using amortization formula *
* 3. Generate month-by-month amortization schedule *
* 4. Handle ARM rate adjustments at specified intervals *
* 5. Adjust final payment for exact zero balance *
* 6. Produce formatted report with summary totals *
* *
* INPUT: MORTPARM - Mortgage parameter file *
* OUTPUT: MORTRPT - Amortization schedule report *
*================================================================*
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
FUNCTION ALL INTRINSIC.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MORTGAGE-INPUT
ASSIGN TO MORTPARM
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-INPUT-STATUS.
SELECT MORTGAGE-REPORT
ASSIGN TO MORTRPT
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-REPORT-STATUS.
DATA DIVISION.
FILE SECTION.
FD MORTGAGE-INPUT
RECORDING MODE IS F
RECORD CONTAINS 80 CHARACTERS.
01 MORTGAGE-INPUT-REC.
05 MI-LOAN-NUMBER PIC X(10).
05 MI-BORROWER-NAME PIC X(25).
05 MI-LOAN-AMOUNT PIC 9(07)V99.
05 MI-ANNUAL-RATE PIC 99V9(4).
05 MI-TERM-MONTHS PIC 9(03).
05 MI-LOAN-TYPE PIC X(01).
88 MI-FIXED-RATE VALUE 'F'.
88 MI-ARM-LOAN VALUE 'A'.
05 MI-ARM-FIXED-MONTHS PIC 9(03).
05 MI-ARM-ADJ-RATE PIC 99V9(4).
05 MI-ARM-RATE-CAP PIC 99V9(4).
05 FILLER PIC X(12).
FD MORTGAGE-REPORT
RECORDING MODE IS F
RECORD CONTAINS 132 CHARACTERS.
01 REPORT-LINE PIC X(132).
WORKING-STORAGE SECTION.
*----------------------------------------------------------------*
* FILE STATUS AND CONTROL FLAGS *
*----------------------------------------------------------------*
01 WS-FILE-STATUS.
05 WS-INPUT-STATUS PIC X(02).
05 WS-REPORT-STATUS PIC X(02).
01 WS-FLAGS.
05 WS-EOF-FLAG PIC X(01) VALUE 'N'.
88 END-OF-FILE VALUE 'Y'.
88 NOT-END-OF-FILE VALUE 'N'.
05 WS-ERROR-FLAG PIC X(01) VALUE 'N'.
88 CALC-ERROR VALUE 'Y'.
88 NO-CALC-ERROR VALUE 'N'.
*----------------------------------------------------------------*
* MORTGAGE CALCULATION FIELDS - ALL COMP-3 FOR PRECISION *
* These fields carry extended decimal precision for *
* intermediate calculations to prevent cumulative rounding *
* errors across hundreds of monthly payment calculations. *
*----------------------------------------------------------------*
01 WS-MORTGAGE-FIELDS.
05 WS-LOAN-AMOUNT PIC S9(09)V99 COMP-3.
05 WS-ANNUAL-RATE PIC S9(02)V9(06) COMP-3.
05 WS-MONTHLY-RATE PIC S9(02)V9(10) COMP-3.
05 WS-TERM-MONTHS PIC S9(04) COMP-3.
05 WS-REMAINING-MONTHS PIC S9(04) COMP-3.
05 WS-MONTHLY-PAYMENT PIC S9(07)V99 COMP-3.
05 WS-CURRENT-BALANCE PIC S9(09)V99 COMP-3.
05 WS-INTEREST-PORTION PIC S9(07)V99 COMP-3.
05 WS-PRINCIPAL-PORTION PIC S9(07)V99 COMP-3.
*----------------------------------------------------------------*
* INTERMEDIATE CALCULATION FIELDS WITH EXTENDED PRECISION *
*----------------------------------------------------------------*
01 WS-CALC-WORK.
05 WS-POWER-FACTOR PIC S9(05)V9(12) COMP-3.
05 WS-NUMERATOR PIC S9(15)V9(10) COMP-3.
05 WS-DENOMINATOR PIC S9(15)V9(10) COMP-3.
05 WS-GROWTH-FACTOR PIC S9(03)V9(12) COMP-3.
05 WS-PERIOD-CTR PIC S9(04) COMP-3.
*----------------------------------------------------------------*
* ARM (ADJUSTABLE RATE MORTGAGE) FIELDS *
*----------------------------------------------------------------*
01 WS-ARM-FIELDS.
05 WS-ARM-FIXED-MONTHS PIC S9(04) COMP-3.
05 WS-ARM-ADJ-RATE PIC S9(02)V9(06) COMP-3.
05 WS-ARM-RATE-CAP PIC S9(02)V9(06) COMP-3.
05 WS-ARM-NEW-RATE PIC S9(02)V9(06) COMP-3.
05 WS-ARM-ADJUSTED PIC X(01) VALUE 'N'.
88 ARM-RATE-ADJUSTED VALUE 'Y'.
88 ARM-NOT-ADJUSTED VALUE 'N'.
*----------------------------------------------------------------*
* CUMULATIVE TRACKING FIELDS *
*----------------------------------------------------------------*
01 WS-TOTALS.
05 WS-TOTAL-INTEREST PIC S9(09)V99 COMP-3
VALUE ZEROS.
05 WS-TOTAL-PRINCIPAL PIC S9(09)V99 COMP-3
VALUE ZEROS.
05 WS-TOTAL-PAYMENTS PIC S9(11)V99 COMP-3
VALUE ZEROS.
05 WS-PAYMENT-COUNT PIC S9(04) COMP-3
VALUE ZEROS.
*----------------------------------------------------------------*
* LOOP AND CONTROL FIELDS *
*----------------------------------------------------------------*
01 WS-CONTROLS.
05 WS-CURRENT-MONTH PIC 9(04).
05 WS-LOANS-PROCESSED PIC 9(04) VALUE ZEROS.
05 WS-LINE-COUNT PIC 9(03) VALUE 99.
05 WS-PAGE-NUMBER PIC 9(03) VALUE 0.
05 WS-LINES-PER-PAGE PIC 9(03) VALUE 55.
*----------------------------------------------------------------*
* REPORT FORMAT LINES *
*----------------------------------------------------------------*
01 WS-RPT-HEADER-1.
05 FILLER PIC X(01) VALUE SPACE.
05 FILLER PIC X(50)
VALUE 'HEARTLAND FEDERAL CREDIT UNION'.
05 FILLER PIC X(50)
VALUE 'MORTGAGE AMORTIZATION SCHEDULE'.
05 FILLER PIC X(20) VALUE SPACES.
05 FILLER PIC X(06) VALUE 'PAGE: '.
05 WH1-PAGE PIC ZZ9.
05 FILLER PIC X(02) VALUE SPACES.
01 WS-RPT-HEADER-2.
05 FILLER PIC X(132) VALUE ALL '='.
01 WS-RPT-LOAN-LINE-1.
05 FILLER PIC X(01) VALUE SPACE.
05 FILLER PIC X(14) VALUE 'LOAN NUMBER: '.
05 WLL1-LOAN-NUM PIC X(10).
05 FILLER PIC X(05) VALUE SPACES.
05 FILLER PIC X(11) VALUE 'BORROWER: '.
05 WLL1-BORROWER PIC X(25).
05 FILLER PIC X(05) VALUE SPACES.
05 FILLER PIC X(06) VALUE 'TYPE: '.
05 WLL1-LOAN-TYPE PIC X(15).
05 FILLER PIC X(40) VALUE SPACES.
01 WS-RPT-LOAN-LINE-2.
05 FILLER PIC X(01) VALUE SPACE.
05 FILLER PIC X(16) VALUE 'LOAN AMOUNT: $'.
05 WLL2-AMOUNT PIC Z,ZZZ,ZZ9.99.
05 FILLER PIC X(05) VALUE SPACES.
05 FILLER PIC X(14) VALUE 'ANNUAL RATE: '.
05 WLL2-RATE PIC Z9.9999.
05 FILLER PIC X(01) VALUE '%'.
05 FILLER PIC X(05) VALUE SPACES.
05 FILLER PIC X(08) VALUE 'TERM: '.
05 WLL2-TERM PIC ZZ9.
05 FILLER PIC X(07) VALUE ' MONTHS'.
05 FILLER PIC X(05) VALUE SPACES.
05 FILLER PIC X(12) VALUE 'PAYMENT: $'.
05 WLL2-PAYMENT PIC ZZ,ZZ9.99.
05 FILLER PIC X(22) VALUE SPACES.
01 WS-RPT-COL-HDR.
05 FILLER PIC X(01) VALUE SPACE.
05 FILLER PIC X(08) VALUE 'PAYMENT '.
05 FILLER PIC X(18) VALUE ' PAYMENT '.
05 FILLER PIC X(18) VALUE ' INTEREST '.
05 FILLER PIC X(18) VALUE ' PRINCIPAL '.
05 FILLER PIC X(22) VALUE ' BALANCE '.
05 FILLER PIC X(22) VALUE ' CUM INTEREST '.
05 FILLER PIC X(25) VALUE SPACES.
01 WS-RPT-COL-DASH.
05 FILLER PIC X(01) VALUE SPACE.
05 FILLER PIC X(08) VALUE '--------'.
05 FILLER PIC X(18) VALUE '------------------'.
05 FILLER PIC X(18) VALUE '------------------'.
05 FILLER PIC X(18) VALUE '------------------'.
05 FILLER PIC X(22) VALUE '----------------------'.
05 FILLER PIC X(22) VALUE '----------------------'.
05 FILLER PIC X(25) VALUE SPACES.
01 WS-RPT-DETAIL.
05 FILLER PIC X(01) VALUE SPACE.
05 WD-PMT-NUM PIC ZZZ9.
05 FILLER PIC X(04) VALUE SPACES.
05 WD-PMT-AMOUNT PIC $ZZZ,ZZ9.99.
05 FILLER PIC X(04) VALUE SPACES.
05 WD-INTEREST PIC $ZZZ,ZZ9.99.
05 FILLER PIC X(04) VALUE SPACES.
05 WD-PRINCIPAL PIC $ZZZ,ZZ9.99.
05 FILLER PIC X(04) VALUE SPACES.
05 WD-BALANCE PIC $Z,ZZZ,ZZ9.99.
05 FILLER PIC X(04) VALUE SPACES.
05 WD-CUM-INT PIC $Z,ZZZ,ZZ9.99.
05 FILLER PIC X(25) VALUE SPACES.
01 WS-RPT-ARM-NOTICE.
05 FILLER PIC X(01) VALUE SPACE.
05 FILLER PIC X(40)
VALUE '*** RATE ADJUSTMENT: NEW RATE = '.
05 WARN-NEW-RATE PIC Z9.9999.
05 FILLER PIC X(20)
VALUE '%, NEW PAYMENT = $'.
05 WARN-NEW-PMT PIC ZZ,ZZ9.99.
05 FILLER PIC X(05) VALUE ' ***'.
05 FILLER PIC X(42) VALUE SPACES.
01 WS-RPT-SUMMARY.
05 FILLER PIC X(132) VALUE ALL '-'.
01 WS-RPT-SUM-LINE-1.
05 FILLER PIC X(01) VALUE SPACE.
05 FILLER PIC X(28)
VALUE 'TOTAL PRINCIPAL PAID: $'.
05 WSUM-PRINCIPAL PIC Z,ZZZ,ZZ9.99.
05 FILLER PIC X(10) VALUE SPACES.
05 FILLER PIC X(28)
VALUE 'TOTAL INTEREST PAID: $'.
05 WSUM-INTEREST PIC Z,ZZZ,ZZ9.99.
05 FILLER PIC X(31) VALUE SPACES.
01 WS-RPT-SUM-LINE-2.
05 FILLER PIC X(01) VALUE SPACE.
05 FILLER PIC X(28)
VALUE 'TOTAL ALL PAYMENTS: $'.
05 WSUM-ALL-PMTS PIC Z,ZZZ,ZZ9.99.
05 FILLER PIC X(10) VALUE SPACES.
05 FILLER PIC X(28)
VALUE 'NUMBER OF PAYMENTS: '.
05 WSUM-PMT-COUNT PIC ZZ,ZZ9.
05 FILLER PIC X(38) VALUE SPACES.
01 WS-BLANK-LINE PIC X(132) VALUE SPACES.
PROCEDURE DIVISION.
*================================================================*
* MAIN CONTROL PARAGRAPH *
*================================================================*
0000-MAIN-CONTROL.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-MORTGAGES
UNTIL END-OF-FILE
PERFORM 9000-FINALIZE
STOP RUN
.
*================================================================*
* INITIALIZE - OPEN FILES AND READ FIRST RECORD *
*================================================================*
1000-INITIALIZE.
OPEN INPUT MORTGAGE-INPUT
OPEN OUTPUT MORTGAGE-REPORT
IF WS-INPUT-STATUS NOT = '00'
DISPLAY 'ERROR OPENING INPUT FILE: '
WS-INPUT-STATUS
SET END-OF-FILE TO TRUE
END-IF
IF WS-REPORT-STATUS NOT = '00'
DISPLAY 'ERROR OPENING REPORT FILE: '
WS-REPORT-STATUS
SET END-OF-FILE TO TRUE
END-IF
PERFORM 8000-READ-INPUT
.
*================================================================*
* PROCESS EACH MORTGAGE RECORD *
*================================================================*
2000-PROCESS-MORTGAGES.
ADD 1 TO WS-LOANS-PROCESSED
* Reset totals for each new loan
MOVE ZEROS TO WS-TOTAL-INTEREST
MOVE ZEROS TO WS-TOTAL-PRINCIPAL
MOVE ZEROS TO WS-TOTAL-PAYMENTS
MOVE ZEROS TO WS-PAYMENT-COUNT
SET NO-CALC-ERROR TO TRUE
SET ARM-NOT-ADJUSTED TO TRUE
* Load parameters and calculate payment
PERFORM 3000-LOAD-PARAMETERS
PERFORM 4000-CALCULATE-PAYMENT
PERFORM 5000-WRITE-LOAN-HEADER
PERFORM 6000-GENERATE-SCHEDULE
PERFORM 7000-WRITE-SUMMARY
PERFORM 8000-READ-INPUT
.
*================================================================*
* LOAD MORTGAGE PARAMETERS FROM INPUT RECORD *
*================================================================*
3000-LOAD-PARAMETERS.
MOVE MI-LOAN-AMOUNT TO WS-LOAN-AMOUNT
MOVE MI-LOAN-AMOUNT TO WS-CURRENT-BALANCE
MOVE MI-TERM-MONTHS TO WS-TERM-MONTHS
MOVE MI-TERM-MONTHS TO WS-REMAINING-MONTHS
* Convert annual rate from percentage to decimal
* Example: 6.5000 -> 0.065000
COMPUTE WS-ANNUAL-RATE =
MI-ANNUAL-RATE / 100
* Calculate monthly rate with extended precision
* Example: 0.065000 / 12 = 0.005416666666
COMPUTE WS-MONTHLY-RATE =
WS-ANNUAL-RATE / 12
* Load ARM-specific fields if applicable
IF MI-ARM-LOAN
MOVE MI-ARM-FIXED-MONTHS TO WS-ARM-FIXED-MONTHS
COMPUTE WS-ARM-ADJ-RATE =
MI-ARM-ADJ-RATE / 100
COMPUTE WS-ARM-RATE-CAP =
MI-ARM-RATE-CAP / 100
END-IF
.
*================================================================*
* CALCULATE MONTHLY PAYMENT USING AMORTIZATION FORMULA *
* *
* M = P * [r * (1+r)^n] / [(1+r)^n - 1] *
* *
* Uses iterative multiplication for (1+r)^n to maintain *
* packed-decimal precision throughout the calculation. *
*================================================================*
4000-CALCULATE-PAYMENT.
* Calculate growth factor: (1 + monthly rate)
COMPUTE WS-GROWTH-FACTOR =
1 + WS-MONTHLY-RATE
* Calculate (1+r)^n through iterative multiplication
* This preserves COMP-3 precision instead of using **
* which may convert to floating-point internally
MOVE 1.0 TO WS-POWER-FACTOR
PERFORM VARYING WS-PERIOD-CTR
FROM 1 BY 1
UNTIL WS-PERIOD-CTR > WS-REMAINING-MONTHS
MULTIPLY WS-POWER-FACTOR BY WS-GROWTH-FACTOR
GIVING WS-POWER-FACTOR ROUNDED
END-PERFORM
* Numerator: P * r * (1+r)^n
COMPUTE WS-NUMERATOR ROUNDED =
WS-CURRENT-BALANCE * WS-MONTHLY-RATE
* WS-POWER-FACTOR
ON SIZE ERROR
SET CALC-ERROR TO TRUE
DISPLAY 'SIZE ERROR IN NUMERATOR CALC'
END-COMPUTE
* Denominator: (1+r)^n - 1
COMPUTE WS-DENOMINATOR =
WS-POWER-FACTOR - 1
ON SIZE ERROR
SET CALC-ERROR TO TRUE
DISPLAY 'SIZE ERROR IN DENOMINATOR CALC'
END-COMPUTE
* Monthly payment: M = numerator / denominator
IF NOT CALC-ERROR
COMPUTE WS-MONTHLY-PAYMENT ROUNDED =
WS-NUMERATOR / WS-DENOMINATOR
ON SIZE ERROR
SET CALC-ERROR TO TRUE
DISPLAY 'SIZE ERROR IN PAYMENT CALC'
END-COMPUTE
END-IF
.
*================================================================*
* WRITE LOAN HEADER INFORMATION TO REPORT *
*================================================================*
5000-WRITE-LOAN-HEADER.
* Force new page for each loan
MOVE 99 TO WS-LINE-COUNT
PERFORM 5500-CHECK-PAGE
* Loan information line 1
MOVE MI-LOAN-NUMBER TO WLL1-LOAN-NUM
MOVE MI-BORROWER-NAME TO WLL1-BORROWER
IF MI-FIXED-RATE
MOVE 'FIXED RATE' TO WLL1-LOAN-TYPE
ELSE
MOVE 'ADJUSTABLE ARM' TO WLL1-LOAN-TYPE
END-IF
WRITE REPORT-LINE FROM WS-RPT-LOAN-LINE-1
ADD 1 TO WS-LINE-COUNT
* Loan information line 2
MOVE WS-LOAN-AMOUNT TO WLL2-AMOUNT
COMPUTE WLL2-RATE = MI-ANNUAL-RATE
MOVE WS-TERM-MONTHS TO WLL2-TERM
MOVE WS-MONTHLY-PAYMENT TO WLL2-PAYMENT
WRITE REPORT-LINE FROM WS-RPT-LOAN-LINE-2
ADD 1 TO WS-LINE-COUNT
WRITE REPORT-LINE FROM WS-BLANK-LINE
WRITE REPORT-LINE FROM WS-RPT-COL-HDR
WRITE REPORT-LINE FROM WS-RPT-COL-DASH
ADD 3 TO WS-LINE-COUNT
.
*================================================================*
* CHECK PAGE BREAK AND PRINT PAGE HEADER IF NEEDED *
*================================================================*
5500-CHECK-PAGE.
IF WS-LINE-COUNT >= WS-LINES-PER-PAGE
ADD 1 TO WS-PAGE-NUMBER
MOVE WS-PAGE-NUMBER TO WH1-PAGE
IF WS-PAGE-NUMBER > 1
WRITE REPORT-LINE FROM WS-BLANK-LINE
AFTER ADVANCING PAGE
END-IF
WRITE REPORT-LINE FROM WS-RPT-HEADER-1
WRITE REPORT-LINE FROM WS-RPT-HEADER-2
WRITE REPORT-LINE FROM WS-BLANK-LINE
MOVE 3 TO WS-LINE-COUNT
END-IF
.
*================================================================*
* GENERATE COMPLETE AMORTIZATION SCHEDULE *
*================================================================*
6000-GENERATE-SCHEDULE.
PERFORM VARYING WS-CURRENT-MONTH
FROM 1 BY 1
UNTIL WS-CURRENT-MONTH > WS-TERM-MONTHS
OR WS-CURRENT-BALANCE <= ZEROS
OR CALC-ERROR
PERFORM 5500-CHECK-PAGE
* Check for ARM rate adjustment
IF MI-ARM-LOAN
AND ARM-NOT-ADJUSTED
AND WS-CURRENT-MONTH > WS-ARM-FIXED-MONTHS
PERFORM 6500-ADJUST-ARM-RATE
END-IF
* Calculate this month's payment allocation
PERFORM 6100-CALC-MONTHLY-ALLOCATION
END-PERFORM
.
*================================================================*
* CALCULATE ONE MONTH'S INTEREST/PRINCIPAL ALLOCATION *
*================================================================*
6100-CALC-MONTHLY-ALLOCATION.
* Step 1: Interest = remaining balance * monthly rate
COMPUTE WS-INTEREST-PORTION ROUNDED =
WS-CURRENT-BALANCE * WS-MONTHLY-RATE
ON SIZE ERROR
SET CALC-ERROR TO TRUE
DISPLAY 'SIZE ERROR IN INTEREST CALC'
END-COMPUTE
* Step 2: Principal = payment - interest
COMPUTE WS-PRINCIPAL-PORTION =
WS-MONTHLY-PAYMENT - WS-INTEREST-PORTION
ON SIZE ERROR
SET CALC-ERROR TO TRUE
DISPLAY 'SIZE ERROR IN PRINCIPAL CALC'
END-COMPUTE
* Step 3: Final payment adjustment
* The last payment must be adjusted so the balance
* reaches exactly zero. Accumulated rounding across
* hundreds of payments means the final regular payment
* would leave a small residual balance (positive or
* negative). We detect this and adjust accordingly.
IF WS-PRINCIPAL-PORTION >= WS-CURRENT-BALANCE
MOVE WS-CURRENT-BALANCE TO WS-PRINCIPAL-PORTION
COMPUTE WS-MONTHLY-PAYMENT =
WS-PRINCIPAL-PORTION + WS-INTEREST-PORTION
END-IF
* Step 4: Update the remaining balance
SUBTRACT WS-PRINCIPAL-PORTION FROM WS-CURRENT-BALANCE
* Step 5: Guard against tiny negative balances from
* rounding
IF WS-CURRENT-BALANCE < ZEROS
ADD WS-CURRENT-BALANCE TO WS-PRINCIPAL-PORTION
MOVE ZEROS TO WS-CURRENT-BALANCE
END-IF
* Step 6: Accumulate totals
ADD WS-INTEREST-PORTION TO WS-TOTAL-INTEREST
ADD WS-PRINCIPAL-PORTION TO WS-TOTAL-PRINCIPAL
ADD WS-MONTHLY-PAYMENT TO WS-TOTAL-PAYMENTS
ADD 1 TO WS-PAYMENT-COUNT
* Step 7: Write detail line
PERFORM 6200-WRITE-DETAIL
.
*================================================================*
* WRITE ONE DETAIL LINE OF THE AMORTIZATION SCHEDULE *
*================================================================*
6200-WRITE-DETAIL.
MOVE WS-CURRENT-MONTH TO WD-PMT-NUM
MOVE WS-MONTHLY-PAYMENT TO WD-PMT-AMOUNT
MOVE WS-INTEREST-PORTION TO WD-INTEREST
MOVE WS-PRINCIPAL-PORTION TO WD-PRINCIPAL
MOVE WS-CURRENT-BALANCE TO WD-BALANCE
MOVE WS-TOTAL-INTEREST TO WD-CUM-INT
WRITE REPORT-LINE FROM WS-RPT-DETAIL
ADD 1 TO WS-LINE-COUNT
.
*================================================================*
* ADJUST ARM RATE AFTER FIXED PERIOD EXPIRES *
* *
* When the introductory fixed period ends on an adjustable-rate *
* mortgage, the rate changes to the adjustment rate. The monthly *
* payment is recalculated based on the remaining balance and *
* remaining term at the new rate. *
*================================================================*
6500-ADJUST-ARM-RATE.
SET ARM-RATE-ADJUSTED TO TRUE
* Apply rate cap if adjustment exceeds maximum
IF WS-ARM-ADJ-RATE > WS-ARM-RATE-CAP
MOVE WS-ARM-RATE-CAP TO WS-ARM-NEW-RATE
ELSE
MOVE WS-ARM-ADJ-RATE TO WS-ARM-NEW-RATE
END-IF
* Update the monthly rate
COMPUTE WS-MONTHLY-RATE =
WS-ARM-NEW-RATE / 12
* Calculate remaining months
COMPUTE WS-REMAINING-MONTHS =
WS-TERM-MONTHS - WS-CURRENT-MONTH + 1
* Recalculate monthly payment for remaining balance
* and remaining term at the new rate
PERFORM 4000-CALCULATE-PAYMENT
* Write rate adjustment notice
COMPUTE WARN-NEW-RATE = WS-ARM-NEW-RATE * 100
MOVE WS-MONTHLY-PAYMENT TO WARN-NEW-PMT
WRITE REPORT-LINE FROM WS-RPT-ARM-NOTICE
ADD 1 TO WS-LINE-COUNT
.
*================================================================*
* WRITE LOAN SUMMARY TOTALS *
*================================================================*
7000-WRITE-SUMMARY.
WRITE REPORT-LINE FROM WS-RPT-SUMMARY
MOVE WS-TOTAL-PRINCIPAL TO WSUM-PRINCIPAL
MOVE WS-TOTAL-INTEREST TO WSUM-INTEREST
WRITE REPORT-LINE FROM WS-RPT-SUM-LINE-1
MOVE WS-TOTAL-PAYMENTS TO WSUM-ALL-PMTS
MOVE WS-PAYMENT-COUNT TO WSUM-PMT-COUNT
WRITE REPORT-LINE FROM WS-RPT-SUM-LINE-2
WRITE REPORT-LINE FROM WS-BLANK-LINE
.
*================================================================*
* READ NEXT MORTGAGE INPUT RECORD *
*================================================================*
8000-READ-INPUT.
READ MORTGAGE-INPUT
AT END
SET END-OF-FILE TO TRUE
END-READ
.
*================================================================*
* CLOSE FILES AND DISPLAY SUMMARY *
*================================================================*
9000-FINALIZE.
CLOSE MORTGAGE-INPUT
MORTGAGE-REPORT
DISPLAY 'MORTGAGE CALCULATION COMPLETE'
DISPLAY ' LOANS PROCESSED: ' WS-LOANS-PROCESSED
.
The JCL to execute this program on a z/OS system follows:
//MORTCALC JOB (ACCT),'MORTGAGE CALC',CLASS=A,
// MSGCLASS=X,NOTIFY=&SYSUID
//*
//*================================================================*
//* MORTGAGE PAYMENT CALCULATOR - EXECUTION JCL *
//*================================================================*
//*
//STEP01 EXEC PGM=MORTCALC
//STEPLIB DD DSN=HFCU.MORTGAGE.LOADLIB,DISP=SHR
//MORTPARM DD DSN=HFCU.MORTGAGE.INPUT,DISP=SHR
//MORTRPT DD DSN=HFCU.MORTGAGE.REPORT,DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(5,2),RLSE),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//SYSOUT DD SYSOUT=*
How the Program Works
Parameter Loading (3000-LOAD-PARAMETERS)
The program reads each mortgage record and converts the annual interest rate from a percentage to a decimal by dividing by 100. It then derives the monthly rate by dividing by 12. The monthly rate field carries 10 decimal places to preserve precision through subsequent calculations. For ARM loans, the adjustment rate and rate cap are also loaded.
Payment Calculation (4000-CALCULATE-PAYMENT)
Instead of using the COBOL exponentiation operator (**), which may internally convert COMP-3 values to floating-point, the program computes (1 + r)^n through iterative multiplication in a PERFORM VARYING loop. This keeps all arithmetic in packed decimal, eliminating the floating-point conversion that could introduce binary representation errors. The result is then used in the standard amortization formula to compute the monthly payment, rounded to the nearest cent.
Amortization Schedule (6000/6100)
For each month, the program calculates interest on the remaining balance, derives the principal portion by subtracting interest from the payment, and reduces the balance. The final payment is automatically adjusted when the principal portion would exceed the remaining balance, ensuring the schedule ends at exactly zero.
ARM Rate Adjustment (6500-ADJUST-ARM-RATE)
When the fixed period of an adjustable-rate mortgage expires, the program applies the new rate (subject to the rate cap) and recalculates the monthly payment based on the remaining balance and remaining term. The recalculation reuses the same 4000-CALCULATE-PAYMENT paragraph, demonstrating modular design. A notice line is inserted in the report showing the new rate and payment.
Precision Considerations
Several design choices in this program ensure penny-exact results:
-
WS-MONTHLY-RATE uses PIC SV9(10): Dividing 6.5% by 12 yields 0.00541666..., a repeating decimal. Ten decimal places capture this with sufficient precision.
-
WS-POWER-FACTOR uses PIC S9(5)V9(12): The growth factor raised to the power of 360 can produce values like 6.989+, requiring both integer and extensive decimal capacity.
-
Iterative multiplication instead of exponentiation: Keeps all intermediate results in COMP-3, avoiding any floating-point conversion.
-
ROUNDED on every monetary COMPUTE: Ensures half-cent values round to the nearest cent rather than being truncated downward.
-
ON SIZE ERROR on critical calculations: Detects any overflow condition that would corrupt the result.
-
Final payment adjustment: The explicit check in paragraph 6100 guarantees the schedule balances to zero regardless of accumulated rounding across hundreds of payments.
Discussion Questions
-
Why does the program use iterative multiplication (a PERFORM VARYING loop) instead of the ** operator to compute the power factor? What would happen if the ** operator internally converted COMP-3 values to floating-point?
-
Explain why the final payment in an amortization schedule is almost never equal to the regular monthly payment. How large can this difference be for a typical 30-year mortgage?
-
When an ARM rate adjusts, the program recalculates the payment based on the remaining balance and remaining term. Why is this recalculation necessary? What would happen if the original payment amount continued at the new rate?
-
The program uses PIC S9(02)V9(10) for the monthly rate and PIC S9(05)V9(12) for the power factor. What would happen if these fields used only 2 decimal places (PIC V99)?
-
Consider a scenario where two loan officers calculate a payment for the same mortgage parameters on different systems -- one running this COBOL program, the other using a spreadsheet with floating-point arithmetic. Under what circumstances might they get different payment amounts, and what are the regulatory implications?
Connection to Chapter Concepts
This case study directly applies these Chapter 33 concepts:
- COMP-3 packed decimal: All monetary and rate fields use COMP-3 for exact decimal arithmetic (Section 33.2).
- ROUNDED phrase: Every COMPUTE that produces a monetary result uses ROUNDED (Section 33.3).
- ON SIZE ERROR: Overflow detection on all critical calculations (Section 33.3).
- Intermediate precision: Rate and power factor fields carry 10-12 decimal places (Section 33.2).
- Amortization formula: The standard M = P[r(1+r)^n] / [(1+r)^n - 1] formula implemented with proper decimal handling (Section 33.4).
- Final payment reconciliation: Adjusting the last payment for exact zero balance (Section 33.4).
- Iterative compounding: Using PERFORM VARYING instead of ** for packed-decimal precision (Section 33.3).