Financial calculations represent the heart of COBOL's original purpose and its enduring strength. When Grace Hopper and her colleagues designed COBOL in 1959, they built a language that treats decimal arithmetic as a first-class citizen rather than...
In This Chapter
- Part VII - COBOL in Financial Systems
- 33.1 Why COBOL for Financial Calculations
- 33.2 COBOL Decimal Arithmetic Fundamentals
- 33.3 Interest Calculations
- 33.4 Amortization Schedules
- 33.5 Currency Handling
- 33.6 Tax Calculations
- 33.7 Financial Date Calculations
- 33.8 Present Value, Future Value, NPV, and IRR
- 33.9 Regulatory Compliance
- 33.10 Handling Very Large Amounts
- 33.11 Summary
- Review Questions
- Programming Exercises
Chapter 33: Financial Calculations in COBOL
Part VII - COBOL in Financial Systems
Financial calculations represent the heart of COBOL's original purpose and its enduring strength. When Grace Hopper and her colleagues designed COBOL in 1959, they built a language that treats decimal arithmetic as a first-class citizen rather than an afterthought. Today, over six decades later, COBOL processes an estimated $3 trillion in daily commerce precisely because its arithmetic model aligns perfectly with the requirements of financial computing. This chapter explores the full spectrum of financial calculations in COBOL, from simple interest computations to complex amortization schedules, from tax bracket calculations to present value analysis. Every example presents complete, working code that you can compile and run on any standard COBOL compiler.
33.1 Why COBOL for Financial Calculations
The Floating-Point Problem
Before examining COBOL's strengths, it is essential to understand why languages based on IEEE 754 floating-point arithmetic present serious problems for financial work. Consider what happens in many languages when you add 0.1 and 0.2:
0.1 + 0.2 = 0.30000000000000004
This occurs because 0.1 and 0.2 cannot be represented exactly in binary floating-point. While the error appears insignificant, in financial systems processing millions of transactions daily, these tiny discrepancies accumulate into material differences that violate regulatory requirements and destroy audit trails.
COBOL's Fixed-Point Decimal Advantage
COBOL stores numbers exactly as decimal values. When you define a field as PIC 9(5)V99, COBOL stores exactly five integer digits and two decimal digits. The value 123.45 is stored as precisely 123.45, not as a binary approximation. This design decision, made in 1959, remains the single most important reason COBOL dominates financial computing.
The advantages extend beyond simple accuracy:
- Predictable rounding: COBOL's ROUNDED phrase applies deterministic rounding rules that auditors can verify.
- Exact intermediate results: Arithmetic operations on packed-decimal fields produce exact intermediate results up to 18 or 31 digits depending on the compiler.
- No representation surprises: The value stored is the value you specified, period.
- Regulatory compliance: Financial regulators require exact decimal arithmetic for interest calculations, tax computations, and fee assessments.
Real-World Impact
In 2012, a major brokerage firm discovered that its Java-based pricing engine accumulated rounding errors of $0.003 per trade. Across 50 million daily trades, this produced a daily discrepancy of $150,000. The firm's COBOL-based back-office settlement system, processing the same trades, balanced to the penny every day. This is not an isolated case; it reflects a fundamental architectural difference between floating-point and fixed-point decimal arithmetic.
33.2 COBOL Decimal Arithmetic Fundamentals
As introduced in Chapter 3 (Data Types and PICTURE Clauses) and Chapter 6 (Arithmetic Operations), COBOL provides several numeric storage formats. In financial work, the choice of storage format directly affects both performance and precision.
PACKED-DECIMAL (COMP-3)
Packed-decimal storage, specified with USAGE COMP-3 or USAGE PACKED-DECIMAL, stores two decimal digits per byte with a trailing sign nibble. This is the preferred format for financial calculations on IBM mainframes because the hardware provides native packed-decimal arithmetic instructions.
01 WS-TRANSACTION-AMOUNT PIC S9(13)V99 COMP-3.
01 WS-INTEREST-RATE PIC S9(3)V9(6) COMP-3.
01 WS-BALANCE PIC S9(15)V99 COMP-3.
Storage characteristics:
- PIC S9(13)V99 COMP-3 occupies 8 bytes (15 digits = 8 bytes)
- PIC S9(3)V9(6) COMP-3 occupies 5 bytes (9 digits = 5 bytes)
- Formula: CEIL((number-of-digits + 1) / 2) bytes
BINARY (COMP / COMP-4 / COMP-5)
Binary storage is efficient for counters, subscripts, and fields used primarily in comparisons rather than decimal arithmetic. However, binary fields require conversion for decimal operations, which can introduce performance overhead in calculation-intensive programs.
01 WS-RECORD-COUNT PIC S9(8) COMP.
01 WS-LOOP-INDEX PIC S9(4) COMP.
01 WS-TABLE-SIZE PIC S9(4) COMP VALUE 360.
DISPLAY Numeric
DISPLAY numeric stores one digit per byte in character format. While least space-efficient, DISPLAY numeric is valuable for fields that appear directly in reports or flat file output without conversion.
01 WS-PRINT-AMOUNT PIC Z(10)9.99.
01 WS-REPORT-RATE PIC Z9.999999.
Choosing the Right Format for Financial Work
The following table summarizes the recommended usage:
| Scenario | Recommended Format | Reason |
|---|---|---|
| Monetary amounts | COMP-3 | Native decimal arithmetic, exact precision |
| Interest rates | COMP-3 | Requires decimal precision beyond 2 places |
| Loop counters | COMP | Binary arithmetic is faster for iteration |
| Record counts | COMP | No decimal arithmetic needed |
| Report fields | DISPLAY | Direct output without conversion |
| File output amounts | COMP-3 or DISPLAY | Depends on file format requirements |
Precision Planning
Financial calculations frequently require intermediate results with more decimal places than the final result. A sound practice is to define working fields with extra precision:
01 WS-CALC-FIELDS.
05 WS-DAILY-RATE PIC S9(3)V9(10) COMP-3.
05 WS-ACCUM-INTEREST PIC S9(15)V9(6) COMP-3.
05 WS-ROUND-RESULT PIC S9(13)V99 COMP-3.
By carrying ten decimal places in WS-DAILY-RATE and six in WS-ACCUM-INTEREST, you ensure that rounding occurs only at the final step when the result is stored in the two-decimal-place output field.
33.3 Interest Calculations
Interest calculations are the most fundamental financial computations in banking and lending systems. COBOL programs calculate interest for savings accounts, certificates of deposit, mortgages, personal loans, credit cards, and commercial lending facilities.
Simple Interest
Simple interest is calculated on the original principal only. The formula is:
Interest = Principal x Rate x Time
The following complete program calculates simple interest for a batch of loan records:
IDENTIFICATION DIVISION.
PROGRAM-ID. SIMPLE-INTEREST.
AUTHOR. FINANCIAL-SYSTEMS-TEAM.
*================================================================*
* PROGRAM: SIMPLE-INTEREST *
* PURPOSE: Calculate simple interest for a batch of loan records *
* DATE: 2024-01-15 *
*================================================================*
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOAN-FILE ASSIGN TO 'LOANIN'
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-LOAN-STATUS.
SELECT REPORT-FILE ASSIGN TO 'RPTOUT'
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-RPT-STATUS.
DATA DIVISION.
FILE SECTION.
FD LOAN-FILE
RECORDING MODE IS F
RECORD CONTAINS 80 CHARACTERS.
01 LOAN-RECORD.
05 LR-ACCOUNT-NUM PIC X(10).
05 LR-PRINCIPAL PIC S9(11)V99 COMP-3.
05 LR-ANNUAL-RATE PIC S9(3)V9(6) COMP-3.
05 LR-TERM-DAYS PIC S9(5) COMP-3.
05 LR-START-DATE PIC 9(8).
05 FILLER PIC X(41).
FD REPORT-FILE
RECORDING MODE IS F
RECORD CONTAINS 132 CHARACTERS.
01 REPORT-RECORD PIC X(132).
WORKING-STORAGE SECTION.
01 WS-FILE-STATUS.
05 WS-LOAN-STATUS PIC XX.
05 WS-RPT-STATUS PIC XX.
01 WS-FLAGS.
05 WS-EOF-FLAG PIC X VALUE 'N'.
88 END-OF-FILE VALUE 'Y'.
01 WS-COUNTERS.
05 WS-RECORDS-READ PIC S9(8) COMP VALUE 0.
05 WS-RECORDS-WRITTEN PIC S9(8) COMP VALUE 0.
01 WS-CALC-FIELDS.
05 WS-DAILY-RATE PIC S9(3)V9(10) COMP-3.
05 WS-INTEREST-AMT PIC S9(13)V9(4) COMP-3.
05 WS-ROUNDED-INT PIC S9(13)V99 COMP-3.
05 WS-TOTAL-INTEREST PIC S9(15)V99 COMP-3
VALUE 0.
01 WS-REPORT-HEADER.
05 FILLER PIC X(10) VALUE 'ACCOUNT '.
05 FILLER PIC X(20) VALUE 'PRINCIPAL '.
05 FILLER PIC X(12) VALUE 'RATE '.
05 FILLER PIC X(10) VALUE 'DAYS '.
05 FILLER PIC X(20) VALUE 'INTEREST '.
05 FILLER PIC X(60) VALUE SPACES.
01 WS-REPORT-DETAIL.
05 WS-RPT-ACCOUNT PIC X(10).
05 FILLER PIC X(2) VALUE SPACES.
05 WS-RPT-PRINCIPAL PIC Z(10)9.99.
05 FILLER PIC X(2) VALUE SPACES.
05 WS-RPT-RATE PIC Z9.999999.
05 FILLER PIC X(2) VALUE SPACES.
05 WS-RPT-DAYS PIC Z(4)9.
05 FILLER PIC X(2) VALUE SPACES.
05 WS-RPT-INTEREST PIC Z(10)9.99.
05 FILLER PIC X(55) VALUE SPACES.
01 WS-REPORT-TOTAL.
05 FILLER PIC X(46) VALUE SPACES.
05 FILLER PIC X(16)
VALUE 'TOTAL INTEREST: '.
05 WS-RPT-TOTAL-INT PIC Z(12)9.99.
05 FILLER PIC X(53) VALUE SPACES.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-LOANS
UNTIL END-OF-FILE
PERFORM 3000-FINALIZE
STOP RUN
.
1000-INITIALIZE.
OPEN INPUT LOAN-FILE
OUTPUT REPORT-FILE
IF WS-LOAN-STATUS NOT = '00'
DISPLAY 'ERROR OPENING LOAN FILE: ' WS-LOAN-STATUS
STOP RUN
END-IF
WRITE REPORT-RECORD FROM WS-REPORT-HEADER
PERFORM 9000-READ-LOAN
.
2000-PROCESS-LOANS.
ADD 1 TO WS-RECORDS-READ
* Calculate daily rate: annual rate / 365
DIVIDE LR-ANNUAL-RATE BY 365
GIVING WS-DAILY-RATE ROUNDED
* Simple interest = principal * daily rate * number of days
MULTIPLY LR-PRINCIPAL BY WS-DAILY-RATE
GIVING WS-INTEREST-AMT ROUNDED
MULTIPLY WS-INTEREST-AMT BY LR-TERM-DAYS
GIVING WS-INTEREST-AMT ROUNDED
* Round to cents for final result
MOVE WS-INTEREST-AMT TO WS-ROUNDED-INT
ADD WS-ROUNDED-INT TO WS-TOTAL-INTEREST
* Format report line
MOVE LR-ACCOUNT-NUM TO WS-RPT-ACCOUNT
MOVE LR-PRINCIPAL TO WS-RPT-PRINCIPAL
MOVE LR-ANNUAL-RATE TO WS-RPT-RATE
MOVE LR-TERM-DAYS TO WS-RPT-DAYS
MOVE WS-ROUNDED-INT TO WS-RPT-INTEREST
WRITE REPORT-RECORD FROM WS-REPORT-DETAIL
ADD 1 TO WS-RECORDS-WRITTEN
PERFORM 9000-READ-LOAN
.
3000-FINALIZE.
MOVE WS-TOTAL-INTEREST TO WS-RPT-TOTAL-INT
WRITE REPORT-RECORD FROM WS-REPORT-TOTAL
CLOSE LOAN-FILE
REPORT-FILE
DISPLAY 'SIMPLE INTEREST CALCULATION COMPLETE'
DISPLAY 'RECORDS READ: ' WS-RECORDS-READ
DISPLAY 'RECORDS WRITTEN: ' WS-RECORDS-WRITTEN
DISPLAY 'TOTAL INTEREST: ' WS-TOTAL-INTEREST
.
9000-READ-LOAN.
READ LOAN-FILE
AT END SET END-OF-FILE TO TRUE
END-READ
.
Compound Interest
Compound interest is calculated on both the original principal and accumulated interest. The formula is:
A = P x (1 + r/n)^(n*t)
Where A is the final amount, P is the principal, r is the annual rate, n is the number of compounding periods per year, and t is the time in years.
COBOL does not have a built-in exponentiation operator for non-integer exponents when using packed-decimal fields. The standard approach is to use iterative multiplication:
IDENTIFICATION DIVISION.
PROGRAM-ID. COMPOUND-INTEREST.
*================================================================*
* PROGRAM: COMPOUND-INTEREST *
* PURPOSE: Calculate compound interest with various compounding *
* frequencies (monthly, quarterly, daily) *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-INPUT-FIELDS.
05 WS-PRINCIPAL PIC S9(13)V99 COMP-3.
05 WS-ANNUAL-RATE PIC S9(3)V9(6) COMP-3.
05 WS-YEARS PIC S9(3) COMP-3.
05 WS-COMPOUND-FREQ PIC S9(3) COMP-3.
88 FREQ-DAILY VALUE 365.
88 FREQ-MONTHLY VALUE 12.
88 FREQ-QUARTERLY VALUE 4.
88 FREQ-SEMI-ANNUAL VALUE 2.
88 FREQ-ANNUAL VALUE 1.
01 WS-CALC-FIELDS.
05 WS-PERIOD-RATE PIC S9(3)V9(10) COMP-3.
05 WS-GROWTH-FACTOR PIC S9(5)V9(10) COMP-3.
05 WS-TOTAL-PERIODS PIC S9(7) COMP-3.
05 WS-PERIOD-COUNTER PIC S9(7) COMP-3.
05 WS-ACCUMULATOR PIC S9(15)V9(6) COMP-3.
05 WS-COMPOUND-AMOUNT PIC S9(15)V99 COMP-3.
05 WS-INTEREST-EARNED PIC S9(15)V99 COMP-3.
01 WS-DISPLAY-FIELDS.
05 WS-DISP-PRINCIPAL PIC $ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-AMOUNT PIC $ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-INTEREST PIC $ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-RATE PIC Z9.999999.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
* Example: $100,000 at 5.25% compounded monthly for 10 years
MOVE 100000.00 TO WS-PRINCIPAL
MOVE 5.250000 TO WS-ANNUAL-RATE
MOVE 10 TO WS-YEARS
SET FREQ-MONTHLY TO TRUE
PERFORM 1000-CALCULATE-COMPOUND-INTEREST
MOVE WS-PRINCIPAL TO WS-DISP-PRINCIPAL
MOVE WS-COMPOUND-AMOUNT TO WS-DISP-AMOUNT
MOVE WS-INTEREST-EARNED TO WS-DISP-INTEREST
MOVE WS-ANNUAL-RATE TO WS-DISP-RATE
DISPLAY '=================================='
DISPLAY 'COMPOUND INTEREST CALCULATION'
DISPLAY '=================================='
DISPLAY 'PRINCIPAL: ' WS-DISP-PRINCIPAL
DISPLAY 'ANNUAL RATE: ' WS-DISP-RATE '%'
DISPLAY 'COMPOUNDING: MONTHLY'
DISPLAY 'YEARS: ' WS-YEARS
DISPLAY 'FINAL AMOUNT: ' WS-DISP-AMOUNT
DISPLAY 'INTEREST EARNED: ' WS-DISP-INTEREST
STOP RUN
.
1000-CALCULATE-COMPOUND-INTEREST.
* Calculate period rate = annual rate / compounding frequency
DIVIDE WS-ANNUAL-RATE BY 100
GIVING WS-PERIOD-RATE
DIVIDE WS-PERIOD-RATE BY WS-COMPOUND-FREQ
GIVING WS-PERIOD-RATE ROUNDED
* Growth factor per period = 1 + period rate
ADD 1 TO WS-PERIOD-RATE
GIVING WS-GROWTH-FACTOR
* Total periods = frequency * years
MULTIPLY WS-COMPOUND-FREQ BY WS-YEARS
GIVING WS-TOTAL-PERIODS
* Iterative compounding: multiply growth factor
* for each period
MOVE 1.0 TO WS-ACCUMULATOR
PERFORM VARYING WS-PERIOD-COUNTER FROM 1 BY 1
UNTIL WS-PERIOD-COUNTER > WS-TOTAL-PERIODS
MULTIPLY WS-ACCUMULATOR BY WS-GROWTH-FACTOR
GIVING WS-ACCUMULATOR ROUNDED
END-PERFORM
* Final amount = principal * accumulated growth factor
MULTIPLY WS-PRINCIPAL BY WS-ACCUMULATOR
GIVING WS-COMPOUND-AMOUNT ROUNDED
* Interest earned = final amount - principal
SUBTRACT WS-PRINCIPAL FROM WS-COMPOUND-AMOUNT
GIVING WS-INTEREST-EARNED
.
Daily Interest Accrual
Many financial institutions calculate interest on a daily basis. This is particularly important for savings accounts, money market accounts, and commercial loans where balances change frequently:
IDENTIFICATION DIVISION.
PROGRAM-ID. DAILY-ACCRUAL.
*================================================================*
* PROGRAM: DAILY-ACCRUAL *
* PURPOSE: Calculate daily interest accrual for accounts with *
* varying balances throughout the month *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ACCRUAL-TABLE.
05 WS-DAILY-ENTRY OCCURS 31 TIMES.
10 WS-DAY-BALANCE PIC S9(13)V99 COMP-3.
10 WS-DAY-ACCRUAL PIC S9(9)V9(6) COMP-3.
01 WS-CALC-FIELDS.
05 WS-ANNUAL-RATE PIC S9(3)V9(6) COMP-3.
05 WS-DAILY-RATE PIC S9(3)V9(10) COMP-3.
05 WS-DAYS-IN-MONTH PIC S9(3) COMP-3.
05 WS-DAYS-IN-YEAR PIC S9(3) COMP-3 VALUE 365.
05 WS-DAY-INDEX PIC S9(3) COMP-3.
05 WS-MONTH-ACCRUAL PIC S9(13)V99 COMP-3.
05 WS-ACCUM-ACCRUAL PIC S9(13)V9(6) COMP-3.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
* Example: 4.50% annual rate, 30-day month
MOVE 4.500000 TO WS-ANNUAL-RATE
MOVE 30 TO WS-DAYS-IN-MONTH
* Set up sample daily balances (varying throughout month)
MOVE 50000.00 TO WS-DAY-BALANCE(1)
MOVE 50000.00 TO WS-DAY-BALANCE(2)
MOVE 50000.00 TO WS-DAY-BALANCE(3)
MOVE 50000.00 TO WS-DAY-BALANCE(4)
MOVE 50000.00 TO WS-DAY-BALANCE(5)
MOVE 48500.00 TO WS-DAY-BALANCE(6)
MOVE 48500.00 TO WS-DAY-BALANCE(7)
MOVE 48500.00 TO WS-DAY-BALANCE(8)
MOVE 48500.00 TO WS-DAY-BALANCE(9)
MOVE 48500.00 TO WS-DAY-BALANCE(10)
MOVE 48500.00 TO WS-DAY-BALANCE(11)
MOVE 48500.00 TO WS-DAY-BALANCE(12)
MOVE 48500.00 TO WS-DAY-BALANCE(13)
MOVE 48500.00 TO WS-DAY-BALANCE(14)
MOVE 51200.00 TO WS-DAY-BALANCE(15)
MOVE 51200.00 TO WS-DAY-BALANCE(16)
MOVE 51200.00 TO WS-DAY-BALANCE(17)
MOVE 51200.00 TO WS-DAY-BALANCE(18)
MOVE 51200.00 TO WS-DAY-BALANCE(19)
MOVE 51200.00 TO WS-DAY-BALANCE(20)
MOVE 51200.00 TO WS-DAY-BALANCE(21)
MOVE 51200.00 TO WS-DAY-BALANCE(22)
MOVE 51200.00 TO WS-DAY-BALANCE(23)
MOVE 51200.00 TO WS-DAY-BALANCE(24)
MOVE 51200.00 TO WS-DAY-BALANCE(25)
MOVE 53750.00 TO WS-DAY-BALANCE(26)
MOVE 53750.00 TO WS-DAY-BALANCE(27)
MOVE 53750.00 TO WS-DAY-BALANCE(28)
MOVE 53750.00 TO WS-DAY-BALANCE(29)
MOVE 53750.00 TO WS-DAY-BALANCE(30)
PERFORM 1000-CALCULATE-DAILY-ACCRUAL
DISPLAY 'MONTHLY ACCRUED INTEREST: ' WS-MONTH-ACCRUAL
STOP RUN
.
1000-CALCULATE-DAILY-ACCRUAL.
* Daily rate = annual rate / 100 / days in year
DIVIDE WS-ANNUAL-RATE BY 100
GIVING WS-DAILY-RATE
DIVIDE WS-DAILY-RATE BY WS-DAYS-IN-YEAR
GIVING WS-DAILY-RATE ROUNDED
MOVE ZERO TO WS-ACCUM-ACCRUAL
PERFORM VARYING WS-DAY-INDEX FROM 1 BY 1
UNTIL WS-DAY-INDEX > WS-DAYS-IN-MONTH
* Daily accrual = balance * daily rate
MULTIPLY WS-DAY-BALANCE(WS-DAY-INDEX)
BY WS-DAILY-RATE
GIVING WS-DAY-ACCRUAL(WS-DAY-INDEX) ROUNDED
ADD WS-DAY-ACCRUAL(WS-DAY-INDEX)
TO WS-ACCUM-ACCRUAL
END-PERFORM
* Round accumulated accrual to cents
MOVE WS-ACCUM-ACCRUAL TO WS-MONTH-ACCRUAL
.
APR to APY Conversion
The Annual Percentage Rate (APR) and Annual Percentage Yield (APY) are related but distinct measures. APY accounts for the effect of compounding:
APY = (1 + APR/n)^n - 1
Where n is the number of compounding periods per year.
IDENTIFICATION DIVISION.
PROGRAM-ID. APR-APY-CONVERT.
*================================================================*
* PROGRAM: APR-APY-CONVERT *
* PURPOSE: Convert between APR and APY for Truth-in-Lending and *
* Truth-in-Savings disclosures *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-INPUT-FIELDS.
05 WS-APR PIC S9(3)V9(6) COMP-3.
05 WS-COMPOUND-PERIODS PIC S9(3) COMP-3.
01 WS-CALC-FIELDS.
05 WS-PERIOD-RATE PIC S9(3)V9(10) COMP-3.
05 WS-GROWTH-FACTOR PIC S9(5)V9(10) COMP-3.
05 WS-ACCUMULATED PIC S9(5)V9(10) COMP-3.
05 WS-PERIOD-CTR PIC S9(3) COMP-3.
05 WS-APY PIC S9(3)V9(6) COMP-3.
01 WS-DISPLAY-FIELDS.
05 WS-DISP-APR PIC Z9.999999.
05 WS-DISP-APY PIC Z9.999999.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
* Example: 5.00% APR compounded monthly
MOVE 5.000000 TO WS-APR
MOVE 12 TO WS-COMPOUND-PERIODS
PERFORM 1000-APR-TO-APY
MOVE WS-APR TO WS-DISP-APR
MOVE WS-APY TO WS-DISP-APY
DISPLAY 'APR: ' WS-DISP-APR '% -> APY: ' WS-DISP-APY '%'
* Example: 5.00% APR compounded daily
MOVE 5.000000 TO WS-APR
MOVE 365 TO WS-COMPOUND-PERIODS
PERFORM 1000-APR-TO-APY
MOVE WS-APR TO WS-DISP-APR
MOVE WS-APY TO WS-DISP-APY
DISPLAY 'APR: ' WS-DISP-APR '% -> APY: ' WS-DISP-APY '%'
STOP RUN
.
1000-APR-TO-APY.
* Period rate = APR / 100 / n
DIVIDE WS-APR BY 100
GIVING WS-PERIOD-RATE
DIVIDE WS-PERIOD-RATE BY WS-COMPOUND-PERIODS
GIVING WS-PERIOD-RATE ROUNDED
* Growth factor = 1 + period rate
ADD 1 TO WS-PERIOD-RATE
GIVING WS-GROWTH-FACTOR
* Accumulated = growth factor ^ n (iterative)
MOVE 1.0 TO WS-ACCUMULATED
PERFORM VARYING WS-PERIOD-CTR FROM 1 BY 1
UNTIL WS-PERIOD-CTR > WS-COMPOUND-PERIODS
MULTIPLY WS-ACCUMULATED BY WS-GROWTH-FACTOR
GIVING WS-ACCUMULATED ROUNDED
END-PERFORM
* APY = (accumulated - 1) * 100
SUBTRACT 1 FROM WS-ACCUMULATED
MULTIPLY WS-ACCUMULATED BY 100
GIVING WS-APY ROUNDED
.
33.4 Amortization Schedules
Amortization schedules are among the most common financial calculations in banking. Every mortgage, auto loan, and personal loan requires a payment schedule showing how each payment is split between principal and interest.
The Amortization Formula
The standard monthly payment formula is:
M = P x [r(1+r)^n] / [(1+r)^n - 1]
Where M is the monthly payment, P is the loan principal, r is the monthly interest rate, and n is the total number of payments.
Complete Amortization Schedule Program
The following program generates a full amortization schedule for a fixed-rate loan:
IDENTIFICATION DIVISION.
PROGRAM-ID. AMORT-SCHEDULE.
*================================================================*
* PROGRAM: AMORT-SCHEDULE *
* PURPOSE: Generate complete amortization schedule for fixed-rate *
* loans (mortgage, auto, personal) *
* NOTES: Handles standard US mortgage conventions with final *
* payment adjustment for rounding *
*================================================================*
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT AMORT-REPORT ASSIGN TO 'AMORTOUT'
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-RPT-STATUS.
DATA DIVISION.
FILE SECTION.
FD AMORT-REPORT
RECORDING MODE IS F
RECORD CONTAINS 132 CHARACTERS.
01 REPORT-LINE PIC X(132).
WORKING-STORAGE SECTION.
01 WS-RPT-STATUS PIC XX.
01 WS-LOAN-PARAMS.
05 WS-LOAN-AMOUNT PIC S9(13)V99 COMP-3.
05 WS-ANNUAL-RATE PIC S9(3)V9(6) COMP-3.
05 WS-TERM-MONTHS PIC S9(5) COMP-3.
05 WS-LOAN-TYPE PIC X(10).
01 WS-CALC-FIELDS.
05 WS-MONTHLY-RATE PIC S9(3)V9(10) COMP-3.
05 WS-GROWTH-FACTOR PIC S9(5)V9(10) COMP-3.
05 WS-POWER-RESULT PIC S9(7)V9(10) COMP-3.
05 WS-NUMERATOR PIC S9(15)V9(10) COMP-3.
05 WS-DENOMINATOR PIC S9(15)V9(10) COMP-3.
05 WS-MONTHLY-PAYMENT PIC S9(9)V99 COMP-3.
05 WS-PERIOD-CTR PIC S9(5) COMP-3.
05 WS-REMAINING-BAL PIC S9(13)V99 COMP-3.
05 WS-INTEREST-PORTION PIC S9(9)V99 COMP-3.
05 WS-PRINCIPAL-PORTION PIC S9(9)V99 COMP-3.
05 WS-TOTAL-INTEREST PIC S9(15)V99 COMP-3.
05 WS-TOTAL-PRINCIPAL PIC S9(15)V99 COMP-3.
05 WS-TOTAL-PAID PIC S9(15)V99 COMP-3.
05 WS-INTEREST-WORK PIC S9(13)V9(6) COMP-3.
01 WS-REPORT-HEADER-1.
05 FILLER PIC X(50) VALUE
'=================================================='.
05 FILLER PIC X(50) VALUE
'=================================================='.
05 FILLER PIC X(32) VALUE SPACES.
01 WS-REPORT-HEADER-2.
05 FILLER PIC X(30) VALUE ' LOAN AMORTIZATION SCHEDULE '.
05 FILLER PIC X(102) VALUE SPACES.
01 WS-LOAN-INFO-1.
05 FILLER PIC X(16) VALUE ' LOAN AMOUNT: '.
05 WS-INFO-AMOUNT PIC $ZZZ,ZZZ,ZZ9.99.
05 FILLER PIC X(10) VALUE ' TYPE: '.
05 WS-INFO-TYPE PIC X(10).
05 FILLER PIC X(80) VALUE SPACES.
01 WS-LOAN-INFO-2.
05 FILLER PIC X(16) VALUE ' ANNUAL RATE: '.
05 WS-INFO-RATE PIC Z9.999999.
05 FILLER PIC X(12) VALUE '% TERM: '.
05 WS-INFO-TERM PIC ZZ9.
05 FILLER PIC X(7) VALUE ' MONTHS'.
05 FILLER PIC X(80) VALUE SPACES.
01 WS-LOAN-INFO-3.
05 FILLER PIC X(20) VALUE ' MONTHLY PAYMENT: '.
05 WS-INFO-PAYMENT PIC $ZZZ,ZZ9.99.
05 FILLER PIC X(100) VALUE SPACES.
01 WS-COLUMN-HEADER.
05 FILLER PIC X(8) VALUE ' MONTH '.
05 FILLER PIC X(16) VALUE ' PAYMENT '.
05 FILLER PIC X(16) VALUE ' INTEREST '.
05 FILLER PIC X(16) VALUE ' PRINCIPAL '.
05 FILLER PIC X(20) VALUE ' REMAINING BALANCE '.
05 FILLER PIC X(56) VALUE SPACES.
01 WS-DETAIL-LINE.
05 FILLER PIC X(2) VALUE SPACES.
05 WS-DET-MONTH PIC ZZ9.
05 FILLER PIC X(3) VALUE SPACES.
05 WS-DET-PAYMENT PIC $ZZZ,ZZ9.99.
05 FILLER PIC X(4) VALUE SPACES.
05 WS-DET-INTEREST PIC $ZZZ,ZZ9.99.
05 FILLER PIC X(4) VALUE SPACES.
05 WS-DET-PRINCIPAL PIC $ZZZ,ZZ9.99.
05 FILLER PIC X(4) VALUE SPACES.
05 WS-DET-BALANCE PIC $ZZZ,ZZZ,ZZ9.99.
05 FILLER PIC X(54) VALUE SPACES.
01 WS-TOTAL-LINE.
05 FILLER PIC X(5) VALUE ' TOT'.
05 FILLER PIC X(3) VALUE SPACES.
05 WS-TOT-PAID PIC $ZZZ,ZZZ,ZZ9.99.
05 FILLER PIC X(1) VALUE SPACES.
05 WS-TOT-INTEREST PIC $ZZZ,ZZZ,ZZ9.99.
05 FILLER PIC X(1) VALUE SPACES.
05 WS-TOT-PRINCIPAL PIC $ZZZ,ZZZ,ZZ9.99.
05 FILLER PIC X(69) VALUE SPACES.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
OPEN OUTPUT AMORT-REPORT
* Example: $250,000 mortgage at 6.50% for 30 years
MOVE 250000.00 TO WS-LOAN-AMOUNT
MOVE 6.500000 TO WS-ANNUAL-RATE
MOVE 360 TO WS-TERM-MONTHS
MOVE 'MORTGAGE' TO WS-LOAN-TYPE
PERFORM 1000-CALCULATE-PAYMENT
PERFORM 2000-GENERATE-SCHEDULE
PERFORM 3000-WRITE-TOTALS
CLOSE AMORT-REPORT
DISPLAY 'AMORTIZATION SCHEDULE GENERATED SUCCESSFULLY'
STOP RUN
.
1000-CALCULATE-PAYMENT.
* Monthly rate = annual rate / 100 / 12
DIVIDE WS-ANNUAL-RATE BY 100
GIVING WS-MONTHLY-RATE
DIVIDE WS-MONTHLY-RATE BY 12
GIVING WS-MONTHLY-RATE ROUNDED
* Growth factor = 1 + monthly rate
ADD 1 TO WS-MONTHLY-RATE
GIVING WS-GROWTH-FACTOR
* Calculate (1 + r)^n iteratively
MOVE 1.0 TO WS-POWER-RESULT
PERFORM VARYING WS-PERIOD-CTR FROM 1 BY 1
UNTIL WS-PERIOD-CTR > WS-TERM-MONTHS
MULTIPLY WS-POWER-RESULT BY WS-GROWTH-FACTOR
GIVING WS-POWER-RESULT ROUNDED
END-PERFORM
* Numerator = monthly rate * (1 + r)^n
MULTIPLY WS-MONTHLY-RATE BY WS-POWER-RESULT
GIVING WS-NUMERATOR
* Denominator = (1 + r)^n - 1
SUBTRACT 1 FROM WS-POWER-RESULT
GIVING WS-DENOMINATOR
* Monthly payment = principal * numerator / denominator
MULTIPLY WS-LOAN-AMOUNT BY WS-NUMERATOR
GIVING WS-MONTHLY-PAYMENT
DIVIDE WS-MONTHLY-PAYMENT BY WS-DENOMINATOR
GIVING WS-MONTHLY-PAYMENT ROUNDED
* Write header information
WRITE REPORT-LINE FROM WS-REPORT-HEADER-1
WRITE REPORT-LINE FROM WS-REPORT-HEADER-2
WRITE REPORT-LINE FROM WS-REPORT-HEADER-1
MOVE WS-LOAN-AMOUNT TO WS-INFO-AMOUNT
MOVE WS-LOAN-TYPE TO WS-INFO-TYPE
WRITE REPORT-LINE FROM WS-LOAN-INFO-1
MOVE WS-ANNUAL-RATE TO WS-INFO-RATE
MOVE WS-TERM-MONTHS TO WS-INFO-TERM
WRITE REPORT-LINE FROM WS-LOAN-INFO-2
MOVE WS-MONTHLY-PAYMENT TO WS-INFO-PAYMENT
WRITE REPORT-LINE FROM WS-LOAN-INFO-3
WRITE REPORT-LINE FROM WS-REPORT-HEADER-1
WRITE REPORT-LINE FROM WS-COLUMN-HEADER
.
2000-GENERATE-SCHEDULE.
MOVE WS-LOAN-AMOUNT TO WS-REMAINING-BAL
MOVE ZERO TO WS-TOTAL-INTEREST
MOVE ZERO TO WS-TOTAL-PRINCIPAL
MOVE ZERO TO WS-TOTAL-PAID
PERFORM VARYING WS-PERIOD-CTR FROM 1 BY 1
UNTIL WS-PERIOD-CTR > WS-TERM-MONTHS
* Interest for this period = balance * monthly rate
MULTIPLY WS-REMAINING-BAL BY WS-MONTHLY-RATE
GIVING WS-INTEREST-WORK ROUNDED
MOVE WS-INTEREST-WORK TO WS-INTEREST-PORTION
* Principal for this period = payment - interest
SUBTRACT WS-INTEREST-PORTION FROM WS-MONTHLY-PAYMENT
GIVING WS-PRINCIPAL-PORTION
* Handle final payment adjustment for rounding
IF WS-PERIOD-CTR = WS-TERM-MONTHS
MOVE WS-REMAINING-BAL TO WS-PRINCIPAL-PORTION
ADD WS-INTEREST-PORTION TO WS-PRINCIPAL-PORTION
GIVING WS-MONTHLY-PAYMENT
END-IF
* Update remaining balance
SUBTRACT WS-PRINCIPAL-PORTION FROM WS-REMAINING-BAL
* 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-PAID
* Format and write detail line
MOVE WS-PERIOD-CTR TO WS-DET-MONTH
MOVE WS-MONTHLY-PAYMENT TO WS-DET-PAYMENT
MOVE WS-INTEREST-PORTION TO WS-DET-INTEREST
MOVE WS-PRINCIPAL-PORTION TO WS-DET-PRINCIPAL
MOVE WS-REMAINING-BAL TO WS-DET-BALANCE
WRITE REPORT-LINE FROM WS-DETAIL-LINE
END-PERFORM
.
3000-WRITE-TOTALS.
WRITE REPORT-LINE FROM WS-REPORT-HEADER-1
MOVE WS-TOTAL-PAID TO WS-TOT-PAID
MOVE WS-TOTAL-INTEREST TO WS-TOT-INTEREST
MOVE WS-TOTAL-PRINCIPAL TO WS-TOT-PRINCIPAL
WRITE REPORT-LINE FROM WS-TOTAL-LINE
DISPLAY 'TOTAL PAID: ' WS-TOTAL-PAID
DISPLAY 'TOTAL INTEREST: ' WS-TOTAL-INTEREST
DISPLAY 'TOTAL PRINCIPAL: ' WS-TOTAL-PRINCIPAL
.
The JCL to execute this amortization program on an IBM mainframe would be:
//AMORTJOB JOB (ACCT),'AMORT SCHEDULE',CLASS=A,
// MSGCLASS=X,NOTIFY=&SYSUID
//*
//STEP01 EXEC PGM=AMORTSCH
//STEPLIB DD DSN=PROD.FINANCE.LOADLIB,DISP=SHR
//AMORTOUT DD DSN=PROD.FINANCE.AMORT.REPORT,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(5,2),RLSE),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//SYSOUT DD SYSOUT=*
//SYSPRINT DD SYSOUT=*
Auto Loan vs. Mortgage Differences
While both use the same fundamental amortization formula, auto loans and mortgages differ in several practical aspects that affect COBOL programs:
- Term length: Mortgages typically span 180-360 months; auto loans span 36-84 months.
- Prepayment handling: Mortgage systems must track prepayment penalties; auto loans rarely have them.
- Escrow: Mortgage payments often include escrow for taxes and insurance, requiring additional calculation fields.
- Day count: Some auto loans use Actual/365 day count while most mortgages use 30/360.
33.5 Currency Handling
The ROUNDED Phrase
COBOL's ROUNDED phrase is the primary mechanism for controlling how arithmetic results are truncated to fit the receiving field. Without ROUNDED, COBOL truncates (chops off extra digits). With ROUNDED, COBOL applies rounding rules.
As discussed in Chapter 6 (Arithmetic Operations), the basic ROUNDED phrase performs standard arithmetic rounding (round half up):
* Without ROUNDED - truncation
DIVIDE 10.00 BY 3 GIVING WS-RESULT
* WS-RESULT = 3.33 (truncated from 3.333...)
* With ROUNDED - standard rounding
DIVIDE 10.00 BY 3 GIVING WS-RESULT ROUNDED
* WS-RESULT = 3.33 (rounded from 3.333...)
* Key difference example:
DIVIDE 2.00 BY 3 GIVING WS-RESULT
* WS-RESULT = 0.66 (truncated from 0.666...)
DIVIDE 2.00 BY 3 GIVING WS-RESULT ROUNDED
* WS-RESULT = 0.67 (rounded from 0.666...)
COBOL 2002 Rounding Modes
The COBOL 2002 and later standards introduced explicit rounding mode specification. Financial systems often require specific rounding modes for regulatory compliance:
* COBOL 2002+ rounding modes
DIVIDE WS-A BY WS-B GIVING WS-C
ROUNDED MODE IS TRUNCATION
* Always truncate toward zero
DIVIDE WS-A BY WS-B GIVING WS-C
ROUNDED MODE IS AWAY-FROM-ZERO
* Always round away from zero
DIVIDE WS-A BY WS-B GIVING WS-C
ROUNDED MODE IS NEAREST-EVEN
* Banker's rounding - round half to nearest even digit
DIVIDE WS-A BY WS-B GIVING WS-C
ROUNDED MODE IS NEAREST-AWAY-FROM-ZERO
* Traditional rounding - round half away from zero
Banker's Rounding Implementation
Banker's rounding (round half to even) eliminates the slight upward bias of standard rounding. When the value is exactly halfway between two representable values, it rounds to the nearest even digit. This is critical for high-volume financial processing where systematic bias would accumulate.
For compilers that do not support the COBOL 2002 rounding modes, you can implement banker's rounding manually:
IDENTIFICATION DIVISION.
PROGRAM-ID. BANKERS-ROUND.
*================================================================*
* PROGRAM: BANKERS-ROUND *
* PURPOSE: Implement banker's rounding (round half to even) for *
* compilers without COBOL 2002 rounding modes *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-INPUT-VALUE PIC S9(13)V9(4) COMP-3.
01 WS-OUTPUT-VALUE PIC S9(13)V99 COMP-3.
01 WS-WORK-FIELDS.
05 WS-TRUNCATED PIC S9(13)V99 COMP-3.
05 WS-REMAINDER PIC S9(3)V9(4) COMP-3.
05 WS-HALF-CHECK PIC S9(3)V9(4) COMP-3.
05 WS-LAST-DIGIT PIC S9(1) COMP-3.
05 WS-ROUNDED-UP PIC S9(13)V99 COMP-3.
05 WS-SIGN-SAVE PIC S9(1) COMP-3.
05 WS-ABS-VALUE PIC S9(13)V9(4) COMP-3.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
* Test cases for banker's rounding
MOVE 2.505 TO WS-INPUT-VALUE
PERFORM 1000-BANKERS-ROUND
DISPLAY 'Input: 2.505 -> ' WS-OUTPUT-VALUE
* Expected: 2.50 (5 rounds to even, 0 is even)
MOVE 2.515 TO WS-INPUT-VALUE
PERFORM 1000-BANKERS-ROUND
DISPLAY 'Input: 2.515 -> ' WS-OUTPUT-VALUE
* Expected: 2.52 (5 rounds to even, 2 is even)
MOVE 2.525 TO WS-INPUT-VALUE
PERFORM 1000-BANKERS-ROUND
DISPLAY 'Input: 2.525 -> ' WS-OUTPUT-VALUE
* Expected: 2.52 (5 rounds to even, 2 is even)
MOVE 2.535 TO WS-INPUT-VALUE
PERFORM 1000-BANKERS-ROUND
DISPLAY 'Input: 2.535 -> ' WS-OUTPUT-VALUE
* Expected: 2.54 (5 rounds to even, 4 is even)
MOVE 2.545 TO WS-INPUT-VALUE
PERFORM 1000-BANKERS-ROUND
DISPLAY 'Input: 2.545 -> ' WS-OUTPUT-VALUE
* Expected: 2.54 (5 rounds to even, 4 is even)
MOVE 2.546 TO WS-INPUT-VALUE
PERFORM 1000-BANKERS-ROUND
DISPLAY 'Input: 2.546 -> ' WS-OUTPUT-VALUE
* Expected: 2.55 (above halfway, round up)
STOP RUN
.
1000-BANKERS-ROUND.
* Handle sign
IF WS-INPUT-VALUE < 0
MULTIPLY WS-INPUT-VALUE BY -1
GIVING WS-ABS-VALUE
MOVE -1 TO WS-SIGN-SAVE
ELSE
MOVE WS-INPUT-VALUE TO WS-ABS-VALUE
MOVE 1 TO WS-SIGN-SAVE
END-IF
* Truncate to target precision (2 decimal places)
MOVE WS-ABS-VALUE TO WS-TRUNCATED
* Get the remainder beyond 2 decimal places
SUBTRACT WS-TRUNCATED FROM WS-ABS-VALUE
GIVING WS-REMAINDER
* Check if remainder is exactly 0.005 (halfway)
MOVE 0.005 TO WS-HALF-CHECK
EVALUATE TRUE
* Below halfway - truncate (already done)
WHEN WS-REMAINDER < WS-HALF-CHECK
MOVE WS-TRUNCATED TO WS-OUTPUT-VALUE
* Above halfway - round up
WHEN WS-REMAINDER > WS-HALF-CHECK
ADD 0.01 TO WS-TRUNCATED
GIVING WS-OUTPUT-VALUE
* Exactly halfway - round to even
WHEN OTHER
DIVIDE WS-TRUNCATED BY 0.02
GIVING WS-LAST-DIGIT
REMAINDER WS-REMAINDER
IF WS-REMAINDER = ZERO
* Last cent digit is even - truncate
MOVE WS-TRUNCATED TO WS-OUTPUT-VALUE
ELSE
* Last cent digit is odd - round up
ADD 0.01 TO WS-TRUNCATED
GIVING WS-OUTPUT-VALUE
END-IF
END-EVALUATE
* Restore sign
IF WS-SIGN-SAVE = -1
MULTIPLY WS-OUTPUT-VALUE BY -1
GIVING WS-OUTPUT-VALUE
END-IF
.
Multi-Currency Handling
International financial systems must handle multiple currencies with different decimal precision (USD uses 2 decimals, JPY uses 0, BHD uses 3) and exchange rate conversions:
01 WS-CURRENCY-TABLE.
05 WS-CURRENCY-ENTRY OCCURS 10 TIMES.
10 WS-CURR-CODE PIC X(3).
10 WS-CURR-DECIMALS PIC 9(1).
10 WS-CURR-RATE PIC S9(5)V9(8) COMP-3.
10 WS-CURR-NAME PIC X(20).
01 WS-CONVERSION-FIELDS.
05 WS-SOURCE-AMOUNT PIC S9(15)V9(4) COMP-3.
05 WS-TARGET-AMOUNT PIC S9(15)V9(4) COMP-3.
05 WS-BASE-AMOUNT PIC S9(15)V9(8) COMP-3.
05 WS-SOURCE-IDX PIC S9(3) COMP.
05 WS-TARGET-IDX PIC S9(3) COMP.
* Convert between any two currencies via USD base
1000-CONVERT-CURRENCY.
* Step 1: Convert source to USD base
DIVIDE WS-SOURCE-AMOUNT
BY WS-CURR-RATE(WS-SOURCE-IDX)
GIVING WS-BASE-AMOUNT ROUNDED
* Step 2: Convert USD base to target currency
MULTIPLY WS-BASE-AMOUNT
BY WS-CURR-RATE(WS-TARGET-IDX)
GIVING WS-TARGET-AMOUNT ROUNDED
.
33.6 Tax Calculations
Tax calculations are among the most complex financial computations because tax law creates intricate rules with brackets, phase-outs, credits, and special cases. COBOL's structured approach to data and its precise arithmetic make it well-suited for these calculations.
Progressive Tax Bracket Calculation
The US federal income tax system uses progressive brackets where different portions of income are taxed at different rates. The following program implements a complete progressive tax calculation:
IDENTIFICATION DIVISION.
PROGRAM-ID. TAX-CALC.
*================================================================*
* PROGRAM: TAX-CALC *
* PURPOSE: Calculate progressive federal income tax using *
* bracket-based rates with table-driven approach *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TAX-BRACKETS.
* 2024 Single Filer Tax Brackets
* Format: Lower-Limit(11), Upper-Limit(11), Rate(7)
05 FILLER PIC X(29) VALUE '0000000000001150000000010.000'.
05 FILLER PIC X(29) VALUE '0000001150100468750000012.000'.
05 FILLER PIC X(29) VALUE '0000004687501005250000022.000'.
05 FILLER PIC X(29) VALUE '0000010052501911750000024.000'.
05 FILLER PIC X(29) VALUE '0000019117502437500000032.000'.
05 FILLER PIC X(29) VALUE '0000024375005781250000035.000'.
05 FILLER PIC X(29) VALUE '0000057812509999999999937.000'.
01 WS-BRACKET-TABLE REDEFINES WS-TAX-BRACKETS.
05 WS-BRACKET OCCURS 7 TIMES.
10 WS-BRACKET-LOWER PIC 9(11).
10 WS-BRACKET-UPPER PIC 9(11).
10 WS-BRACKET-RATE PIC 99V999.
01 WS-TAX-INPUT.
05 WS-GROSS-INCOME PIC S9(11)V99 COMP-3.
05 WS-STANDARD-DEDUCTION PIC S9(11)V99 COMP-3.
05 WS-TAXABLE-INCOME PIC S9(11)V99 COMP-3.
01 WS-TAX-CALC.
05 WS-BRACKET-IDX PIC S9(3) COMP.
05 WS-NUM-BRACKETS PIC S9(3) COMP VALUE 7.
05 WS-BRACKET-AMOUNT PIC S9(11)V99 COMP-3.
05 WS-BRACKET-TAX PIC S9(11)V9(4) COMP-3.
05 WS-TOTAL-TAX PIC S9(11)V99 COMP-3.
05 WS-EFFECTIVE-RATE PIC S9(3)V9(4) COMP-3.
05 WS-REMAINING-INCOME PIC S9(11)V99 COMP-3.
05 WS-BRACKET-WIDTH PIC S9(11)V99 COMP-3.
01 WS-DISPLAY-FIELDS.
05 WS-DISP-INCOME PIC $ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-DEDUCTION PIC $ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-TAXABLE PIC $ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-TAX PIC $ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-RATE PIC Z9.9999.
05 WS-DISP-BRKT-AMT PIC $ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-BRKT-TAX PIC $ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-BRKT-RATE PIC Z9.999.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
* Example: $95,000 gross income, single filer
MOVE 95000.00 TO WS-GROSS-INCOME
MOVE 14600.00 TO WS-STANDARD-DEDUCTION
* Calculate taxable income
SUBTRACT WS-STANDARD-DEDUCTION FROM WS-GROSS-INCOME
GIVING WS-TAXABLE-INCOME
IF WS-TAXABLE-INCOME < 0
MOVE ZERO TO WS-TAXABLE-INCOME
END-IF
PERFORM 1000-CALCULATE-TAX
MOVE WS-GROSS-INCOME TO WS-DISP-INCOME
MOVE WS-STANDARD-DEDUCTION TO WS-DISP-DEDUCTION
MOVE WS-TAXABLE-INCOME TO WS-DISP-TAXABLE
MOVE WS-TOTAL-TAX TO WS-DISP-TAX
* Effective rate = total tax / gross income * 100
IF WS-GROSS-INCOME > ZERO
DIVIDE WS-TOTAL-TAX BY WS-GROSS-INCOME
GIVING WS-EFFECTIVE-RATE
MULTIPLY WS-EFFECTIVE-RATE BY 100
GIVING WS-EFFECTIVE-RATE
END-IF
MOVE WS-EFFECTIVE-RATE TO WS-DISP-RATE
DISPLAY '=================================='
DISPLAY 'FEDERAL INCOME TAX CALCULATION'
DISPLAY '=================================='
DISPLAY 'GROSS INCOME: ' WS-DISP-INCOME
DISPLAY 'STANDARD DEDUCTION: ' WS-DISP-DEDUCTION
DISPLAY 'TAXABLE INCOME: ' WS-DISP-TAXABLE
DISPLAY 'TOTAL TAX: ' WS-DISP-TAX
DISPLAY 'EFFECTIVE RATE: ' WS-DISP-RATE '%'
STOP RUN
.
1000-CALCULATE-TAX.
MOVE ZERO TO WS-TOTAL-TAX
MOVE WS-TAXABLE-INCOME TO WS-REMAINING-INCOME
PERFORM VARYING WS-BRACKET-IDX FROM 1 BY 1
UNTIL WS-BRACKET-IDX > WS-NUM-BRACKETS
OR WS-REMAINING-INCOME <= ZERO
* Calculate bracket width
SUBTRACT WS-BRACKET-LOWER(WS-BRACKET-IDX)
FROM WS-BRACKET-UPPER(WS-BRACKET-IDX)
GIVING WS-BRACKET-WIDTH
* Amount taxed in this bracket is the lesser of
* remaining income or bracket width
IF WS-REMAINING-INCOME <= WS-BRACKET-WIDTH
MOVE WS-REMAINING-INCOME TO WS-BRACKET-AMOUNT
ELSE
MOVE WS-BRACKET-WIDTH TO WS-BRACKET-AMOUNT
END-IF
* Calculate tax for this bracket
MULTIPLY WS-BRACKET-AMOUNT
BY WS-BRACKET-RATE(WS-BRACKET-IDX)
GIVING WS-BRACKET-TAX
DIVIDE WS-BRACKET-TAX BY 100
GIVING WS-BRACKET-TAX ROUNDED
* Display bracket detail
MOVE WS-BRACKET-AMOUNT TO WS-DISP-BRKT-AMT
MOVE WS-BRACKET-TAX TO WS-DISP-BRKT-TAX
MOVE WS-BRACKET-RATE(WS-BRACKET-IDX)
TO WS-DISP-BRKT-RATE
DISPLAY ' BRACKET ' WS-BRACKET-IDX ': '
WS-DISP-BRKT-AMT ' @ '
WS-DISP-BRKT-RATE '% = '
WS-DISP-BRKT-TAX
* Accumulate and reduce remaining
ADD WS-BRACKET-TAX TO WS-TOTAL-TAX
SUBTRACT WS-BRACKET-AMOUNT FROM WS-REMAINING-INCOME
END-PERFORM
.
Payroll Withholding Calculation
Payroll systems must calculate federal and state income tax withholding, Social Security tax (FICA), and Medicare tax for each pay period:
IDENTIFICATION DIVISION.
PROGRAM-ID. PAYROLL-TAX.
*================================================================*
* PROGRAM: PAYROLL-TAX *
* PURPOSE: Calculate payroll tax withholdings including federal, *
* FICA, and Medicare taxes *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-EMPLOYEE-DATA.
05 WS-EMP-ID PIC X(10).
05 WS-GROSS-PAY PIC S9(9)V99 COMP-3.
05 WS-PAY-FREQUENCY PIC X(1).
88 PAY-WEEKLY VALUE 'W'.
88 PAY-BIWEEKLY VALUE 'B'.
88 PAY-SEMIMONTHLY VALUE 'S'.
88 PAY-MONTHLY VALUE 'M'.
05 WS-FILING-STATUS PIC X(1).
88 STATUS-SINGLE VALUE 'S'.
88 STATUS-MARRIED VALUE 'M'.
05 WS-YTD-GROSS PIC S9(11)V99 COMP-3.
05 WS-YTD-FICA-WAGES PIC S9(11)V99 COMP-3.
01 WS-TAX-CONSTANTS.
05 WS-FICA-RATE PIC S9(3)V9(4) COMP-3
VALUE 6.2000.
05 WS-FICA-WAGE-LIMIT PIC S9(11)V99 COMP-3
VALUE 168600.00.
05 WS-MEDICARE-RATE PIC S9(3)V9(4) COMP-3
VALUE 1.4500.
05 WS-ADDL-MEDICARE-RATE PIC S9(3)V9(4) COMP-3
VALUE 0.9000.
05 WS-ADDL-MEDICARE-THRESH PIC S9(11)V99 COMP-3
VALUE 200000.00.
01 WS-CALC-FIELDS.
05 WS-ANNUALIZED-PAY PIC S9(11)V99 COMP-3.
05 WS-FICA-TAXABLE PIC S9(11)V99 COMP-3.
05 WS-FICA-TAX PIC S9(9)V99 COMP-3.
05 WS-MEDICARE-TAX PIC S9(9)V99 COMP-3.
05 WS-ADDL-MEDICARE-TAX PIC S9(9)V99 COMP-3.
05 WS-FEDERAL-TAX PIC S9(9)V99 COMP-3.
05 WS-TOTAL-TAX PIC S9(9)V99 COMP-3.
05 WS-NET-PAY PIC S9(9)V99 COMP-3.
05 WS-NEW-YTD-FICA PIC S9(11)V99 COMP-3.
05 WS-EXCESS-FICA PIC S9(11)V99 COMP-3.
05 WS-WORK-AMT PIC S9(11)V9(4) COMP-3.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
* Example: Biweekly pay, $3,846.15 gross
MOVE 'E001234567' TO WS-EMP-ID
MOVE 3846.15 TO WS-GROSS-PAY
SET PAY-BIWEEKLY TO TRUE
SET STATUS-SINGLE TO TRUE
MOVE 88461.45 TO WS-YTD-GROSS
MOVE 88461.45 TO WS-YTD-FICA-WAGES
PERFORM 1000-CALC-FICA
PERFORM 2000-CALC-MEDICARE
PERFORM 3000-CALC-FEDERAL
ADD WS-FICA-TAX WS-MEDICARE-TAX WS-ADDL-MEDICARE-TAX
WS-FEDERAL-TAX
GIVING WS-TOTAL-TAX
SUBTRACT WS-TOTAL-TAX FROM WS-GROSS-PAY
GIVING WS-NET-PAY
DISPLAY 'PAYROLL TAX SUMMARY FOR: ' WS-EMP-ID
DISPLAY 'GROSS PAY: ' WS-GROSS-PAY
DISPLAY 'FICA TAX: ' WS-FICA-TAX
DISPLAY 'MEDICARE TAX: ' WS-MEDICARE-TAX
DISPLAY 'ADDL MEDICARE: ' WS-ADDL-MEDICARE-TAX
DISPLAY 'FEDERAL TAX: ' WS-FEDERAL-TAX
DISPLAY 'TOTAL TAX: ' WS-TOTAL-TAX
DISPLAY 'NET PAY: ' WS-NET-PAY
STOP RUN
.
1000-CALC-FICA.
* Check if employee has reached FICA wage limit
ADD WS-YTD-FICA-WAGES WS-GROSS-PAY
GIVING WS-NEW-YTD-FICA
IF WS-YTD-FICA-WAGES >= WS-FICA-WAGE-LIMIT
* Already exceeded limit - no FICA tax
MOVE ZERO TO WS-FICA-TAX
ELSE IF WS-NEW-YTD-FICA > WS-FICA-WAGE-LIMIT
* This paycheck crosses the limit
SUBTRACT WS-YTD-FICA-WAGES FROM WS-FICA-WAGE-LIMIT
GIVING WS-FICA-TAXABLE
MULTIPLY WS-FICA-TAXABLE BY WS-FICA-RATE
GIVING WS-WORK-AMT
DIVIDE WS-WORK-AMT BY 100
GIVING WS-FICA-TAX ROUNDED
ELSE
* Full paycheck is taxable
MULTIPLY WS-GROSS-PAY BY WS-FICA-RATE
GIVING WS-WORK-AMT
DIVIDE WS-WORK-AMT BY 100
GIVING WS-FICA-TAX ROUNDED
END-IF
.
2000-CALC-MEDICARE.
* Standard Medicare tax on all wages
MULTIPLY WS-GROSS-PAY BY WS-MEDICARE-RATE
GIVING WS-WORK-AMT
DIVIDE WS-WORK-AMT BY 100
GIVING WS-MEDICARE-TAX ROUNDED
* Additional Medicare tax for high earners
ADD WS-YTD-GROSS WS-GROSS-PAY
GIVING WS-WORK-AMT
IF WS-WORK-AMT > WS-ADDL-MEDICARE-THRESH
IF WS-YTD-GROSS >= WS-ADDL-MEDICARE-THRESH
* All of this paycheck is subject
MULTIPLY WS-GROSS-PAY BY WS-ADDL-MEDICARE-RATE
GIVING WS-WORK-AMT
DIVIDE WS-WORK-AMT BY 100
GIVING WS-ADDL-MEDICARE-TAX ROUNDED
ELSE
* Only the amount over threshold is subject
SUBTRACT WS-ADDL-MEDICARE-THRESH
FROM WS-WORK-AMT
GIVING WS-WORK-AMT
MULTIPLY WS-WORK-AMT BY WS-ADDL-MEDICARE-RATE
GIVING WS-WORK-AMT
DIVIDE WS-WORK-AMT BY 100
GIVING WS-ADDL-MEDICARE-TAX ROUNDED
END-IF
ELSE
MOVE ZERO TO WS-ADDL-MEDICARE-TAX
END-IF
.
3000-CALC-FEDERAL.
* Simplified federal withholding calculation
* (In production, this would use IRS Publication 15-T tables)
MOVE 500.00 TO WS-FEDERAL-TAX
.
Sales Tax Calculation
Sales tax calculations handle varying tax rates by jurisdiction, tax-exempt items, and combined state/county/city rates:
01 WS-SALES-TAX-TABLE.
05 WS-JURISDICTION OCCURS 50 TIMES.
10 WS-JURIS-CODE PIC X(5).
10 WS-STATE-RATE PIC S9(3)V9(4) COMP-3.
10 WS-COUNTY-RATE PIC S9(3)V9(4) COMP-3.
10 WS-CITY-RATE PIC S9(3)V9(4) COMP-3.
10 WS-SPECIAL-RATE PIC S9(3)V9(4) COMP-3.
01 WS-SALES-TAX-CALC.
05 WS-ITEM-AMOUNT PIC S9(9)V99 COMP-3.
05 WS-COMBINED-RATE PIC S9(3)V9(4) COMP-3.
05 WS-TAX-AMOUNT PIC S9(9)V99 COMP-3.
05 WS-TOTAL-AMOUNT PIC S9(9)V99 COMP-3.
1000-CALC-SALES-TAX.
* Combine all applicable rates
ADD WS-STATE-RATE(WS-JURIS-IDX)
WS-COUNTY-RATE(WS-JURIS-IDX)
WS-CITY-RATE(WS-JURIS-IDX)
WS-SPECIAL-RATE(WS-JURIS-IDX)
GIVING WS-COMBINED-RATE
* Calculate tax amount
MULTIPLY WS-ITEM-AMOUNT BY WS-COMBINED-RATE
GIVING WS-TAX-AMOUNT
DIVIDE WS-TAX-AMOUNT BY 100
GIVING WS-TAX-AMOUNT ROUNDED
* Total with tax
ADD WS-ITEM-AMOUNT WS-TAX-AMOUNT
GIVING WS-TOTAL-AMOUNT
.
33.7 Financial Date Calculations
Financial systems use specific conventions for counting days between dates. These conventions, called day count conventions, directly affect interest calculations. Different financial instruments use different conventions, and using the wrong one produces incorrect interest amounts.
Day Count Conventions
The four primary day count conventions are:
-
30/360 (Bond Basis): Assumes every month has 30 days and the year has 360 days. Used for most US corporate and municipal bonds.
-
Actual/360 (Money Market): Uses the actual number of days between dates but divides by 360. Used for US money market instruments and commercial loans.
-
Actual/365 (Fixed): Uses actual days between dates divided by 365. Used for many government bonds and some commercial loans.
-
Actual/Actual (ISDA): Uses actual days in the actual year (365 or 366 for leap years). Used for US Treasury bonds.
Day Count Convention Program
IDENTIFICATION DIVISION.
PROGRAM-ID. DAY-COUNT.
*================================================================*
* PROGRAM: DAY-COUNT *
* PURPOSE: Implement financial day count conventions for *
* interest accrual calculations *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DATE-FIELDS.
05 WS-START-DATE.
10 WS-START-YEAR PIC 9(4).
10 WS-START-MONTH PIC 9(2).
10 WS-START-DAY PIC 9(2).
05 WS-END-DATE.
10 WS-END-YEAR PIC 9(4).
10 WS-END-MONTH PIC 9(2).
10 WS-END-DAY PIC 9(2).
01 WS-CALC-FIELDS.
05 WS-ACTUAL-DAYS PIC S9(7) COMP-3.
05 WS-30-360-DAYS PIC S9(7) COMP-3.
05 WS-YEAR-BASIS PIC S9(3) COMP-3.
05 WS-DAY-FRACTION PIC S9(3)V9(10) COMP-3.
05 WS-D1 PIC S9(3) COMP-3.
05 WS-D2 PIC S9(3) COMP-3.
05 WS-M1 PIC S9(3) COMP-3.
05 WS-M2 PIC S9(3) COMP-3.
05 WS-Y1 PIC S9(5) COMP-3.
05 WS-Y2 PIC S9(5) COMP-3.
01 WS-INTEGER-DATE-1 PIC S9(7) COMP-3.
01 WS-INTEGER-DATE-2 PIC S9(7) COMP-3.
01 WS-LEAP-FLAG PIC X VALUE 'N'.
88 IS-LEAP-YEAR VALUE 'Y'.
01 WS-DAYS-PER-MONTH.
05 FILLER PIC 9(2) VALUE 31.
05 FILLER PIC 9(2) VALUE 28.
05 FILLER PIC 9(2) VALUE 31.
05 FILLER PIC 9(2) VALUE 30.
05 FILLER PIC 9(2) VALUE 31.
05 FILLER PIC 9(2) VALUE 30.
05 FILLER PIC 9(2) VALUE 31.
05 FILLER PIC 9(2) VALUE 31.
05 FILLER PIC 9(2) VALUE 30.
05 FILLER PIC 9(2) VALUE 31.
05 FILLER PIC 9(2) VALUE 30.
05 FILLER PIC 9(2) VALUE 31.
01 WS-DAYS-TABLE REDEFINES WS-DAYS-PER-MONTH.
05 WS-MONTH-DAYS PIC 9(2) OCCURS 12 TIMES.
01 WS-WORK-FIELDS.
05 WS-MONTH-IDX PIC S9(3) COMP-3.
05 WS-YEAR-IDX PIC S9(5) COMP-3.
05 WS-WORK-DAYS PIC S9(7) COMP-3.
05 WS-REMAIN-4 PIC S9(5) COMP-3.
05 WS-REMAIN-100 PIC S9(5) COMP-3.
05 WS-REMAIN-400 PIC S9(5) COMP-3.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
* Example: Calculate day fractions from Jan 15, 2024
* to Jul 15, 2024
MOVE '20240115' TO WS-START-DATE
MOVE '20240715' TO WS-END-DATE
PERFORM 1000-CALC-30-360
DISPLAY '30/360 DAYS: ' WS-30-360-DAYS
MOVE 360 TO WS-YEAR-BASIS
DIVIDE WS-30-360-DAYS BY WS-YEAR-BASIS
GIVING WS-DAY-FRACTION
DISPLAY '30/360 FRACTION: ' WS-DAY-FRACTION
PERFORM 2000-CALC-ACTUAL-DAYS
DISPLAY 'ACTUAL DAYS: ' WS-ACTUAL-DAYS
MOVE 360 TO WS-YEAR-BASIS
DIVIDE WS-ACTUAL-DAYS BY WS-YEAR-BASIS
GIVING WS-DAY-FRACTION
DISPLAY 'ACT/360 FRACTION:' WS-DAY-FRACTION
MOVE 365 TO WS-YEAR-BASIS
DIVIDE WS-ACTUAL-DAYS BY WS-YEAR-BASIS
GIVING WS-DAY-FRACTION
DISPLAY 'ACT/365 FRACTION:' WS-DAY-FRACTION
STOP RUN
.
1000-CALC-30-360.
* US 30/360 (NASD method)
MOVE WS-START-DAY TO WS-D1
MOVE WS-END-DAY TO WS-D2
MOVE WS-START-MONTH TO WS-M1
MOVE WS-END-MONTH TO WS-M2
MOVE WS-START-YEAR TO WS-Y1
MOVE WS-END-YEAR TO WS-Y2
* If D1 is 31, change to 30
IF WS-D1 = 31
MOVE 30 TO WS-D1
END-IF
* If D2 is 31 and D1 is 30 or 31, change D2 to 30
IF WS-D2 = 31 AND WS-D1 = 30
MOVE 30 TO WS-D2
END-IF
* 30/360 days = (Y2-Y1)*360 + (M2-M1)*30 + (D2-D1)
SUBTRACT WS-Y1 FROM WS-Y2 GIVING WS-30-360-DAYS
MULTIPLY WS-30-360-DAYS BY 360
SUBTRACT WS-M1 FROM WS-M2 GIVING WS-WORK-DAYS
MULTIPLY WS-WORK-DAYS BY 30
ADD WS-WORK-DAYS TO WS-30-360-DAYS
SUBTRACT WS-D1 FROM WS-D2 GIVING WS-WORK-DAYS
ADD WS-WORK-DAYS TO WS-30-360-DAYS
.
2000-CALC-ACTUAL-DAYS.
* Convert both dates to integer day numbers and subtract
PERFORM 2100-DATE-TO-INTEGER-1
PERFORM 2200-DATE-TO-INTEGER-2
SUBTRACT WS-INTEGER-DATE-1 FROM WS-INTEGER-DATE-2
GIVING WS-ACTUAL-DAYS
.
2100-DATE-TO-INTEGER-1.
* Convert start date to integer (days from reference)
MOVE ZERO TO WS-INTEGER-DATE-1
COMPUTE WS-INTEGER-DATE-1 =
FUNCTION INTEGER-OF-DATE(WS-START-DATE)
.
2200-DATE-TO-INTEGER-2.
* Convert end date to integer (days from reference)
MOVE ZERO TO WS-INTEGER-DATE-2
COMPUTE WS-INTEGER-DATE-2 =
FUNCTION INTEGER-OF-DATE(WS-END-DATE)
.
3000-CHECK-LEAP-YEAR.
* Leap year check for Actual/Actual calculations
MOVE 'N' TO WS-LEAP-FLAG
DIVIDE WS-YEAR-IDX BY 4
GIVING WS-WORK-DAYS REMAINDER WS-REMAIN-4
IF WS-REMAIN-4 = ZERO
DIVIDE WS-YEAR-IDX BY 100
GIVING WS-WORK-DAYS REMAINDER WS-REMAIN-100
IF WS-REMAIN-100 NOT = ZERO
SET IS-LEAP-YEAR TO TRUE
ELSE
DIVIDE WS-YEAR-IDX BY 400
GIVING WS-WORK-DAYS REMAINDER WS-REMAIN-400
IF WS-REMAIN-400 = ZERO
SET IS-LEAP-YEAR TO TRUE
END-IF
END-IF
END-IF
.
33.8 Present Value, Future Value, NPV, and IRR
Time value of money calculations are fundamental to financial analysis, investment evaluation, and accounting. COBOL programs implement these calculations for pension fund valuations, lease accounting, investment portfolio analysis, and capital budgeting.
Present Value and Future Value
Present value (PV) answers the question: what is a future amount worth today? Future value (FV) answers the reverse: what will today's amount be worth in the future?
PV = FV / (1 + r)^n FV = PV x (1 + r)^n
Net Present Value (NPV)
NPV is the sum of present values of a series of cash flows:
NPV = Sum of [CF(t) / (1 + r)^t] for t = 0 to n
Complete Time Value of Money Program
IDENTIFICATION DIVISION.
PROGRAM-ID. TIME-VALUE-MONEY.
*================================================================*
* PROGRAM: TIME-VALUE-MONEY *
* PURPOSE: Calculate PV, FV, NPV, and IRR for financial *
* analysis and investment evaluation *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MAX-PERIODS PIC S9(3) COMP VALUE 60.
01 WS-CASH-FLOW-TABLE.
05 WS-CASH-FLOW PIC S9(13)V99 COMP-3
OCCURS 60 TIMES.
01 WS-TVM-FIELDS.
05 WS-PRESENT-VALUE PIC S9(15)V99 COMP-3.
05 WS-FUTURE-VALUE PIC S9(15)V99 COMP-3.
05 WS-DISCOUNT-RATE PIC S9(3)V9(8) COMP-3.
05 WS-NUM-PERIODS PIC S9(3) COMP-3.
05 WS-NPV-RESULT PIC S9(15)V99 COMP-3.
05 WS-IRR-RESULT PIC S9(3)V9(6) COMP-3.
01 WS-CALC-WORK.
05 WS-PERIOD-CTR PIC S9(3) COMP-3.
05 WS-DISCOUNT-FACTOR PIC S9(5)V9(10) COMP-3.
05 WS-GROWTH-FACTOR PIC S9(5)V9(10) COMP-3.
05 WS-ACCUMULATOR PIC S9(15)V9(6) COMP-3.
05 WS-PV-CASHFLOW PIC S9(15)V9(6) COMP-3.
05 WS-POWER-CTR PIC S9(3) COMP-3.
05 WS-POWER-RESULT PIC S9(7)V9(10) COMP-3.
01 WS-IRR-WORK.
05 WS-IRR-LOW PIC S9(3)V9(8) COMP-3.
05 WS-IRR-HIGH PIC S9(3)V9(8) COMP-3.
05 WS-IRR-MID PIC S9(3)V9(8) COMP-3.
05 WS-NPV-LOW PIC S9(15)V99 COMP-3.
05 WS-NPV-HIGH PIC S9(15)V99 COMP-3.
05 WS-NPV-MID PIC S9(15)V99 COMP-3.
05 WS-IRR-TOLERANCE PIC S9(3)V9(8) COMP-3
VALUE 0.00000100.
05 WS-IRR-MAX-ITER PIC S9(5) COMP VALUE 1000.
05 WS-IRR-ITERATION PIC S9(5) COMP.
01 WS-DISPLAY-FIELDS.
05 WS-DISP-PV PIC $ZZZ,ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-FV PIC $ZZZ,ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-NPV PIC -ZZZ,ZZZ,ZZZ,ZZ9.99.
05 WS-DISP-IRR PIC Z9.999999.
05 WS-DISP-CF PIC -ZZZ,ZZZ,ZZ9.99.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
* ============================================
* Example 1: Present Value Calculation
* What is $100,000 in 5 years worth today
* at a 7% discount rate?
* ============================================
MOVE 100000.00 TO WS-FUTURE-VALUE
MOVE 0.07 TO WS-DISCOUNT-RATE
MOVE 5 TO WS-NUM-PERIODS
PERFORM 1000-CALC-PRESENT-VALUE
MOVE WS-PRESENT-VALUE TO WS-DISP-PV
MOVE WS-FUTURE-VALUE TO WS-DISP-FV
DISPLAY '==============================='
DISPLAY 'PRESENT VALUE CALCULATION'
DISPLAY 'FUTURE VALUE: ' WS-DISP-FV
DISPLAY 'DISCOUNT RATE: 7.00%'
DISPLAY 'PERIODS: 5'
DISPLAY 'PRESENT VALUE: ' WS-DISP-PV
* ============================================
* Example 2: NPV of an Investment
* Initial investment: -$500,000
* Year 1 cash flow: $125,000
* Year 2 cash flow: $150,000
* Year 3 cash flow: $175,000
* Year 4 cash flow: $150,000
* Year 5 cash flow: $100,000
* Discount rate: 10%
* ============================================
MOVE -500000.00 TO WS-CASH-FLOW(1)
MOVE 125000.00 TO WS-CASH-FLOW(2)
MOVE 150000.00 TO WS-CASH-FLOW(3)
MOVE 175000.00 TO WS-CASH-FLOW(4)
MOVE 150000.00 TO WS-CASH-FLOW(5)
MOVE 100000.00 TO WS-CASH-FLOW(6)
MOVE 6 TO WS-NUM-PERIODS
MOVE 0.10 TO WS-DISCOUNT-RATE
PERFORM 2000-CALC-NPV
MOVE WS-NPV-RESULT TO WS-DISP-NPV
DISPLAY '==============================='
DISPLAY 'NET PRESENT VALUE CALCULATION'
DISPLAY 'DISCOUNT RATE: 10.00%'
PERFORM VARYING WS-PERIOD-CTR FROM 1 BY 1
UNTIL WS-PERIOD-CTR > WS-NUM-PERIODS
MOVE WS-CASH-FLOW(WS-PERIOD-CTR) TO WS-DISP-CF
DISPLAY ' PERIOD ' WS-PERIOD-CTR ': ' WS-DISP-CF
END-PERFORM
DISPLAY 'NPV: ' WS-DISP-NPV
* ============================================
* Example 3: Internal Rate of Return (IRR)
* Using same cash flows as NPV example
* ============================================
PERFORM 3000-CALC-IRR
MULTIPLY WS-IRR-RESULT BY 100
GIVING WS-DISP-IRR
DISPLAY '==============================='
DISPLAY 'INTERNAL RATE OF RETURN'
DISPLAY 'IRR: ' WS-DISP-IRR '%'
STOP RUN
.
1000-CALC-PRESENT-VALUE.
* PV = FV / (1 + r)^n
* Calculate (1 + r)^n iteratively
ADD 1 TO WS-DISCOUNT-RATE
GIVING WS-GROWTH-FACTOR
MOVE 1.0 TO WS-POWER-RESULT
PERFORM VARYING WS-POWER-CTR FROM 1 BY 1
UNTIL WS-POWER-CTR > WS-NUM-PERIODS
MULTIPLY WS-POWER-RESULT BY WS-GROWTH-FACTOR
GIVING WS-POWER-RESULT ROUNDED
END-PERFORM
DIVIDE WS-FUTURE-VALUE BY WS-POWER-RESULT
GIVING WS-PRESENT-VALUE ROUNDED
.
2000-CALC-NPV.
* NPV = sum of CF(t) / (1 + r)^t for t = 0..n-1
MOVE ZERO TO WS-NPV-RESULT
PERFORM VARYING WS-PERIOD-CTR FROM 1 BY 1
UNTIL WS-PERIOD-CTR > WS-NUM-PERIODS
* Calculate discount factor (1 + r)^(t-1)
ADD 1 TO WS-DISCOUNT-RATE
GIVING WS-GROWTH-FACTOR
MOVE 1.0 TO WS-DISCOUNT-FACTOR
SUBTRACT 1 FROM WS-PERIOD-CTR
GIVING WS-POWER-CTR
PERFORM VARYING WS-POWER-CTR
FROM WS-POWER-CTR BY -1
UNTIL WS-POWER-CTR < 1
MULTIPLY WS-DISCOUNT-FACTOR
BY WS-GROWTH-FACTOR
GIVING WS-DISCOUNT-FACTOR ROUNDED
END-PERFORM
* PV of this cash flow
DIVIDE WS-CASH-FLOW(WS-PERIOD-CTR)
BY WS-DISCOUNT-FACTOR
GIVING WS-PV-CASHFLOW ROUNDED
ADD WS-PV-CASHFLOW TO WS-NPV-RESULT
END-PERFORM
.
3000-CALC-IRR.
* IRR is the discount rate that makes NPV = 0
* Use bisection method to find it
MOVE 0.00 TO WS-IRR-LOW
MOVE 1.00 TO WS-IRR-HIGH
MOVE ZERO TO WS-IRR-ITERATION
PERFORM UNTIL WS-IRR-ITERATION >= WS-IRR-MAX-ITER
ADD 1 TO WS-IRR-ITERATION
* Calculate midpoint
ADD WS-IRR-LOW TO WS-IRR-HIGH
GIVING WS-IRR-MID
DIVIDE WS-IRR-MID BY 2
GIVING WS-IRR-MID
* Calculate NPV at midpoint rate
MOVE WS-IRR-MID TO WS-DISCOUNT-RATE
PERFORM 2000-CALC-NPV
MOVE WS-NPV-RESULT TO WS-NPV-MID
* Check convergence
IF WS-NPV-MID > -0.01 AND WS-NPV-MID < 0.01
MOVE WS-IRR-MID TO WS-IRR-RESULT
EXIT PERFORM
END-IF
* Narrow the search range
IF WS-NPV-MID > 0
MOVE WS-IRR-MID TO WS-IRR-LOW
ELSE
MOVE WS-IRR-MID TO WS-IRR-HIGH
END-IF
* Check if range is narrow enough
SUBTRACT WS-IRR-LOW FROM WS-IRR-HIGH
GIVING WS-ACCUMULATOR
IF WS-ACCUMULATOR < WS-IRR-TOLERANCE
MOVE WS-IRR-MID TO WS-IRR-RESULT
EXIT PERFORM
END-IF
END-PERFORM
.
33.9 Regulatory Compliance
Financial calculations must meet strict regulatory requirements. COBOL programs in banking, insurance, and securities must implement specific precision requirements, maintain complete audit trails, and follow mandated calculation methods.
Precision Requirements
Different regulatory frameworks mandate different levels of precision:
- Truth in Lending Act (TILA): APR must be accurate to 1/8 of 1 percentage point (0.125%).
- Truth in Savings Act (TISA): APY must be accurate to 1/100 of 1 percentage point (0.01%).
- IRS Tax Calculations: Must be accurate to the penny for amounts, to 4 decimal places for tax rates.
- Securities Pricing: Bond prices must be accurate to 1/32 or 1/64 of a point depending on instrument type.
Audit Trail Implementation
Every financial calculation should produce a traceable audit record. The following structure captures calculation details for regulatory examination:
01 WS-AUDIT-RECORD.
05 WS-AUDIT-TIMESTAMP.
10 WS-AUDIT-DATE PIC 9(8).
10 WS-AUDIT-TIME PIC 9(8).
05 WS-AUDIT-PROGRAM PIC X(8).
05 WS-AUDIT-USER PIC X(8).
05 WS-AUDIT-ACCOUNT PIC X(15).
05 WS-AUDIT-CALC-TYPE PIC X(4).
88 CALC-SIMPLE-INT VALUE 'SINT'.
88 CALC-COMPOUND VALUE 'CINT'.
88 CALC-AMORT VALUE 'AMRT'.
88 CALC-TAX VALUE 'TAX '.
88 CALC-NPV VALUE 'NPV '.
05 WS-AUDIT-INPUTS.
10 WS-AUDIT-INPUT-1 PIC S9(15)V9(6) COMP-3.
10 WS-AUDIT-INPUT-2 PIC S9(15)V9(6) COMP-3.
10 WS-AUDIT-INPUT-3 PIC S9(15)V9(6) COMP-3.
10 WS-AUDIT-INPUT-4 PIC S9(15)V9(6) COMP-3.
05 WS-AUDIT-RESULT PIC S9(15)V9(6) COMP-3.
05 WS-AUDIT-PRECISION PIC 9(2).
05 WS-AUDIT-ROUND-MODE PIC X(4).
05 WS-AUDIT-STATUS PIC X(2).
88 AUDIT-SUCCESS VALUE '00'.
88 AUDIT-OVERFLOW VALUE '01'.
88 AUDIT-UNDERFLOW VALUE '02'.
88 AUDIT-ERROR VALUE '99'.
1000-WRITE-AUDIT-TRAIL.
* Populate audit timestamp
MOVE FUNCTION CURRENT-DATE(1:8) TO WS-AUDIT-DATE
MOVE FUNCTION CURRENT-DATE(9:8) TO WS-AUDIT-TIME
* Write audit record to sequential audit file
WRITE AUDIT-RECORD FROM WS-AUDIT-RECORD
IF WS-AUDIT-FILE-STATUS NOT = '00'
DISPLAY 'AUDIT WRITE ERROR: ' WS-AUDIT-FILE-STATUS
PERFORM 9999-ABEND-PROCESSING
END-IF
.
Regulatory Calculation Validation
Production financial programs should include self-validation routines that verify calculations against known test cases. This practice satisfies regulatory examination requirements:
1000-VALIDATE-INTEREST-CALC.
* Known test case: $10,000 at 5% simple interest
* for 90 days (Actual/365) = $123.29
MOVE 10000.00 TO WS-PRINCIPAL
MOVE 5.000000 TO WS-ANNUAL-RATE
MOVE 90 TO WS-TERM-DAYS
MOVE 365 TO WS-DAYS-IN-YEAR
PERFORM 2000-CALC-SIMPLE-INTEREST
IF WS-INTEREST-RESULT NOT = 123.29
DISPLAY 'VALIDATION FAILURE: SIMPLE INTEREST'
DISPLAY 'EXPECTED: 123.29'
DISPLAY 'ACTUAL: ' WS-INTEREST-RESULT
MOVE 'VF' TO WS-RETURN-CODE
ELSE
DISPLAY 'VALIDATION PASSED: SIMPLE INTEREST'
END-IF
.
33.10 Handling Very Large Amounts
As financial systems process increasingly large transactions, COBOL programs must handle amounts that exceed traditional PICTURE clause limits. National debt calculations, derivatives notional amounts, and sovereign wealth fund transactions can involve amounts in the trillions.
Extending PICTURE Clauses
Standard COBOL supports up to 18 digits in a numeric field. For amounts exceeding this, you can use COBOL's extended precision capabilities or implement multi-field arithmetic:
* Standard maximum: 18 digits (quadrillions)
01 WS-LARGE-AMOUNT PIC S9(16)V99 COMP-3.
* Maximum value: 9,999,999,999,999,999.99
* IBM Enterprise COBOL supports 31-digit fields
* with ARITH(EXTEND) compiler option
01 WS-EXTENDED-AMOUNT PIC S9(29)V99 COMP-3.
* Maximum: 29-digit integer + 2 decimal places
Overflow Detection
Financial programs must detect arithmetic overflow before it corrupts data. COBOL provides the ON SIZE ERROR phrase for this purpose:
IDENTIFICATION DIVISION.
PROGRAM-ID. OVERFLOW-DETECT.
*================================================================*
* PROGRAM: OVERFLOW-DETECT *
* PURPOSE: Demonstrate overflow detection in financial *
* calculations using ON SIZE ERROR *
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-AMOUNT-1 PIC S9(13)V99 COMP-3.
01 WS-AMOUNT-2 PIC S9(13)V99 COMP-3.
01 WS-RESULT PIC S9(13)V99 COMP-3.
01 WS-LARGE-RESULT PIC S9(17)V99 COMP-3.
01 WS-OVERFLOW-FLAG PIC X VALUE 'N'.
88 OVERFLOW-OCCURRED VALUE 'Y'.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
MOVE 9999999999999.99 TO WS-AMOUNT-1
MOVE 5000000000000.00 TO WS-AMOUNT-2
* This addition will overflow the result field
ADD WS-AMOUNT-1 TO WS-AMOUNT-2
GIVING WS-RESULT
ON SIZE ERROR
SET OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW DETECTED IN ADDITION'
DISPLAY 'ATTEMPTING WITH LARGER FIELD...'
* Retry with larger result field
ADD WS-AMOUNT-1 TO WS-AMOUNT-2
GIVING WS-LARGE-RESULT
ON SIZE ERROR
DISPLAY 'CRITICAL: OVERFLOW IN LARGE '
'FIELD - ABORTING'
STOP RUN
NOT ON SIZE ERROR
DISPLAY 'RECOVERED: RESULT = '
WS-LARGE-RESULT
END-ADD
NOT ON SIZE ERROR
DISPLAY 'RESULT: ' WS-RESULT
END-ADD
STOP RUN
.
Multi-Precision Arithmetic
For amounts that exceed even 31 digits (certain government bond market aggregate calculations), you can implement multi-field arithmetic:
01 WS-MEGA-AMOUNT.
05 WS-MEGA-HIGH PIC S9(18) COMP-3.
05 WS-MEGA-LOW PIC S9(18) COMP-3.
* Represents up to 36-digit amounts
* Total = HIGH * 10^18 + LOW
01 WS-MEGA-AMOUNT-2.
05 WS-MEGA-HIGH-2 PIC S9(18) COMP-3.
05 WS-MEGA-LOW-2 PIC S9(18) COMP-3.
01 WS-MEGA-RESULT.
05 WS-MEGA-RES-HIGH PIC S9(18) COMP-3.
05 WS-MEGA-RES-LOW PIC S9(18) COMP-3.
01 WS-CARRY PIC S9(18) COMP-3.
01 WS-OVERFLOW-LIMIT PIC S9(18) COMP-3
VALUE 999999999999999999.
1000-MEGA-ADD.
* Add two multi-precision amounts
ADD WS-MEGA-LOW TO WS-MEGA-LOW-2
GIVING WS-MEGA-RES-LOW
* Check for carry
IF WS-MEGA-RES-LOW > WS-OVERFLOW-LIMIT
SUBTRACT WS-OVERFLOW-LIMIT FROM WS-MEGA-RES-LOW
SUBTRACT 1 FROM WS-MEGA-RES-LOW
MOVE 1 TO WS-CARRY
ELSE
MOVE 0 TO WS-CARRY
END-IF
* Add high-order portions plus carry
ADD WS-MEGA-HIGH WS-MEGA-HIGH-2 WS-CARRY
GIVING WS-MEGA-RES-HIGH
ON SIZE ERROR
DISPLAY 'OVERFLOW: AMOUNT EXCEEDS 36 DIGITS'
END-ADD
.
JCL for a Complete Financial Batch Job
A typical financial calculation batch job on an IBM mainframe combines multiple programs in a single job stream:
//FINCALC JOB (ACCT),'FINANCIAL CALCS',CLASS=A,
// MSGCLASS=X,NOTIFY=&SYSUID,
// MSGLEVEL=(1,1)
//*
//*================================================================*
//* STEP 1: EXTRACT LOAN RECORDS FOR INTEREST CALCULATION *
//*================================================================*
//EXTRACT EXEC PGM=SORT
//SYSOUT DD SYSOUT=*
//SORTIN DD DSN=PROD.LOAN.MASTER,DISP=SHR
//SORTOUT DD DSN=&&LOANEXT,
// DISP=(NEW,PASS),
// SPACE=(CYL,(10,5),RLSE),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
//SYSIN DD *
SORT FIELDS=(1,10,CH,A)
INCLUDE COND=(45,1,CH,EQ,C'A')
/*
//*
//*================================================================*
//* STEP 2: CALCULATE SIMPLE INTEREST *
//*================================================================*
//CALCINT EXEC PGM=SIMPINT
//STEPLIB DD DSN=PROD.FINANCE.LOADLIB,DISP=SHR
//LOANIN DD DSN=&&LOANEXT,DISP=(OLD,PASS)
//RPTOUT DD DSN=PROD.FINANCE.INTEREST.REPORT,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(5,2),RLSE),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//AUDITOUT DD DSN=PROD.FINANCE.AUDIT.D&LYYMMDD,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(2,1),RLSE),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
//SYSOUT DD SYSOUT=*
//*
//*================================================================*
//* STEP 3: GENERATE AMORTIZATION SCHEDULES *
//*================================================================*
//AMORT EXEC PGM=AMORTSCH,COND=(0,NE,CALCINT)
//STEPLIB DD DSN=PROD.FINANCE.LOADLIB,DISP=SHR
//LOANIN DD DSN=&&LOANEXT,DISP=(OLD,DELETE)
//AMORTOUT DD DSN=PROD.FINANCE.AMORT.REPORT,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(20,10),RLSE),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//SYSOUT DD SYSOUT=*
//*
//*================================================================*
//* STEP 4: REGULATORY COMPLIANCE VALIDATION *
//*================================================================*
//VALIDATE EXEC PGM=REGVALID,COND=(0,NE,AMORT)
//STEPLIB DD DSN=PROD.FINANCE.LOADLIB,DISP=SHR
//RPTIN DD DSN=PROD.FINANCE.INTEREST.REPORT,DISP=SHR
// DD DSN=PROD.FINANCE.AMORT.REPORT,DISP=SHR
//VALRPT DD DSN=PROD.FINANCE.VALID.REPORT,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(1,1),RLSE),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//SYSOUT DD SYSOUT=*
33.11 Summary
Financial calculations represent COBOL's greatest strength and its primary reason for enduring relevance in the 21st century. The key principles covered in this chapter include:
-
Decimal arithmetic precision: COBOL's packed-decimal format stores monetary amounts exactly, eliminating the floating-point errors that plague other languages in financial applications.
-
Interest calculation methods: Simple interest, compound interest, and daily accrual all follow well-defined mathematical formulas that translate directly into COBOL arithmetic statements.
-
Amortization schedules: The standard amortization formula, implemented through iterative calculation, produces payment schedules for mortgages, auto loans, and personal loans. Always handle the final payment adjustment to account for accumulated rounding.
-
Currency handling: The ROUNDED phrase, banker's rounding, and multi-currency conversion require careful attention to precision and rounding mode selection.
-
Tax calculations: Progressive tax brackets, payroll withholding, and sales tax all use table-driven approaches that separate business rules from program logic.
-
Day count conventions: Different financial instruments require different day counting methods (30/360, Actual/360, Actual/365, Actual/Actual), and using the wrong convention produces incorrect interest amounts.
-
Time value of money: Present value, future value, NPV, and IRR calculations support investment analysis and accounting valuations.
-
Regulatory compliance: Financial programs must maintain audit trails, meet precision requirements, and include self-validation routines.
-
Large amount handling: Extended PICTURE clauses, overflow detection with ON SIZE ERROR, and multi-precision arithmetic techniques handle amounts that exceed standard field sizes.
These concepts build on the arithmetic operations covered in Chapter 6 and the data type fundamentals from Chapter 3. In the next chapter, we will examine banking and transaction processing systems, where these financial calculations operate within the context of real-time and batch processing environments.
Review Questions
-
Why does COBOL's packed-decimal arithmetic produce exact results while floating-point arithmetic does not?
-
What is the difference between truncation and rounding in COBOL, and why does it matter for financial calculations?
-
Explain why banker's rounding is preferred over standard rounding for high-volume financial processing.
-
A mortgage of $300,000 at 5.75% annual interest for 30 years uses monthly compounding. How would you structure the COBOL data fields for this calculation?
-
What are the four primary day count conventions, and when is each one used?
-
Explain the bisection method for calculating Internal Rate of Return (IRR) in COBOL.
-
How does the ON SIZE ERROR phrase help prevent data corruption in financial programs?
-
Why should financial calculations use intermediate fields with more decimal places than the final result?
Programming Exercises
-
Certificate of Deposit Calculator: Write a COBOL program that calculates the maturity value of a CD given principal, APY, term in months, and compounding frequency. Include early withdrawal penalty calculations (typically 90 or 180 days of interest).
-
Credit Card Interest Calculator: Implement the average daily balance method for credit card interest calculation. Process a file of daily transactions and compute the monthly finance charge.
-
Bond Pricing Program: Write a program that calculates the price of a fixed-rate bond given face value, coupon rate, yield to maturity, and years to maturity. Use the 30/360 day count convention.
-
Multi-Bracket Tax Engine: Extend the tax calculation program to handle both federal and state taxes, including state-specific bracket tables loaded from a configuration file.
-
Loan Comparison Tool: Create a program that compares different loan options by calculating total interest paid, monthly payment, and effective interest rate for each option, producing a side-by-side comparison report.