Case Study 2: Payroll Calculation System
Background
Heartland Manufacturing Group (HMG) operates three manufacturing plants in Ohio, employing a workforce of 1,450 hourly and salaried workers. Every two weeks, HMG's payroll department processes earnings for all employees, calculating gross pay, federal and state taxes, FICA contributions, voluntary deductions, and net pay. The payroll system runs as a COBOL batch program on HMG's IBM z/OS mainframe, processing the entire workforce in the Friday evening batch window.
In early 2024, HMG's payroll manager, Linda Yamamoto, identified a problem: the existing payroll program had been patched repeatedly over 18 years to accommodate new tax brackets, deduction types, and overtime rules. The accumulated patches had introduced inconsistencies in how rounding was applied -- some calculations used ROUNDED, others did not, and two subroutines used different rounding methods for the same type of calculation. During a routine internal audit, the auditor found that 23 employees had received paychecks that were off by one or two cents due to inconsistent rounding of tax withholding amounts.
Linda authorized a complete rewrite of the payroll calculation engine. The lead developer, Marcus Washington, designed the new system with three non-negotiable requirements:
- Every monetary calculation must use the ROUNDED phrase
- Every arithmetic operation must include ON SIZE ERROR protection
- Progressive tax bracket calculations must produce results that match the IRS withholding tables to the penny
This case study follows Marcus's implementation of the payroll calculation system, demonstrating COMPUTE for complex formulas, EVALUATE for tax bracket routing, ROUNDED for currency precision, and ON SIZE ERROR for overflow protection.
The Requirements
The payroll program must compute the following for each employee per pay period:
- Gross pay -- regular hours times hourly rate, plus overtime at 1.5x
- Federal tax withholding -- using progressive tax brackets for the employee's filing status
- State tax withholding -- Ohio's progressive income tax schedule
- FICA taxes -- Social Security (6.2%) and Medicare (1.45%), with the Social Security wage base cap
- Voluntary deductions -- health insurance, dental, 401(k), and union dues
- Net pay -- gross pay minus all deductions
The Complete Program
The following program demonstrates the payroll calculation engine processing a small payroll of three employees with different pay rates, hours, and filing statuses.
IDENTIFICATION DIVISION.
PROGRAM-ID. PAYROLL.
*================================================================*
* Program: PAYROLL
* Description: Biweekly Payroll Calculation System
* Author: Marcus Washington
* Date: 2024-02-15
* Purpose: Demonstrates COBOL arithmetic operations for
* payroll processing at Heartland Manufacturing.
* Uses COMPUTE, ROUNDED, ON SIZE ERROR, and
* EVALUATE for progressive tax bracket calculations.
*================================================================*
DATA DIVISION.
WORKING-STORAGE SECTION.
*================================================================*
* PROGRAM CONSTANTS *
*================================================================*
01 WS-C-PROGRAM-INFO.
05 WS-C-PROGRAM-ID PIC X(08) VALUE 'PAYROLL '.
05 WS-C-PAY-PERIOD-TYPE PIC X(10) VALUE 'BI-WEEKLY '.
05 WS-C-PAY-PERIODS-YR PIC 9(02) VALUE 26.
*--- Hours and overtime constants ---
01 WS-C-HOURS-CONSTANTS.
05 WS-C-MAX-REG-HOURS PIC 9(02)V9 VALUE 80.0.
05 WS-C-OVERTIME-MULT PIC 9(01)V9 VALUE 1.5.
05 WS-C-MAX-HOURS-PERIOD PIC 9(03)V9 VALUE 120.0.
*--- FICA constants (2024 rates) ---
01 WS-C-FICA-CONSTANTS.
05 WS-C-SS-RATE PIC V9(04) VALUE 0.0620.
05 WS-C-SS-WAGE-BASE PIC 9(09)V99
VALUE 168600.00.
05 WS-C-MEDICARE-RATE PIC V9(04) VALUE 0.0145.
05 WS-C-MEDICARE-SURTAX PIC V9(04) VALUE 0.0090.
05 WS-C-SURTAX-THRESH PIC 9(09)V99
VALUE 200000.00.
*--- Federal tax brackets (single filer, biweekly, 2024) ---
* Brackets are stored as annualized amounts for clarity
01 WS-C-FED-BRACKETS.
05 WS-C-FED-BRKT-1-MAX PIC 9(07)V99
VALUE 11600.00.
05 WS-C-FED-BRKT-1-RATE PIC V9(04) VALUE 0.1000.
05 WS-C-FED-BRKT-2-MAX PIC 9(07)V99
VALUE 47150.00.
05 WS-C-FED-BRKT-2-RATE PIC V9(04) VALUE 0.1200.
05 WS-C-FED-BRKT-3-MAX PIC 9(07)V99
VALUE 100525.00.
05 WS-C-FED-BRKT-3-RATE PIC V9(04) VALUE 0.2200.
05 WS-C-FED-BRKT-4-MAX PIC 9(07)V99
VALUE 191950.00.
05 WS-C-FED-BRKT-4-RATE PIC V9(04) VALUE 0.2400.
05 WS-C-FED-BRKT-5-RATE PIC V9(04) VALUE 0.3200.
*--- Ohio state tax brackets (2024 simplified) ---
01 WS-C-STATE-BRACKETS.
05 WS-C-ST-BRKT-1-MAX PIC 9(07)V99
VALUE 26050.00.
05 WS-C-ST-BRKT-1-RATE PIC V9(04) VALUE 0.0000.
05 WS-C-ST-BRKT-2-MAX PIC 9(07)V99
VALUE 46100.00.
05 WS-C-ST-BRKT-2-RATE PIC V9(04) VALUE 0.0275.
05 WS-C-ST-BRKT-3-MAX PIC 9(07)V99
VALUE 92150.00.
05 WS-C-ST-BRKT-3-RATE PIC V9(04) VALUE 0.0325.
05 WS-C-ST-BRKT-4-RATE PIC V9(04) VALUE 0.0375.
*--- Deduction constants ---
01 WS-C-DEDUCTION-RATES.
05 WS-C-HEALTH-SINGLE PIC 9(05)V99
VALUE 00185.50.
05 WS-C-HEALTH-FAMILY PIC 9(05)V99
VALUE 00425.00.
05 WS-C-DENTAL-SINGLE PIC 9(05)V99
VALUE 00028.75.
05 WS-C-DENTAL-FAMILY PIC 9(05)V99
VALUE 00072.50.
05 WS-C-UNION-DUES PIC 9(05)V99
VALUE 00045.00.
*--- Report constants ---
01 WS-C-REPORT-SEP PIC X(65) VALUE ALL '='.
01 WS-C-DETAIL-SEP PIC X(65) VALUE ALL '-'.
*================================================================*
* FLAGS AND SWITCHES *
*================================================================*
01 WS-F-OVERFLOW-FLAG PIC X(01) VALUE 'N'.
88 WS-OVERFLOW-OCCURRED VALUE 'Y'.
88 WS-NO-OVERFLOW VALUE 'N'.
01 WS-F-SS-CAPPED-FLAG PIC X(01) VALUE 'N'.
88 WS-SS-WAGE-CAPPED VALUE 'Y'.
88 WS-SS-WAGE-NOT-CAPPED VALUE 'N'.
*================================================================*
* COUNTERS AND ACCUMULATORS *
*================================================================*
01 WS-CTR-EMPLOYEES PIC 9(05) VALUE ZERO.
01 WS-ACC-PAYROLL-TOTALS.
05 WS-ACC-GROSS-PAY PIC 9(11)V99 VALUE ZERO.
05 WS-ACC-FED-TAX PIC 9(09)V99 VALUE ZERO.
05 WS-ACC-STATE-TAX PIC 9(09)V99 VALUE ZERO.
05 WS-ACC-SS-TAX PIC 9(09)V99 VALUE ZERO.
05 WS-ACC-MEDICARE-TAX PIC 9(09)V99 VALUE ZERO.
05 WS-ACC-DEDUCTIONS PIC 9(09)V99 VALUE ZERO.
05 WS-ACC-NET-PAY PIC 9(11)V99 VALUE ZERO.
*================================================================*
* EMPLOYEE INPUT RECORD *
*================================================================*
01 WS-EMPLOYEE-RECORD.
05 WS-EMP-ID PIC X(06).
05 WS-EMP-NAME PIC X(25).
05 WS-EMP-PAY-TYPE PIC X(01).
88 WS-EMP-HOURLY VALUE 'H'.
88 WS-EMP-SALARIED VALUE 'S'.
05 WS-EMP-HOURLY-RATE PIC 9(04)V99.
05 WS-EMP-SALARY-ANNUAL PIC 9(07)V99.
05 WS-EMP-HOURS-WORKED PIC 9(03)V9.
05 WS-EMP-FILING-STATUS PIC X(01).
88 WS-FILING-SINGLE VALUE 'S'.
88 WS-FILING-MARRIED VALUE 'M'.
88 WS-FILING-HEAD-HH VALUE 'H'.
05 WS-EMP-FED-ALLOWANCES PIC 9(02).
05 WS-EMP-401K-PCT PIC 9(02)V99.
05 WS-EMP-HEALTH-PLAN PIC X(01).
88 WS-HEALTH-NONE VALUE 'N'.
88 WS-HEALTH-SINGLE VALUE 'S'.
88 WS-HEALTH-FAMILY VALUE 'F'.
05 WS-EMP-DENTAL-PLAN PIC X(01).
88 WS-DENTAL-NONE VALUE 'N'.
88 WS-DENTAL-SINGLE VALUE 'S'.
88 WS-DENTAL-FAMILY VALUE 'F'.
05 WS-EMP-UNION-MEMBER PIC X(01).
88 WS-IS-UNION-MEMBER VALUE 'Y'.
88 WS-NOT-UNION-MEMBER VALUE 'N'.
05 WS-EMP-YTD-GROSS PIC 9(09)V99.
05 WS-EMP-YTD-SS-WAGES PIC 9(09)V99.
*================================================================*
* CALCULATION WORK FIELDS *
*================================================================*
01 WS-WK-PAY-FIELDS.
05 WS-WK-REG-HOURS PIC 9(03)V9 VALUE ZERO.
05 WS-WK-OT-HOURS PIC 9(03)V9 VALUE ZERO.
05 WS-WK-REG-PAY PIC 9(07)V99 VALUE ZERO.
05 WS-WK-OT-PAY PIC 9(07)V99 VALUE ZERO.
05 WS-WK-GROSS-PAY PIC 9(07)V99 VALUE ZERO.
01 WS-WK-TAX-FIELDS.
05 WS-WK-ANNUALIZED PIC 9(09)V99 VALUE ZERO.
05 WS-WK-TAXABLE-INCOME PIC 9(09)V99 VALUE ZERO.
05 WS-WK-REMAINING-INC PIC 9(09)V99 VALUE ZERO.
05 WS-WK-BRACKET-TAX PIC 9(07)V99 VALUE ZERO.
05 WS-WK-ANNUAL-FED-TAX PIC 9(07)V99 VALUE ZERO.
05 WS-WK-FED-TAX PIC 9(05)V99 VALUE ZERO.
05 WS-WK-ANNUAL-ST-TAX PIC 9(07)V99 VALUE ZERO.
05 WS-WK-STATE-TAX PIC 9(05)V99 VALUE ZERO.
05 WS-WK-SS-TAXABLE PIC 9(09)V99 VALUE ZERO.
05 WS-WK-SS-TAX PIC 9(05)V99 VALUE ZERO.
05 WS-WK-MEDICARE-TAX PIC 9(05)V99 VALUE ZERO.
01 WS-WK-DEDUCTION-FIELDS.
05 WS-WK-HEALTH-DED PIC 9(05)V99 VALUE ZERO.
05 WS-WK-DENTAL-DED PIC 9(05)V99 VALUE ZERO.
05 WS-WK-401K-DED PIC 9(05)V99 VALUE ZERO.
05 WS-WK-UNION-DED PIC 9(05)V99 VALUE ZERO.
05 WS-WK-TOTAL-DED PIC 9(07)V99 VALUE ZERO.
01 WS-WK-NET-PAY PIC S9(07)V99 VALUE ZERO.
01 WS-WK-TOTAL-TAXES PIC 9(07)V99 VALUE ZERO.
*================================================================*
* DISPLAY FIELDS *
*================================================================*
01 WS-DSP-AMOUNT PIC $$$,$$$, MATH1 $9.99.
01 WS-DSP-PCT PIC Z9.99.
01 WS-DSP-COUNT PIC Z,ZZ9.
PROCEDURE DIVISION.
0000-MAIN-CONTROL.
PERFORM 1000-INITIALIZATION
PERFORM 2000-PROCESS-PAYROLL
PERFORM 3000-PRINT-TOTALS
STOP RUN
.
*================================================================*
* 1000-INITIALIZATION *
*================================================================*
1000-INITIALIZATION.
DISPLAY WS-C-REPORT-SEP
DISPLAY ' HEARTLAND MANUFACTURING GROUP'
DISPLAY ' BIWEEKLY PAYROLL REGISTER'
DISPLAY WS-C-REPORT-SEP
DISPLAY SPACES
.
*================================================================*
* 2000-PROCESS-PAYROLL: Process all employees *
*================================================================*
2000-PROCESS-PAYROLL.
* Employee 1: Hourly, overtime, single filer
PERFORM 2050-CLEAR-EMPLOYEE
PERFORM 2100-LOAD-EMPLOYEE-1
PERFORM 2200-CALCULATE-GROSS-PAY
PERFORM 2300-CALCULATE-FEDERAL-TAX
PERFORM 2400-CALCULATE-STATE-TAX
PERFORM 2500-CALCULATE-FICA
PERFORM 2600-CALCULATE-DEDUCTIONS
PERFORM 2700-CALCULATE-NET-PAY
PERFORM 2800-ACCUMULATE-TOTALS
PERFORM 2900-DISPLAY-PAY-STUB
* Employee 2: Salaried, married filer
PERFORM 2050-CLEAR-EMPLOYEE
PERFORM 2100-LOAD-EMPLOYEE-2
PERFORM 2200-CALCULATE-GROSS-PAY
PERFORM 2300-CALCULATE-FEDERAL-TAX
PERFORM 2400-CALCULATE-STATE-TAX
PERFORM 2500-CALCULATE-FICA
PERFORM 2600-CALCULATE-DEDUCTIONS
PERFORM 2700-CALCULATE-NET-PAY
PERFORM 2800-ACCUMULATE-TOTALS
PERFORM 2900-DISPLAY-PAY-STUB
* Employee 3: Hourly, no overtime, head of household
PERFORM 2050-CLEAR-EMPLOYEE
PERFORM 2100-LOAD-EMPLOYEE-3
PERFORM 2200-CALCULATE-GROSS-PAY
PERFORM 2300-CALCULATE-FEDERAL-TAX
PERFORM 2400-CALCULATE-STATE-TAX
PERFORM 2500-CALCULATE-FICA
PERFORM 2600-CALCULATE-DEDUCTIONS
PERFORM 2700-CALCULATE-NET-PAY
PERFORM 2800-ACCUMULATE-TOTALS
PERFORM 2900-DISPLAY-PAY-STUB
.
*================================================================*
* 2050-CLEAR-EMPLOYEE: Reset per-employee fields *
*================================================================*
2050-CLEAR-EMPLOYEE.
INITIALIZE WS-WK-PAY-FIELDS
INITIALIZE WS-WK-TAX-FIELDS
INITIALIZE WS-WK-DEDUCTION-FIELDS
MOVE ZERO TO WS-WK-NET-PAY
MOVE ZERO TO WS-WK-TOTAL-TAXES
SET WS-NO-OVERFLOW TO TRUE
SET WS-SS-WAGE-NOT-CAPPED TO TRUE
.
*================================================================*
* 2100-LOAD-EMPLOYEE-1: Hourly worker with overtime *
*================================================================*
2100-LOAD-EMPLOYEE-1.
MOVE 'E10042' TO WS-EMP-ID
MOVE 'JOHNSON, ROBERT A.' TO WS-EMP-NAME
SET WS-EMP-HOURLY TO TRUE
MOVE 0024.50 TO WS-EMP-HOURLY-RATE
MOVE ZERO TO WS-EMP-SALARY-ANNUAL
MOVE 092.5 TO WS-EMP-HOURS-WORKED
SET WS-FILING-SINGLE TO TRUE
MOVE 01 TO WS-EMP-FED-ALLOWANCES
MOVE 06.00 TO WS-EMP-401K-PCT
SET WS-HEALTH-SINGLE TO TRUE
SET WS-DENTAL-SINGLE TO TRUE
SET WS-IS-UNION-MEMBER TO TRUE
MOVE 038220.00 TO WS-EMP-YTD-GROSS
MOVE 038220.00 TO WS-EMP-YTD-SS-WAGES
.
*================================================================*
* Load Employee 2: Salaried, married filer *
*================================================================*
2100-LOAD-EMPLOYEE-2.
MOVE 'E20015' TO WS-EMP-ID
MOVE 'CHEN, VICTORIA M.' TO WS-EMP-NAME
SET WS-EMP-SALARIED TO TRUE
MOVE ZERO TO WS-EMP-HOURLY-RATE
MOVE 078000.00 TO WS-EMP-SALARY-ANNUAL
MOVE 080.0 TO WS-EMP-HOURS-WORKED
SET WS-FILING-MARRIED TO TRUE
MOVE 03 TO WS-EMP-FED-ALLOWANCES
MOVE 08.00 TO WS-EMP-401K-PCT
SET WS-HEALTH-FAMILY TO TRUE
SET WS-DENTAL-FAMILY TO TRUE
SET WS-NOT-UNION-MEMBER TO TRUE
MOVE 060000.00 TO WS-EMP-YTD-GROSS
MOVE 060000.00 TO WS-EMP-YTD-SS-WAGES
.
*================================================================*
* Load Employee 3: Hourly, no overtime, head of household *
*================================================================*
2100-LOAD-EMPLOYEE-3.
MOVE 'E10087' TO WS-EMP-ID
MOVE 'WILLIAMS, MARIA T.' TO WS-EMP-NAME
SET WS-EMP-HOURLY TO TRUE
MOVE 0019.75 TO WS-EMP-HOURLY-RATE
MOVE ZERO TO WS-EMP-SALARY-ANNUAL
MOVE 076.0 TO WS-EMP-HOURS-WORKED
SET WS-FILING-HEAD-HH TO TRUE
MOVE 02 TO WS-EMP-FED-ALLOWANCES
MOVE 03.00 TO WS-EMP-401K-PCT
SET WS-HEALTH-FAMILY TO TRUE
SET WS-DENTAL-NONE TO TRUE
SET WS-IS-UNION-MEMBER TO TRUE
MOVE 024675.00 TO WS-EMP-YTD-GROSS
MOVE 024675.00 TO WS-EMP-YTD-SS-WAGES
.
*================================================================*
* 2200-CALCULATE-GROSS-PAY *
* Computes regular pay and overtime pay. *
* Overtime applies after 80 hours (biweekly period). *
*================================================================*
2200-CALCULATE-GROSS-PAY.
EVALUATE TRUE
WHEN WS-EMP-HOURLY
* Determine regular and overtime hours
IF WS-EMP-HOURS-WORKED > WS-C-MAX-REG-HOURS
MOVE WS-C-MAX-REG-HOURS
TO WS-WK-REG-HOURS
COMPUTE WS-WK-OT-HOURS =
WS-EMP-HOURS-WORKED
- WS-C-MAX-REG-HOURS
ELSE
MOVE WS-EMP-HOURS-WORKED
TO WS-WK-REG-HOURS
MOVE ZERO TO WS-WK-OT-HOURS
END-IF
* Calculate regular pay
COMPUTE WS-WK-REG-PAY ROUNDED =
WS-WK-REG-HOURS * WS-EMP-HOURLY-RATE
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW: Regular pay calc'
END-COMPUTE
* Calculate overtime pay at 1.5x rate
IF WS-WK-OT-HOURS > ZERO
COMPUTE WS-WK-OT-PAY ROUNDED =
WS-WK-OT-HOURS
* WS-EMP-HOURLY-RATE
* WS-C-OVERTIME-MULT
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW: OT pay calc'
END-COMPUTE
END-IF
* Total gross pay
ADD WS-WK-REG-PAY WS-WK-OT-PAY
GIVING WS-WK-GROSS-PAY
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW: Gross pay calc'
END-ADD
WHEN WS-EMP-SALARIED
* Biweekly salary = annual / 26
COMPUTE WS-WK-GROSS-PAY ROUNDED =
WS-EMP-SALARY-ANNUAL
/ WS-C-PAY-PERIODS-YR
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW: Salary calc'
END-COMPUTE
MOVE WS-WK-GROSS-PAY TO WS-WK-REG-PAY
MOVE ZERO TO WS-WK-OT-PAY
END-EVALUATE
.
*================================================================*
* 2300-CALCULATE-FEDERAL-TAX *
* Progressive tax bracket calculation. *
* Strategy: Annualize the gross, calculate annual tax, *
* then divide by 26 for the per-period amount. *
*================================================================*
2300-CALCULATE-FEDERAL-TAX.
* Annualize the gross pay
COMPUTE WS-WK-ANNUALIZED ROUNDED =
WS-WK-GROSS-PAY * WS-C-PAY-PERIODS-YR
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
END-COMPUTE
MOVE WS-WK-ANNUALIZED TO WS-WK-REMAINING-INC
MOVE ZERO TO WS-WK-ANNUAL-FED-TAX
* Bracket 1: 10% on first $11,600
IF WS-WK-REMAINING-INC > ZERO
IF WS-WK-REMAINING-INC > WS-C-FED-BRKT-1-MAX
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-C-FED-BRKT-1-MAX
* WS-C-FED-BRKT-1-RATE
SUBTRACT WS-C-FED-BRKT-1-MAX
FROM WS-WK-REMAINING-INC
ELSE
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-REMAINING-INC
* WS-C-FED-BRKT-1-RATE
MOVE ZERO TO WS-WK-REMAINING-INC
END-IF
ADD WS-WK-BRACKET-TAX TO WS-WK-ANNUAL-FED-TAX
END-IF
* Bracket 2: 12% on $11,601 to $47,150
IF WS-WK-REMAINING-INC > ZERO
COMPUTE WS-WK-TAXABLE-INCOME =
WS-C-FED-BRKT-2-MAX - WS-C-FED-BRKT-1-MAX
IF WS-WK-REMAINING-INC > WS-WK-TAXABLE-INCOME
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-TAXABLE-INCOME
* WS-C-FED-BRKT-2-RATE
SUBTRACT WS-WK-TAXABLE-INCOME
FROM WS-WK-REMAINING-INC
ELSE
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-REMAINING-INC
* WS-C-FED-BRKT-2-RATE
MOVE ZERO TO WS-WK-REMAINING-INC
END-IF
ADD WS-WK-BRACKET-TAX TO WS-WK-ANNUAL-FED-TAX
END-IF
* Bracket 3: 22% on $47,151 to $100,525
IF WS-WK-REMAINING-INC > ZERO
COMPUTE WS-WK-TAXABLE-INCOME =
WS-C-FED-BRKT-3-MAX - WS-C-FED-BRKT-2-MAX
IF WS-WK-REMAINING-INC > WS-WK-TAXABLE-INCOME
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-TAXABLE-INCOME
* WS-C-FED-BRKT-3-RATE
SUBTRACT WS-WK-TAXABLE-INCOME
FROM WS-WK-REMAINING-INC
ELSE
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-REMAINING-INC
* WS-C-FED-BRKT-3-RATE
MOVE ZERO TO WS-WK-REMAINING-INC
END-IF
ADD WS-WK-BRACKET-TAX TO WS-WK-ANNUAL-FED-TAX
END-IF
* Bracket 4: 24% on $100,526 to $191,950
IF WS-WK-REMAINING-INC > ZERO
COMPUTE WS-WK-TAXABLE-INCOME =
WS-C-FED-BRKT-4-MAX - WS-C-FED-BRKT-3-MAX
IF WS-WK-REMAINING-INC > WS-WK-TAXABLE-INCOME
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-TAXABLE-INCOME
* WS-C-FED-BRKT-4-RATE
SUBTRACT WS-WK-TAXABLE-INCOME
FROM WS-WK-REMAINING-INC
ELSE
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-REMAINING-INC
* WS-C-FED-BRKT-4-RATE
MOVE ZERO TO WS-WK-REMAINING-INC
END-IF
ADD WS-WK-BRACKET-TAX TO WS-WK-ANNUAL-FED-TAX
END-IF
* Bracket 5: 32% on everything above $191,950
IF WS-WK-REMAINING-INC > ZERO
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-REMAINING-INC
* WS-C-FED-BRKT-5-RATE
ADD WS-WK-BRACKET-TAX TO WS-WK-ANNUAL-FED-TAX
MOVE ZERO TO WS-WK-REMAINING-INC
END-IF
* Convert annual tax to per-period amount
IF WS-C-PAY-PERIODS-YR > ZERO
COMPUTE WS-WK-FED-TAX ROUNDED =
WS-WK-ANNUAL-FED-TAX / WS-C-PAY-PERIODS-YR
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW: Federal tax calc'
END-COMPUTE
ELSE
MOVE ZERO TO WS-WK-FED-TAX
END-IF
.
*================================================================*
* 2400-CALCULATE-STATE-TAX *
* Ohio progressive income tax with simplified brackets. *
*================================================================*
2400-CALCULATE-STATE-TAX.
MOVE WS-WK-ANNUALIZED TO WS-WK-REMAINING-INC
MOVE ZERO TO WS-WK-ANNUAL-ST-TAX
* Bracket 1: 0% on first $26,050
IF WS-WK-REMAINING-INC > WS-C-ST-BRKT-1-MAX
SUBTRACT WS-C-ST-BRKT-1-MAX
FROM WS-WK-REMAINING-INC
ELSE
MOVE ZERO TO WS-WK-REMAINING-INC
END-IF
* Bracket 2: 2.75% on $26,051 to $46,100
IF WS-WK-REMAINING-INC > ZERO
COMPUTE WS-WK-TAXABLE-INCOME =
WS-C-ST-BRKT-2-MAX - WS-C-ST-BRKT-1-MAX
IF WS-WK-REMAINING-INC > WS-WK-TAXABLE-INCOME
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-TAXABLE-INCOME
* WS-C-ST-BRKT-2-RATE
SUBTRACT WS-WK-TAXABLE-INCOME
FROM WS-WK-REMAINING-INC
ELSE
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-REMAINING-INC
* WS-C-ST-BRKT-2-RATE
MOVE ZERO TO WS-WK-REMAINING-INC
END-IF
ADD WS-WK-BRACKET-TAX TO WS-WK-ANNUAL-ST-TAX
END-IF
* Bracket 3: 3.25% on $46,101 to $92,150
IF WS-WK-REMAINING-INC > ZERO
COMPUTE WS-WK-TAXABLE-INCOME =
WS-C-ST-BRKT-3-MAX - WS-C-ST-BRKT-2-MAX
IF WS-WK-REMAINING-INC > WS-WK-TAXABLE-INCOME
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-TAXABLE-INCOME
* WS-C-ST-BRKT-3-RATE
SUBTRACT WS-WK-TAXABLE-INCOME
FROM WS-WK-REMAINING-INC
ELSE
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-REMAINING-INC
* WS-C-ST-BRKT-3-RATE
MOVE ZERO TO WS-WK-REMAINING-INC
END-IF
ADD WS-WK-BRACKET-TAX TO WS-WK-ANNUAL-ST-TAX
END-IF
* Bracket 4: 3.75% on everything above $92,150
IF WS-WK-REMAINING-INC > ZERO
COMPUTE WS-WK-BRACKET-TAX ROUNDED =
WS-WK-REMAINING-INC
* WS-C-ST-BRKT-4-RATE
ADD WS-WK-BRACKET-TAX TO WS-WK-ANNUAL-ST-TAX
END-IF
* Convert annual state tax to per-period amount
IF WS-C-PAY-PERIODS-YR > ZERO
COMPUTE WS-WK-STATE-TAX ROUNDED =
WS-WK-ANNUAL-ST-TAX / WS-C-PAY-PERIODS-YR
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW: State tax calc'
END-COMPUTE
ELSE
MOVE ZERO TO WS-WK-STATE-TAX
END-IF
.
*================================================================*
* 2500-CALCULATE-FICA *
* Social Security (6.2% up to wage base) and Medicare (1.45%). *
*================================================================*
2500-CALCULATE-FICA.
* Social Security: check if wage base has been reached
COMPUTE WS-WK-SS-TAXABLE =
WS-C-SS-WAGE-BASE - WS-EMP-YTD-SS-WAGES
IF WS-WK-SS-TAXABLE <= ZERO
* Already over the wage base, no SS tax this period
MOVE ZERO TO WS-WK-SS-TAX
SET WS-SS-WAGE-CAPPED TO TRUE
ELSE
IF WS-WK-SS-TAXABLE < WS-WK-GROSS-PAY
* Will hit the cap this period
COMPUTE WS-WK-SS-TAX ROUNDED =
WS-WK-SS-TAXABLE * WS-C-SS-RATE
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
END-COMPUTE
SET WS-SS-WAGE-CAPPED TO TRUE
ELSE
* Full gross is taxable for SS
COMPUTE WS-WK-SS-TAX ROUNDED =
WS-WK-GROSS-PAY * WS-C-SS-RATE
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
END-COMPUTE
END-IF
END-IF
* Medicare: no wage base limit (1.45% on all wages)
COMPUTE WS-WK-MEDICARE-TAX ROUNDED =
WS-WK-GROSS-PAY * WS-C-MEDICARE-RATE
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW: Medicare tax calc'
END-COMPUTE
.
*================================================================*
* 2600-CALCULATE-DEDUCTIONS *
* Health insurance, dental, 401(k), and union dues. *
*================================================================*
2600-CALCULATE-DEDUCTIONS.
* Health insurance deduction
EVALUATE TRUE
WHEN WS-HEALTH-NONE
MOVE ZERO TO WS-WK-HEALTH-DED
WHEN WS-HEALTH-SINGLE
MOVE WS-C-HEALTH-SINGLE TO WS-WK-HEALTH-DED
WHEN WS-HEALTH-FAMILY
MOVE WS-C-HEALTH-FAMILY TO WS-WK-HEALTH-DED
END-EVALUATE
* Dental insurance deduction
EVALUATE TRUE
WHEN WS-DENTAL-NONE
MOVE ZERO TO WS-WK-DENTAL-DED
WHEN WS-DENTAL-SINGLE
MOVE WS-C-DENTAL-SINGLE TO WS-WK-DENTAL-DED
WHEN WS-DENTAL-FAMILY
MOVE WS-C-DENTAL-FAMILY TO WS-WK-DENTAL-DED
END-EVALUATE
* 401(k) contribution (percentage of gross pay)
IF WS-EMP-401K-PCT > ZERO
COMPUTE WS-WK-401K-DED ROUNDED =
WS-WK-GROSS-PAY
* (WS-EMP-401K-PCT / 100)
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW: 401k calc'
END-COMPUTE
ELSE
MOVE ZERO TO WS-WK-401K-DED
END-IF
* Union dues
IF WS-IS-UNION-MEMBER
MOVE WS-C-UNION-DUES TO WS-WK-UNION-DED
ELSE
MOVE ZERO TO WS-WK-UNION-DED
END-IF
* Total deductions (taxes + voluntary)
COMPUTE WS-WK-TOTAL-TAXES =
WS-WK-FED-TAX
+ WS-WK-STATE-TAX
+ WS-WK-SS-TAX
+ WS-WK-MEDICARE-TAX
COMPUTE WS-WK-TOTAL-DED =
WS-WK-HEALTH-DED
+ WS-WK-DENTAL-DED
+ WS-WK-401K-DED
+ WS-WK-UNION-DED
.
*================================================================*
* 2700-CALCULATE-NET-PAY *
* Gross minus all taxes and deductions. *
*================================================================*
2700-CALCULATE-NET-PAY.
COMPUTE WS-WK-NET-PAY ROUNDED =
WS-WK-GROSS-PAY
- WS-WK-TOTAL-TAXES
- WS-WK-TOTAL-DED
ON SIZE ERROR
SET WS-OVERFLOW-OCCURRED TO TRUE
DISPLAY 'OVERFLOW: Net pay calculation'
END-COMPUTE
IF WS-WK-NET-PAY < ZERO
DISPLAY '*** WARNING: Negative net pay for '
WS-EMP-ID ' ***'
END-IF
.
*================================================================*
* 2800-ACCUMULATE-TOTALS: Add to payroll register totals *
*================================================================*
2800-ACCUMULATE-TOTALS.
ADD 1 TO WS-CTR-EMPLOYEES
ADD WS-WK-GROSS-PAY TO WS-ACC-GROSS-PAY
ADD WS-WK-FED-TAX TO WS-ACC-FED-TAX
ADD WS-WK-STATE-TAX TO WS-ACC-STATE-TAX
ADD WS-WK-SS-TAX TO WS-ACC-SS-TAX
ADD WS-WK-MEDICARE-TAX TO WS-ACC-MEDICARE-TAX
ADD WS-WK-TOTAL-DED TO WS-ACC-DEDUCTIONS
ADD WS-WK-NET-PAY TO WS-ACC-NET-PAY
.
*================================================================*
* 2900-DISPLAY-PAY-STUB: Format and display employee pay stub *
*================================================================*
2900-DISPLAY-PAY-STUB.
DISPLAY WS-C-DETAIL-SEP
DISPLAY ' Employee: ' WS-EMP-ID ' '
WS-EMP-NAME
IF WS-EMP-HOURLY
MOVE WS-EMP-HOURLY-RATE TO WS-DSP-RATE
DISPLAY ' Pay Type: Hourly @ ' WS-DSP-RATE
'/hr'
MOVE WS-WK-REG-HOURS TO WS-DSP-HOURS
DISPLAY ' Regular Hours: ' WS-DSP-HOURS
MOVE WS-WK-OT-HOURS TO WS-DSP-HOURS
DISPLAY ' Overtime Hours: ' WS-DSP-HOURS
ELSE
MOVE WS-EMP-SALARY-ANNUAL TO WS-DSP-AMOUNT
DISPLAY ' Pay Type: Salaried @ '
WS-DSP-AMOUNT '/yr'
END-IF
DISPLAY SPACES
DISPLAY ' EARNINGS:'
MOVE WS-WK-REG-PAY TO WS-DSP-AMOUNT
DISPLAY ' Regular Pay: ' WS-DSP-AMOUNT
MOVE WS-WK-OT-PAY TO WS-DSP-AMOUNT
DISPLAY ' Overtime Pay: ' WS-DSP-AMOUNT
MOVE WS-WK-GROSS-PAY TO WS-DSP-AMOUNT
DISPLAY ' GROSS PAY: ' WS-DSP-AMOUNT
DISPLAY SPACES
DISPLAY ' TAXES:'
MOVE WS-WK-FED-TAX TO WS-DSP-AMOUNT
DISPLAY ' Federal Tax: ' WS-DSP-AMOUNT
MOVE WS-WK-STATE-TAX TO WS-DSP-AMOUNT
DISPLAY ' State Tax (OH): ' WS-DSP-AMOUNT
MOVE WS-WK-SS-TAX TO WS-DSP-AMOUNT
DISPLAY ' Social Security: ' WS-DSP-AMOUNT
IF WS-SS-WAGE-CAPPED
DISPLAY ' (SS wage base reached)'
END-IF
MOVE WS-WK-MEDICARE-TAX TO WS-DSP-AMOUNT
DISPLAY ' Medicare: ' WS-DSP-AMOUNT
MOVE WS-WK-TOTAL-TAXES TO WS-DSP-AMOUNT
DISPLAY ' TOTAL TAXES: ' WS-DSP-AMOUNT
DISPLAY SPACES
DISPLAY ' DEDUCTIONS:'
MOVE WS-WK-HEALTH-DED TO WS-DSP-AMOUNT
DISPLAY ' Health Insurance: ' WS-DSP-AMOUNT
MOVE WS-WK-DENTAL-DED TO WS-DSP-AMOUNT
DISPLAY ' Dental Insurance: ' WS-DSP-AMOUNT
MOVE WS-WK-401K-DED TO WS-DSP-AMOUNT
DISPLAY ' 401(k): ' WS-DSP-AMOUNT
MOVE WS-WK-UNION-DED TO WS-DSP-AMOUNT
DISPLAY ' Union Dues: ' WS-DSP-AMOUNT
MOVE WS-WK-TOTAL-DED TO WS-DSP-AMOUNT
DISPLAY ' TOTAL DEDUCTIONS: ' WS-DSP-AMOUNT
DISPLAY SPACES
MOVE WS-WK-NET-PAY TO WS-DSP-AMOUNT
DISPLAY ' *** NET PAY: ' WS-DSP-AMOUNT
' ***'
DISPLAY SPACES
.
*================================================================*
* 3000-PRINT-TOTALS: Payroll register summary *
*================================================================*
3000-PRINT-TOTALS.
DISPLAY WS-C-REPORT-SEP
DISPLAY ' PAYROLL REGISTER SUMMARY'
DISPLAY WS-C-REPORT-SEP
MOVE WS-CTR-EMPLOYEES TO WS-DSP-COUNT
DISPLAY ' Employees Processed: ' WS-DSP-COUNT
DISPLAY SPACES
MOVE WS-ACC-GROSS-PAY TO WS-DSP-AMOUNT
DISPLAY ' Total Gross Pay: ' WS-DSP-AMOUNT
MOVE WS-ACC-FED-TAX TO WS-DSP-AMOUNT
DISPLAY ' Total Federal Tax: ' WS-DSP-AMOUNT
MOVE WS-ACC-STATE-TAX TO WS-DSP-AMOUNT
DISPLAY ' Total State Tax: ' WS-DSP-AMOUNT
MOVE WS-ACC-SS-TAX TO WS-DSP-AMOUNT
DISPLAY ' Total Soc Security: ' WS-DSP-AMOUNT
MOVE WS-ACC-MEDICARE-TAX TO WS-DSP-AMOUNT
DISPLAY ' Total Medicare: ' WS-DSP-AMOUNT
MOVE WS-ACC-DEDUCTIONS TO WS-DSP-AMOUNT
DISPLAY ' Total Deductions: ' WS-DSP-AMOUNT
MOVE WS-ACC-NET-PAY TO WS-DSP-AMOUNT
DISPLAY ' Total Net Pay: ' WS-DSP-AMOUNT
DISPLAY SPACES
DISPLAY WS-C-REPORT-SEP
IF WS-OVERFLOW-OCCURRED
DISPLAY '*** WARNING: Overflow occurred during'
' processing. Review error log. ***'
END-IF
.
Solution Walkthrough
Gross Pay Calculation: COMPUTE with Overflow Protection
The gross pay calculation demonstrates several key arithmetic principles from Chapter 6.
For hourly employees, the program first separates regular hours from overtime hours by comparing against the 80-hour biweekly threshold. Regular pay uses a straightforward multiplication:
COMPUTE WS-WK-REG-PAY ROUNDED =
WS-WK-REG-HOURS * WS-EMP-HOURLY-RATE
Overtime pay uses a three-factor multiplication that could easily overflow an undersized result field:
COMPUTE WS-WK-OT-PAY ROUNDED =
WS-WK-OT-HOURS * WS-EMP-HOURLY-RATE
* WS-C-OVERTIME-MULT
The overtime multiplier (1.5) is stored as a named constant (WS-C-OVERTIME-MULT) rather than as a literal in the COMPUTE expression. When HMG later introduced double-time for holiday hours (2.0x), Marcus changed one VALUE clause rather than searching for every occurrence of 1.5 in the PROCEDURE DIVISION.
Every COMPUTE includes ROUNDED to ensure currency amounts are calculated to the nearest cent, and ON SIZE ERROR to catch any overflow. The overflow flag is a persistent indicator -- once set, it remains set for the entire batch run so the summary report can warn the payroll manager.
Progressive Tax Brackets: The Subtraction Approach
The federal tax calculation uses the "remaining income" approach to progressive taxation. Rather than calculating what falls in each bracket by comparing against bracket boundaries, the program subtracts each bracket's width from the remaining income:
- Start with the full annualized income in
WS-WK-REMAINING-INC - For each bracket, determine how much income falls in that bracket
- Multiply that portion by the bracket's rate (using ROUNDED)
- Subtract the bracket's width from the remaining income
- Continue to the next bracket until the remaining income is zero
This approach has two advantages over the alternative of using a sequence of IF tests against cumulative thresholds. First, the bracket calculations are independent -- each bracket's code follows exactly the same pattern, making it easy to add new brackets when tax law changes. Second, the running subtraction ensures that every dollar of income is taxed in exactly one bracket, eliminating the risk of double-counting that can occur with overlapping threshold ranges.
The annualization strategy (multiplying the biweekly gross by 26, calculating annual tax, then dividing by 26) matches the IRS-recommended withholding method and ensures that the per-period tax amount properly reflects the progressive rate structure.
FICA: The Social Security Wage Base Cap
The Social Security tax calculation demonstrates a business rule that requires careful arithmetic: the 6.2% tax applies only up to the annual wage base ($168,600 in 2024). The program must handle three scenarios:
- The employee has already exceeded the wage base in prior periods (no SS tax this period)
- The employee will exceed the wage base during this period (tax only on the remaining taxable amount)
- The employee is fully below the wage base (tax on the full gross)
Each scenario produces a different calculation, but all use ROUNDED and ON SIZE ERROR. The WS-SS-WAGE-CAPPED flag is set when the cap is reached, and the pay stub display uses this flag to print a notification.
Deductions: EVALUATE for Plan Selection
The deduction calculation uses EVALUATE TRUE with 88-level condition names to select the correct deduction amount based on the employee's benefit elections. This approach is cleaner than nested IF statements and directly maps the business rule: "If single coverage, deduct the single rate; if family coverage, deduct the family rate; if no coverage, deduct nothing."
The 401(k) contribution uses a percentage-of-gross calculation with ROUNDED:
COMPUTE WS-WK-401K-DED ROUNDED =
WS-WK-GROSS-PAY * (WS-EMP-401K-PCT / 100)
Note the parenthesized division by 100 to convert the percentage (e.g., 6.00) to a decimal (0.06). Without the parentheses, operator precedence would cause the multiplication to happen first, producing an incorrect result.
Net Pay: Signed Fields and Negative Detection
The net pay field uses PIC S9(07)V99 -- a signed field -- because deductions could theoretically exceed gross pay (for example, if a part-time employee with family coverage has a very short pay period). The program explicitly checks for negative net pay and displays a warning. This is a critical business rule: a negative paycheck requires manual intervention by the payroll department.
Lessons Learned
1. ROUNDED Is Non-Negotiable for Payroll
The original payroll program's inconsistent use of ROUNDED was the root cause of the penny discrepancies that triggered the rewrite. Marcus established a strict coding standard: every COMPUTE, MULTIPLY, and DIVIDE that produces a currency amount must include ROUNDED. The cost of including ROUNDED is negligible. The cost of omitting it is audit findings, employee complaints, and potential regulatory exposure.
2. ON SIZE ERROR Is Insurance, Not Overhead
Marcus included ON SIZE ERROR on every arithmetic statement, even those where overflow seems impossible. This "belt and suspenders" approach caught a real problem during testing: when processing an employee with an extremely high overtime rate (a machinist earning $62.50/hour working 40 hours of overtime), the overtime pay calculation overflowed a field that was originally sized as PIC 9(05)V99. Without ON SIZE ERROR, the program would have silently produced an incorrect paycheck. With it, the overflow was detected immediately and the field was resized.
3. Tax Bracket Constants Belong in WORKING-STORAGE, Not in Code
By storing all tax bracket thresholds and rates as named constants in WORKING-STORAGE, Marcus made annual tax updates a 15-minute task. When the IRS published new withholding tables for the following year, the developer updated the VALUE clauses in the constants section and the program was ready for testing. No PROCEDURE DIVISION logic needed to change.
4. Annualization Eliminates Per-Period Bracket Distortion
Applying progressive tax rates directly to biweekly gross pay would produce incorrect withholding. A biweekly paycheck of $3,000 would appear to be entirely in the lowest tax bracket, even though the annualized income of $78,000 spans multiple brackets. The annualization approach correctly distributes the tax burden across brackets and then converts back to a per-period amount.
5. The Signed Field for Net Pay Is Not Optional
Using an unsigned field for net pay masks a genuine business condition. When deductions exceed gross pay, the correct behavior is to detect the negative result and flag it for human review -- not to silently truncate it to zero or, worse, produce an incorrect positive amount. The signed PIC S9 field combined with the explicit negative-value check ensures that this edge case is handled correctly.
Discussion Questions
-
The program calculates federal tax by annualizing the gross pay and then dividing the annual tax by 26. What problems could this approach cause for employees whose pay varies significantly from period to period (for example, due to commissions or large overtime weeks)? How would you address this?
-
The Social Security wage base check uses year-to-date wages stored in the employee record. What would happen if the YTD wages field were not updated correctly after each payroll run? How would you design the system to prevent this class of error?
-
Marcus chose to store tax bracket thresholds as individual named constants rather than as a table with OCCURS. What are the tradeoffs? At what point would a table-based approach become preferable?
-
The 401(k) deduction is calculated as a percentage of gross pay. Some employees might want a flat dollar amount instead. How would you modify the data structure and calculation logic to support both methods?
-
The program does not handle the Medicare additional tax (0.9% surtax above $200,000 in annual wages). How would you add this calculation? What WORKING-STORAGE changes would be needed? Where in the FICA calculation logic would the surtax calculation be inserted?
-
If HMG expanded to a state with no income tax (such as Texas), what WORKING-STORAGE and PROCEDURE DIVISION changes would be needed to support multi-state payroll processing?
Connection to Chapter Concepts
This case study directly illustrates several key concepts from Chapter 6:
-
COMPUTE for complex expressions (Section: The COMPUTE Statement): The overtime pay calculation, 401(k) percentage, and annualization all use COMPUTE with multi-operator expressions.
-
ROUNDED on every currency calculation (Section: The ROUNDED Phrase): Every monetary COMPUTE includes ROUNDED to ensure cent-accurate results.
-
ON SIZE ERROR on every operation (Section: ON SIZE ERROR and NOT ON SIZE ERROR): Every arithmetic operation includes overflow protection with a persistent flag for batch-level reporting.
-
Progressive tax bracket arithmetic (Section: Financial Calculation Patterns): The "remaining income subtraction" approach demonstrates how to implement progressive taxation using sequential COMPUTE and SUBTRACT operations.
-
The GIVING phrase for operand preservation (Section: The GIVING Phrase and Operand Preservation): The ADD ... GIVING pattern is used in gross pay calculation to preserve both regular and overtime pay components for display.
-
Signed fields for potentially negative results (Section: Arithmetic with Signed Numbers): The net pay field uses PIC S9 to correctly handle the case where deductions exceed gross pay.