> "When I tell developers from other languages that COBOL has built-in functions for standard deviation, present value, and annuity calculation, they are genuinely surprised. COBOL was doing domain-specific computing before it was fashionable." —...
In This Chapter
- 20.1 The FUNCTION Keyword: Syntax and Rules
- 20.2 String Functions
- 20.3 Numeric Functions
- 20.4 Mathematical Functions
- 20.5 Statistical Functions
- 20.6 Date and Time Functions
- 20.7 Financial Functions
- 20.8 The RANDOM Function and Simulation
- 20.9 Complete Worked Example: Data Validation and Cleansing Library
- 20.10 Working with MAX and MIN on Alphanumeric Data
- 20.11 Function Error Handling and Edge Cases
- 20.11 Combining Functions: Practical Patterns
- 20.12 Complete Worked Example: Financial Calculation Suite
- 20.13 GlobalBank: Transaction Analysis with Statistical Functions
- 20.10 MedClaim: Date Calculations and Numeric Validation
- 20.15 Practical Pattern: Data Cleansing Pipeline
- 20.16 COBOL 2002+ Function Additions
- 20.12 Function Quick Reference
- 20.13 The Student Mainframe Lab
- 20.14 Chapter Summary
Chapter 20: Intrinsic Functions
"When I tell developers from other languages that COBOL has built-in functions for standard deviation, present value, and annuity calculation, they are genuinely surprised. COBOL was doing domain-specific computing before it was fashionable." — Priya Kapoor, Architect, GlobalBank
One of the most persistent misconceptions about COBOL is that it lacks built-in functions. Programmers who learned COBOL decades ago may remember a language with no functions at all — and indeed, intrinsic functions were not part of the original language. They were introduced in the COBOL-85 amendment (1989) and substantially expanded in COBOL 2002 and COBOL 2014. Today, COBOL provides a rich library of intrinsic functions covering string operations, numeric conversions, mathematics, statistics, date processing, and financial calculations.
In this chapter, we provide comprehensive coverage of COBOL's intrinsic functions, organized by category. For each function, we give the syntax, explain its behavior, and demonstrate its use in realistic business contexts at GlobalBank and MedClaim. By the end of this chapter, you will have a practical command of every commonly used intrinsic function and an appreciation for why COBOL's function library makes it uniquely suited to business computing.
20.1 The FUNCTION Keyword: Syntax and Rules
All intrinsic functions are invoked using the FUNCTION keyword:
MOVE FUNCTION UPPER-CASE(WS-INPUT) TO WS-OUTPUT
COMPUTE WS-RESULT = FUNCTION SQRT(WS-NUMBER)
IF FUNCTION LENGTH(WS-FIELD) > 50
PERFORM 9100-OVERFLOW-HANDLER
END-IF
General Rules
- FUNCTION keyword is required. You cannot write just
UPPER-CASE(WS-INPUT)— it must beFUNCTION UPPER-CASE(WS-INPUT). - Functions can appear anywhere an identifier or literal of the appropriate type can appear. This includes MOVE, IF, EVALUATE, COMPUTE, STRING, DISPLAY, and more.
- Functions return values but do not modify their arguments. They are pure functions with no side effects.
- Functions can be nested:
FUNCTION UPPER-CASE(FUNCTION TRIM(WS-INPUT TRAILING)) - Arguments can be identifiers, literals, or expressions.
💡 Compiler Note: Some older compilers require specific options to enable intrinsic functions. On IBM Enterprise COBOL, they are always available. On GnuCOBOL, ensure you compile with
-std=cobol2014or similar. TheREPOSITORYparagraph was historically used to register functions but is no longer required on modern compilers.
The REPOSITORY Paragraph (Historical)
In older COBOL standards, you could (or had to) register intrinsic functions in the REPOSITORY paragraph:
CONFIGURATION SECTION.
REPOSITORY.
FUNCTION ALL INTRINSIC.
This FUNCTION ALL INTRINSIC declaration eliminates the need to write the FUNCTION keyword before each function call. With it, you can write simply UPPER-CASE(WS-INPUT) instead of FUNCTION UPPER-CASE(WS-INPUT). Many modern shops use this for brevity, but we will always include the FUNCTION keyword in this textbook for clarity.
20.2 String Functions
String functions manipulate and query alphanumeric data. They are among the most frequently used intrinsic functions in business COBOL programs.
LENGTH
Returns the number of characters (bytes for alphanumeric items) in a data item or literal.
01 WS-NAME PIC X(30) VALUE "MARIA CHEN".
01 WS-LEN PIC 99.
MOVE FUNCTION LENGTH(WS-NAME) TO WS-LEN
*> WS-LEN = 30 (allocated length, NOT trimmed length)
MOVE FUNCTION LENGTH("HELLO") TO WS-LEN
*> WS-LEN = 5
⚠️ Important: FUNCTION LENGTH returns the allocated length of a data item, including trailing spaces. To get the length of the actual content, use
FUNCTION LENGTH(FUNCTION TRIM(WS-NAME TRAILING)).
REVERSE
Returns the character-reversed form of the argument.
01 WS-INPUT PIC X(10) VALUE "ABCDE".
01 WS-OUTPUT PIC X(10).
MOVE FUNCTION REVERSE(WS-INPUT) TO WS-OUTPUT
*> WS-OUTPUT = " EDCBA" (spaces reversed too!)
MOVE FUNCTION REVERSE("12345") TO WS-OUTPUT
*> WS-OUTPUT = "54321 "
Note that REVERSE operates on the full allocated length of the data item, including trailing spaces. To reverse just the content:
MOVE FUNCTION REVERSE(
FUNCTION TRIM(WS-INPUT TRAILING))
TO WS-OUTPUT
*> WS-OUTPUT = "EDCBA "
UPPER-CASE and LOWER-CASE
Convert alphabetic characters to upper or lower case. Non-alphabetic characters are unchanged.
01 WS-MIXED PIC X(20) VALUE "Hello World 123".
01 WS-UPPER PIC X(20).
01 WS-LOWER PIC X(20).
MOVE FUNCTION UPPER-CASE(WS-MIXED) TO WS-UPPER
*> WS-UPPER = "HELLO WORLD 123 "
MOVE FUNCTION LOWER-CASE(WS-MIXED) TO WS-LOWER
*> WS-LOWER = "hello world 123 "
These are essential for case-insensitive comparisons:
IF FUNCTION UPPER-CASE(WS-USER-INPUT) = "YES"
PERFORM 3000-CONFIRM
END-IF
TRIM (COBOL 2002+)
Removes leading spaces, trailing spaces, or both from a string.
01 WS-PADDED PIC X(20) VALUE " HELLO ".
01 WS-RESULT PIC X(20).
MOVE FUNCTION TRIM(WS-PADDED) TO WS-RESULT
*> WS-RESULT = "HELLO " (both trimmed)
MOVE FUNCTION TRIM(WS-PADDED LEADING) TO WS-RESULT
*> WS-RESULT = "HELLO " (leading only)
MOVE FUNCTION TRIM(WS-PADDED TRAILING) TO WS-RESULT
*> WS-RESULT = " HELLO " (trailing only)
📊 At GlobalBank, FUNCTION TRIM is used extensively in the online banking display layer to remove padding from fixed-length COBOL fields before formatting them for web service responses. Derek Washington estimates it appears in over 200 programs.
ORD and CHAR
ORD returns the ordinal position of a character in the collating sequence (typically EBCDIC on mainframes, ASCII on open systems). CHAR does the reverse.
COMPUTE WS-ORD = FUNCTION ORD("A")
*> On EBCDIC: 194, On ASCII: 66
MOVE FUNCTION CHAR(194) TO WS-CHAR
*> On EBCDIC: "A"
These functions are primarily used for character classification and transformation:
*> Check if character is uppercase letter (ASCII)
IF FUNCTION ORD(WS-CHAR) >= FUNCTION ORD("A")
AND FUNCTION ORD(WS-CHAR) <= FUNCTION ORD("Z")
DISPLAY "UPPERCASE LETTER"
END-IF
CONCATENATE (COBOL 2014)
Joins two or more strings together:
MOVE FUNCTION CONCATENATE(
WS-FIRST-NAME, " ", WS-LAST-NAME)
TO WS-FULL-NAME
💡 Compiler Support: CONCATENATE is a COBOL 2014 addition. IBM Enterprise COBOL V6.3+ supports it. Older compilers may not have it. The STRING statement is the traditional alternative.
20.3 Numeric Functions
Numeric functions convert, validate, and transform numeric data.
NUMVAL and NUMVAL-C
NUMVAL converts an alphanumeric string containing a numeric value to a numeric item. NUMVAL-C does the same but handles currency symbols and comma/decimal formatting.
01 WS-INPUT-STR PIC X(15) VALUE " 1234.56 ".
01 WS-AMOUNT PIC 9(7)V99.
01 WS-CURR-STR PIC X(15) VALUE "$1,234.56".
COMPUTE WS-AMOUNT = FUNCTION NUMVAL(WS-INPUT-STR)
*> WS-AMOUNT = 1234.56
COMPUTE WS-AMOUNT =
FUNCTION NUMVAL-C(WS-CURR-STR, "$")
*> WS-AMOUNT = 1234.56
NUMVAL handles: - Leading and trailing spaces - Leading or trailing sign (+ or -) - Decimal point
NUMVAL-C additionally handles: - Currency symbol (specified as second argument) - Thousands separators (commas in US format)
⚠️ Runtime Risk: NUMVAL and NUMVAL-C will abend if the input string is not a valid numeric representation. Always validate input before calling these functions:
INSPECT WS-INPUT-STR TALLYING WS-ALPHA-COUNT
FOR ALL "A" THRU "Z"
IF WS-ALPHA-COUNT > ZERO
DISPLAY "NON-NUMERIC INPUT"
PERFORM 9100-INPUT-ERROR
ELSE
COMPUTE WS-AMOUNT =
FUNCTION NUMVAL(WS-INPUT-STR)
END-IF
INTEGER, INTEGER-PART, and MOD
01 WS-DECIMAL PIC S9(5)V99 VALUE 123.67.
01 WS-INT PIC S9(5).
COMPUTE WS-INT = FUNCTION INTEGER(WS-DECIMAL)
*> WS-INT = 123 (truncates toward zero)
COMPUTE WS-INT = FUNCTION INTEGER-PART(WS-DECIMAL)
*> WS-INT = 123 (same for positive; differs for
*> negative — see below)
COMPUTE WS-INT = FUNCTION INTEGER(-3.7)
*> WS-INT = -4 (toward negative infinity)
COMPUTE WS-INT = FUNCTION INTEGER-PART(-3.7)
*> WS-INT = -3 (toward zero)
COMPUTE WS-INT = FUNCTION MOD(17, 5)
*> WS-INT = 2 (17 mod 5 = 2)
COMPUTE WS-INT = FUNCTION REM(17, 5)
*> WS-INT = 2 (remainder of 17 / 5)
The distinction between INTEGER and INTEGER-PART matters for negative numbers: - INTEGER: Returns the greatest integer not greater than the argument (floor function) - INTEGER-PART: Returns the integer part by truncating toward zero
MOD and REM in Business Logic
MOD is particularly useful for check-digit calculations, even/odd tests, and cyclic processing:
*> Check digit validation (Luhn algorithm step)
IF FUNCTION MOD(WS-DIGIT-SUM, 10) = ZERO
SET WS-CHECKSUM-VALID TO TRUE
END-IF
*> Process every 10th record
IF FUNCTION MOD(WS-REC-COUNT, 10) = ZERO
PERFORM 5000-CHECKPOINT
END-IF
*> Determine quarter from month
COMPUTE WS-QUARTER =
FUNCTION INTEGER((WS-MONTH - 1) / 3) + 1
20.4 Mathematical Functions
COBOL provides standard mathematical functions that eliminate the need for hand-coded algorithms.
SQRT, LOG, LOG10, EXP
COMPUTE WS-ROOT = FUNCTION SQRT(144)
*> WS-ROOT = 12.00
COMPUTE WS-NAT-LOG = FUNCTION LOG(2.718281828)
*> WS-NAT-LOG = 1.00 (approximately)
COMPUTE WS-LOG10 = FUNCTION LOG10(1000)
*> WS-LOG10 = 3.00
COMPUTE WS-EXP-VAL = FUNCTION EXP(1)
*> WS-EXP-VAL = 2.718281828...
FACTORIAL
COMPUTE WS-FACT = FUNCTION FACTORIAL(5)
*> WS-FACT = 120
COMPUTE WS-FACT = FUNCTION FACTORIAL(0)
*> WS-FACT = 1 (by definition)
💡 Practical Use: While FACTORIAL may seem purely mathematical, it appears in combinatorial business calculations — for example, computing the number of ways to assign N employees to M positions, or calculating probability distributions for quality control.
ABS and SIGN
COMPUTE WS-ABSOLUTE = FUNCTION ABS(-42.5)
*> WS-ABSOLUTE = 42.5
COMPUTE WS-SGN = FUNCTION SIGN(-42.5)
*> WS-SGN = -1
*> SIGN returns: -1 if negative, 0 if zero, +1 if positive
ABS is essential for computing differences without caring about direction:
*> Variance from expected amount
COMPUTE WS-VARIANCE =
FUNCTION ABS(WS-ACTUAL - WS-EXPECTED)
IF WS-VARIANCE > WS-TOLERANCE
PERFORM 9100-OUT-OF-TOLERANCE
END-IF
20.5 Statistical Functions
COBOL's statistical functions are a standout feature that most other procedural languages lack as built-ins. They operate on a list of arguments or on a table of values.
MEAN, MEDIAN, VARIANCE, STANDARD-DEVIATION
01 WS-VALUES.
05 WS-VAL PIC 9(5)V99 OCCURS 5 TIMES.
01 WS-STAT-RESULT PIC 9(7)V9(4).
MOVE 100.00 TO WS-VAL(1)
MOVE 200.00 TO WS-VAL(2)
MOVE 150.00 TO WS-VAL(3)
MOVE 300.00 TO WS-VAL(4)
MOVE 250.00 TO WS-VAL(5)
COMPUTE WS-STAT-RESULT =
FUNCTION MEAN(WS-VAL(1) WS-VAL(2)
WS-VAL(3) WS-VAL(4)
WS-VAL(5))
*> MEAN = 200.00
COMPUTE WS-STAT-RESULT =
FUNCTION MEDIAN(WS-VAL(1) WS-VAL(2)
WS-VAL(3) WS-VAL(4)
WS-VAL(5))
*> MEDIAN = 200.00
COMPUTE WS-STAT-RESULT =
FUNCTION VARIANCE(WS-VAL(1) WS-VAL(2)
WS-VAL(3) WS-VAL(4)
WS-VAL(5))
*> VARIANCE = 5000.00
COMPUTE WS-STAT-RESULT =
FUNCTION STANDARD-DEVIATION(
WS-VAL(1) WS-VAL(2) WS-VAL(3)
WS-VAL(4) WS-VAL(5))
*> STANDARD-DEVIATION = 70.7107
Using Statistical Functions with Tables
You can pass all elements of a table using the ALL subscript:
01 TRANS-TABLE.
05 TRANS-COUNT PIC 9(5) COMP.
05 TRANS-ENTRY OCCURS 100 TIMES.
10 TRANS-AMOUNT PIC 9(7)V99.
10 TRANS-DATE PIC 9(8).
*> This computes mean of ALL trans amounts
COMPUTE WS-MEAN-AMT =
FUNCTION MEAN(TRANS-AMOUNT(ALL))
*> CAUTION: This includes all 100 entries,
*> even if only TRANS-COUNT are loaded!
⚠️ Critical Warning: When using the ALL subscript with statistical functions on a partially loaded table, ALL refers to the declared OCCURS count (e.g., 100), not the logical count. Unloaded entries contain zeros or garbage, skewing results. For partially loaded tables, you must list the elements explicitly or use a range:
*> Correct approach for partially loaded tables
MOVE ZERO TO WS-SUM
PERFORM VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > TRANS-COUNT
ADD TRANS-AMOUNT(WS-IDX) TO WS-SUM
END-PERFORM
COMPUTE WS-MEAN-AMT = WS-SUM / TRANS-COUNT
RANGE, MAX, MIN, SUM
COMPUTE WS-TOTAL =
FUNCTION SUM(WS-VAL(1) WS-VAL(2)
WS-VAL(3) WS-VAL(4)
WS-VAL(5))
*> SUM = 1000.00
COMPUTE WS-LARGEST =
FUNCTION MAX(WS-VAL(1) WS-VAL(2)
WS-VAL(3) WS-VAL(4)
WS-VAL(5))
*> MAX = 300.00
COMPUTE WS-SMALLEST =
FUNCTION MIN(WS-VAL(1) WS-VAL(2)
WS-VAL(3) WS-VAL(4)
WS-VAL(5))
*> MIN = 100.00
COMPUTE WS-SPREAD =
FUNCTION RANGE(WS-VAL(1) WS-VAL(2)
WS-VAL(3) WS-VAL(4)
WS-VAL(5))
*> RANGE = 200.00 (MAX - MIN)
ORD-MAX and ORD-MIN
These return the position of the maximum or minimum value among the arguments:
COMPUTE WS-MAX-POS =
FUNCTION ORD-MAX(WS-VAL(1) WS-VAL(2)
WS-VAL(3) WS-VAL(4)
WS-VAL(5))
*> WS-MAX-POS = 4 (WS-VAL(4) = 300.00 is the max)
COMPUTE WS-MIN-POS =
FUNCTION ORD-MIN(WS-VAL(1) WS-VAL(2)
WS-VAL(3) WS-VAL(4)
WS-VAL(5))
*> WS-MIN-POS = 1 (WS-VAL(1) = 100.00 is the min)
These are invaluable for finding which branch, account, or category has the highest or lowest value without writing comparison loops.
📊 GlobalBank Application: Maria Chen uses FUNCTION MEAN and FUNCTION STANDARD-DEVIATION to calculate daily transaction statistics for anomaly detection. Transactions more than 3 standard deviations from the mean trigger automatic fraud review.
Using Statistical Functions with Tables
In practice, statistical functions are most useful when applied to tables of data loaded from files. Here is a complete pattern for computing statistics on a table of values:
01 WS-DAILY-AMOUNTS.
05 WS-DA-COUNT PIC 9(5) VALUE ZERO.
05 WS-DA-VALUES.
10 WS-DA-VAL PIC 9(7)V99
OCCURS 1000 TIMES.
01 WS-STATS-OUTPUT.
05 WS-ST-MEAN PIC 9(9)V99.
05 WS-ST-MEDIAN PIC 9(9)V99.
05 WS-ST-STD-DEV PIC 9(9)V99.
05 WS-ST-VARIANCE PIC 9(11)V99.
05 WS-ST-MIN PIC 9(9)V99.
05 WS-ST-MAX PIC 9(9)V99.
05 WS-ST-RANGE PIC 9(9)V99.
05 WS-ST-SUM PIC 9(11)V99.
COMPUTE-TABLE-STATS.
* Compute all statistics for loaded table
* NOTE: COBOL statistical functions require
* listing each element explicitly, or using
* the ALL subscript (compiler dependent)
* With IBM Enterprise COBOL, you can use:
COMPUTE WS-ST-MEAN =
FUNCTION MEAN(
WS-DA-VAL(1) THRU
WS-DA-VAL(WS-DA-COUNT))
COMPUTE WS-ST-MEDIAN =
FUNCTION MEDIAN(
WS-DA-VAL(1) THRU
WS-DA-VAL(WS-DA-COUNT))
COMPUTE WS-ST-STD-DEV =
FUNCTION STANDARD-DEVIATION(
WS-DA-VAL(1) THRU
WS-DA-VAL(WS-DA-COUNT))
COMPUTE WS-ST-MIN =
FUNCTION MIN(
WS-DA-VAL(1) THRU
WS-DA-VAL(WS-DA-COUNT))
COMPUTE WS-ST-MAX =
FUNCTION MAX(
WS-DA-VAL(1) THRU
WS-DA-VAL(WS-DA-COUNT))
COMPUTE WS-ST-SUM =
FUNCTION SUM(
WS-DA-VAL(1) THRU
WS-DA-VAL(WS-DA-COUNT))
COMPUTE WS-ST-RANGE =
WS-ST-MAX - WS-ST-MIN
.
⚠️ Compiler Variation: The
THRUsyntax for table ranges in intrinsic function arguments is an IBM extension. Standard COBOL requires listing each argument explicitly or computing statistics manually. If portability is a concern, implement the calculations using loops:
COMPUTE-MEAN-PORTABLE.
* Portable implementation of MEAN
MOVE ZERO TO WS-ST-SUM
PERFORM VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > WS-DA-COUNT
ADD WS-DA-VAL(WS-IDX) TO WS-ST-SUM
END-PERFORM
COMPUTE WS-ST-MEAN =
WS-ST-SUM / WS-DA-COUNT
.
Anomaly Detection Pattern
A powerful application of statistical functions is automated anomaly detection. This pattern flags values that fall outside a configurable number of standard deviations from the mean:
01 WS-ANOMALY-THRESHOLD PIC 9V9 VALUE 3.0.
01 WS-UPPER-BOUND PIC 9(9)V99.
01 WS-LOWER-BOUND PIC 9(9)V99.
01 WS-ANOMALY-COUNT PIC 9(5) VALUE ZERO.
DETECT-ANOMALIES.
* Calculate bounds
COMPUTE WS-UPPER-BOUND =
WS-ST-MEAN +
(WS-ANOMALY-THRESHOLD * WS-ST-STD-DEV)
COMPUTE WS-LOWER-BOUND =
WS-ST-MEAN -
(WS-ANOMALY-THRESHOLD * WS-ST-STD-DEV)
IF WS-LOWER-BOUND < ZERO
MOVE ZERO TO WS-LOWER-BOUND
END-IF
* Scan for anomalies
MOVE ZERO TO WS-ANOMALY-COUNT
PERFORM VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > WS-DA-COUNT
IF WS-DA-VAL(WS-IDX) > WS-UPPER-BOUND
OR WS-DA-VAL(WS-IDX) < WS-LOWER-BOUND
ADD 1 TO WS-ANOMALY-COUNT
DISPLAY "ANOMALY: RECORD " WS-IDX
" VALUE " WS-DA-VAL(WS-IDX)
" OUTSIDE BOUNDS ["
WS-LOWER-BOUND " - "
WS-UPPER-BOUND "]"
END-IF
END-PERFORM
DISPLAY "ANOMALIES FOUND: " WS-ANOMALY-COUNT
" OUT OF " WS-DA-COUNT " RECORDS"
.
At GlobalBank, this pattern runs nightly on daily transaction volumes per branch. Derek Washington explains: "If a branch that normally processes 500 transactions suddenly processes 5,000, that is an anomaly worth investigating. The statistical functions make the detection trivial — the hard part is deciding what to do when you find one."
ORD-MAX and ORD-MIN: Finding Positions
While MAX and MIN return the extreme values, ORD-MAX and ORD-MIN return the position of the extreme value in the argument list. This is useful when you need to know which element is the outlier:
* Find which month had the highest revenue
01 WS-MONTHLY-REV.
05 WS-MR PIC 9(9)V99 OCCURS 12 TIMES.
01 WS-BEST-MONTH PIC 99.
01 WS-WORST-MONTH PIC 99.
COMPUTE WS-BEST-MONTH =
FUNCTION ORD-MAX(
WS-MR(1) WS-MR(2) WS-MR(3)
WS-MR(4) WS-MR(5) WS-MR(6)
WS-MR(7) WS-MR(8) WS-MR(9)
WS-MR(10) WS-MR(11) WS-MR(12))
COMPUTE WS-WORST-MONTH =
FUNCTION ORD-MIN(
WS-MR(1) WS-MR(2) WS-MR(3)
WS-MR(4) WS-MR(5) WS-MR(6)
WS-MR(7) WS-MR(8) WS-MR(9)
WS-MR(10) WS-MR(11) WS-MR(12))
DISPLAY "BEST MONTH: #" WS-BEST-MONTH
" REVENUE: " WS-MR(WS-BEST-MONTH)
DISPLAY "WORST MONTH: #" WS-WORST-MONTH
" REVENUE: " WS-MR(WS-WORST-MONTH)
💡 Practical Note: ORD-MAX and ORD-MIN return the position within the argument list, not the subscript. If you pass arguments starting at subscript 1, the positions map directly to subscripts. But if you pass a subset (e.g., only months 6-12), remember that position 1 in the result corresponds to your first argument (month 6), not subscript 1.
RANGE Function
The RANGE function computes the difference between the maximum and minimum values. While you could compute this as MAX - MIN, RANGE is more concise and expresses intent more clearly:
* Check if daily transaction amounts are clustered
* (small range) or dispersed (large range)
COMPUTE WS-DAILY-RANGE =
FUNCTION RANGE(
WS-DA-VAL(1) THRU
WS-DA-VAL(WS-DA-COUNT))
IF WS-DAILY-RANGE > (WS-ST-MEAN * 10)
DISPLAY "WARNING: EXTREME SPREAD IN "
"DAILY AMOUNTS"
DISPLAY "RANGE: " WS-DAILY-RANGE
" MEAN: " WS-ST-MEAN
END-IF
20.6 Date and Time Functions
Date functions are covered in depth in Chapter 21. Here we introduce the key date-related intrinsic functions.
CURRENT-DATE
Returns a 21-character string with the current date, time, and timezone offset:
01 WS-CURRENT-DT.
05 WS-CD-DATE.
10 WS-CD-YEAR PIC 9(4).
10 WS-CD-MONTH PIC 9(2).
10 WS-CD-DAY PIC 9(2).
05 WS-CD-TIME.
10 WS-CD-HOURS PIC 9(2).
10 WS-CD-MINS PIC 9(2).
10 WS-CD-SECS PIC 9(2).
10 WS-CD-HSECS PIC 9(2).
05 WS-CD-GMT-DIFF.
10 WS-CD-GMT-SIGN PIC X(1).
10 WS-CD-GMT-HOURS PIC 9(2).
10 WS-CD-GMT-MINS PIC 9(2).
MOVE FUNCTION CURRENT-DATE TO WS-CURRENT-DT
DISPLAY "DATE: " WS-CD-YEAR "-"
WS-CD-MONTH "-" WS-CD-DAY
DISPLAY "TIME: " WS-CD-HOURS ":"
WS-CD-MINS ":" WS-CD-SECS
INTEGER-OF-DATE and DATE-OF-INTEGER
These functions convert between calendar dates and integer day numbers, enabling date arithmetic:
01 WS-DATE-1 PIC 9(8) VALUE 20240101.
01 WS-DATE-2 PIC 9(8) VALUE 20240315.
01 WS-INT-1 PIC 9(7).
01 WS-INT-2 PIC 9(7).
01 WS-DAYS PIC 9(5).
COMPUTE WS-INT-1 =
FUNCTION INTEGER-OF-DATE(WS-DATE-1)
COMPUTE WS-INT-2 =
FUNCTION INTEGER-OF-DATE(WS-DATE-2)
COMPUTE WS-DAYS = WS-INT-2 - WS-INT-1
*> WS-DAYS = 74 (days between Jan 1 and Mar 15)
*> Add 90 days to a date
COMPUTE WS-INT-1 =
FUNCTION INTEGER-OF-DATE(20240615) + 90
COMPUTE WS-DATE-2 =
FUNCTION DATE-OF-INTEGER(WS-INT-1)
*> WS-DATE-2 = 20240913 (Sep 13, 2024)
WHEN-COMPILED
Returns the date and time the program was compiled, in the same 21-character format as CURRENT-DATE:
DISPLAY "COMPILED: " FUNCTION WHEN-COMPILED
*> Useful for version tracking in production
20.7 Financial Functions
COBOL's financial functions directly support the calculations that business systems perform daily.
PRESENT-VALUE
Calculates the present value of a series of future amounts at a given discount rate:
01 WS-PV PIC 9(9)V99.
01 WS-RATE PIC 9V9(6) VALUE 0.05.
*> Rate = 5% per period
COMPUTE WS-PV =
FUNCTION PRESENT-VALUE(WS-RATE,
1000.00, *> Period 1 cash flow
1000.00, *> Period 2 cash flow
1000.00, *> Period 3 cash flow
1000.00, *> Period 4 cash flow
1000.00) *> Period 5 cash flow
*> PV of 5 payments of $1,000 at 5% = $4,329.48
DISPLAY "PRESENT VALUE: " WS-PV
The formula applied is:
PV = amount1/(1+rate)^1 + amount2/(1+rate)^2 + ... + amountN/(1+rate)^N
ANNUITY
Returns the ratio of an annuity paid at the end of each period for a given number of periods at a given interest rate. Multiply by the principal to get the periodic payment:
01 WS-PRINCIPAL PIC 9(9)V99 VALUE 250000.00.
01 WS-MONTHLY-RT PIC 9V9(8).
01 WS-PAYMENT PIC 9(7)V99.
01 WS-PERIODS PIC 9(3) VALUE 360.
COMPUTE WS-MONTHLY-RT = 0.065 / 12
*> 6.5% annual rate / 12 months
COMPUTE WS-PAYMENT =
WS-PRINCIPAL *
FUNCTION ANNUITY(WS-MONTHLY-RT, WS-PERIODS)
DISPLAY "MONTHLY MORTGAGE PAYMENT: " WS-PAYMENT
*> Approximately $1,580.17
💡 GlobalBank Application: The ANNUITY function is used in GlobalBank's mortgage origination system, loan amortization calculators, and CD maturity projections. Before intrinsic functions, these calculations required hand-coded exponentiation and division that was error-prone and difficult to verify.
Practical Financial Calculation: Loan Amortization
01 WS-LOAN-FIELDS.
05 WS-LN-PRINCIPAL PIC 9(9)V99.
05 WS-LN-RATE PIC 9V9(8).
05 WS-LN-PAYMENT PIC 9(7)V99.
05 WS-LN-INTEREST PIC 9(7)V99.
05 WS-LN-PRINCIPAL-PMT PIC 9(7)V99.
05 WS-LN-BALANCE PIC 9(9)V99.
05 WS-LN-TOTAL-INT PIC 9(9)V99.
05 WS-LN-PERIOD PIC 9(3).
COMPUTE-AMORTIZATION.
MOVE WS-LN-PRINCIPAL TO WS-LN-BALANCE
MOVE ZERO TO WS-LN-TOTAL-INT
COMPUTE WS-LN-PAYMENT =
WS-LN-PRINCIPAL *
FUNCTION ANNUITY(WS-LN-RATE, 360)
PERFORM VARYING WS-LN-PERIOD
FROM 1 BY 1 UNTIL WS-LN-PERIOD > 360
* Interest for this period
COMPUTE WS-LN-INTEREST =
WS-LN-BALANCE * WS-LN-RATE
* Principal portion
COMPUTE WS-LN-PRINCIPAL-PMT =
WS-LN-PAYMENT - WS-LN-INTEREST
* Update balance
SUBTRACT WS-LN-PRINCIPAL-PMT
FROM WS-LN-BALANCE
ADD WS-LN-INTEREST TO WS-LN-TOTAL-INT
END-PERFORM
DISPLAY "TOTAL INTEREST PAID: "
WS-LN-TOTAL-INT
.
20.8 The RANDOM Function and Simulation
The RANDOM function generates pseudo-random numbers between 0 (inclusive) and 1 (exclusive). It is seeded with an integer argument on the first call, and subsequent calls without an argument continue the sequence:
*> Seed the generator with a reproducible seed
COMPUTE WS-DUMMY = FUNCTION RANDOM(42)
*> Generate 5 random numbers
PERFORM 5 TIMES
COMPUTE WS-RAND = FUNCTION RANDOM
DISPLAY WS-RAND
END-PERFORM
Generating Random Numbers in a Range
To generate a random integer between MIN and MAX (inclusive):
COMPUTE WS-RANDOM-INT =
FUNCTION INTEGER(
FUNCTION RANDOM * (WS-MAX - WS-MIN + 1))
+ WS-MIN
Seeding with CURRENT-DATE for Non-Reproducible Sequences
For testing and simulation, you may want a different sequence each run:
MOVE FUNCTION CURRENT-DATE(13:4) TO WS-SEED
COMPUTE WS-DUMMY = FUNCTION RANDOM(WS-SEED)
*> Seed based on current seconds + hundredths
Practical Application: Monte Carlo Simulation
At GlobalBank, Priya Kapoor uses the RANDOM function for simple Monte Carlo simulations to estimate risk exposure:
01 WS-SIMULATION.
05 WS-SIM-ITERATIONS PIC 9(5)
VALUE 10000.
05 WS-SIM-COUNT PIC 9(5).
05 WS-DEFAULT-COUNT PIC 9(5) VALUE ZERO.
05 WS-DEFAULT-RATE PIC 9V9(4).
05 WS-SIM-RAND PIC 9V9(8).
05 WS-DEFAULT-PROB PIC 9V9(4)
VALUE 0.0350.
RUN-DEFAULT-SIMULATION.
* Estimate how many loans out of a portfolio
* of 1,000 will default, given a 3.5% default rate
COMPUTE WS-DUMMY = FUNCTION RANDOM(12345)
MOVE ZERO TO WS-DEFAULT-COUNT
PERFORM WS-SIM-ITERATIONS TIMES
COMPUTE WS-SIM-RAND = FUNCTION RANDOM
IF WS-SIM-RAND < WS-DEFAULT-PROB
ADD 1 TO WS-DEFAULT-COUNT
END-IF
END-PERFORM
COMPUTE WS-DEFAULT-RATE =
WS-DEFAULT-COUNT / WS-SIM-ITERATIONS
DISPLAY "SIMULATED DEFAULT RATE: "
WS-DEFAULT-RATE
DISPLAY "EXPECTED: "
WS-DEFAULT-PROB
.
⚠️ Precision Warning: COBOL's RANDOM function is suitable for business simulations and testing but should not be used for cryptographic purposes. For security-sensitive applications, use system-provided cryptographic random number generators accessed through CALL statements to system services.
20.9 Complete Worked Example: Data Validation and Cleansing Library
In production systems, intrinsic functions are rarely used in isolation. They combine into validation and cleansing routines that process every input record before business logic executes. This section presents a complete data validation library that GlobalBank uses for incoming wire transfer records.
The Problem
GlobalBank receives wire transfer instructions from correspondent banks in a semi-structured format. Each record contains: - Beneficiary name (may include special characters, leading/trailing spaces) - Amount (formatted as "$1,234.56" or "1234.56" or "1,234" — inconsistent) - Date (YYYYMMDD, but sometimes MM/DD/YYYY or DD-MMM-YYYY) - Reference number (must be alphanumeric, 8-20 characters)
The validation library must clean each field, validate it, and either accept or reject the record.
01 WS-WIRE-REC.
05 WS-WR-BENEF-NAME PIC X(50).
05 WS-WR-AMOUNT-STR PIC X(20).
05 WS-WR-DATE-STR PIC X(15).
05 WS-WR-REF-NUM PIC X(20).
01 WS-CLEAN-REC.
05 WS-CR-BENEF-NAME PIC X(50).
05 WS-CR-AMOUNT PIC 9(9)V99.
05 WS-CR-DATE PIC 9(8).
05 WS-CR-REF-NUM PIC X(20).
05 WS-CR-VALID PIC X VALUE "Y".
88 WS-RECORD-VALID VALUE "Y".
88 WS-RECORD-INVALID VALUE "N".
05 WS-CR-ERROR-MSG PIC X(100).
01 WS-WORK-FIELDS.
05 WS-WK-NUM-TEST PIC 9(4).
05 WS-WK-AMOUNT-WORK PIC 9(9)V99.
05 WS-WK-TRIMMED PIC X(50).
05 WS-WK-UPPER PIC X(50).
05 WS-WK-FIELD-LEN PIC 9(3).
VALIDATE-WIRE-RECORD.
SET WS-RECORD-VALID TO TRUE
MOVE SPACES TO WS-CR-ERROR-MSG
* Step 1: Clean and validate beneficiary name
PERFORM VALIDATE-BENEF-NAME
* Step 2: Parse and validate amount
PERFORM VALIDATE-AMOUNT
* Step 3: Parse and validate date
PERFORM VALIDATE-DATE
* Step 4: Validate reference number
PERFORM VALIDATE-REF-NUM
.
VALIDATE-BENEF-NAME.
* Trim leading/trailing spaces
MOVE FUNCTION TRIM(WS-WR-BENEF-NAME)
TO WS-WK-TRIMMED
* Check minimum length
IF FUNCTION LENGTH(
FUNCTION TRIM(WS-WR-BENEF-NAME))
< 2
SET WS-RECORD-INVALID TO TRUE
STRING WS-CR-ERROR-MSG DELIMITED SPACES
"BENEF NAME TOO SHORT; "
DELIMITED SIZE
INTO WS-CR-ERROR-MSG
END-IF
* Convert to upper case for standardization
MOVE FUNCTION UPPER-CASE(WS-WK-TRIMMED)
TO WS-CR-BENEF-NAME
.
VALIDATE-AMOUNT.
* First try NUMVAL-C (handles $, commas, signs)
COMPUTE WS-WK-NUM-TEST =
FUNCTION TEST-NUMVAL-C(
WS-WR-AMOUNT-STR "$")
IF WS-WK-NUM-TEST = ZERO
* String is valid for NUMVAL-C conversion
COMPUTE WS-CR-AMOUNT =
FUNCTION NUMVAL-C(
WS-WR-AMOUNT-STR "$")
* Business rule: amount must be > 0
* and <= 10,000,000
IF WS-CR-AMOUNT <= ZERO
SET WS-RECORD-INVALID TO TRUE
STRING WS-CR-ERROR-MSG DELIMITED SPACES
"AMOUNT <= ZERO; "
DELIMITED SIZE
INTO WS-CR-ERROR-MSG
END-IF
IF WS-CR-AMOUNT > 10000000
SET WS-RECORD-INVALID TO TRUE
STRING WS-CR-ERROR-MSG DELIMITED SPACES
"AMOUNT EXCEEDS LIMIT; "
DELIMITED SIZE
INTO WS-CR-ERROR-MSG
END-IF
ELSE
* Not a valid numeric string
SET WS-RECORD-INVALID TO TRUE
STRING WS-CR-ERROR-MSG DELIMITED SPACES
"INVALID AMOUNT FORMAT; "
DELIMITED SIZE
INTO WS-CR-ERROR-MSG
END-IF
.
💡 Production Insight: Derek Washington notes: "TEST-NUMVAL-C before NUMVAL-C is a non-negotiable pattern at GlobalBank. We process 2 million wire transfers per day. Before we added the TEST-NUMVAL-C guard, we got about 50 abends per week from bad amount formats in correspondent bank feeds. The test-first pattern reduced that to zero."
Cascading Validation Pattern
The validate-date paragraph illustrates a cascading validation pattern where each check builds on the previous:
VALIDATE-DATE.
* Attempt to parse the date in multiple formats
* Try YYYYMMDD first (most common)
IF FUNCTION TEST-NUMVAL(WS-WR-DATE-STR) = ZERO
COMPUTE WS-CR-DATE =
FUNCTION NUMVAL(WS-WR-DATE-STR)
* Validate the parsed date
COMPUTE WS-WK-NUM-TEST =
FUNCTION TEST-DATE-YYYYMMDD(WS-CR-DATE)
IF WS-WK-NUM-TEST = ZERO
* Valid date — check business rules
PERFORM CHECK-DATE-BUSINESS-RULES
ELSE
SET WS-RECORD-INVALID TO TRUE
STRING WS-CR-ERROR-MSG DELIMITED SPACES
"INVALID CALENDAR DATE; "
DELIMITED SIZE
INTO WS-CR-ERROR-MSG
END-IF
ELSE
SET WS-RECORD-INVALID TO TRUE
STRING WS-CR-ERROR-MSG DELIMITED SPACES
"DATE NOT NUMERIC; "
DELIMITED SIZE
INTO WS-CR-ERROR-MSG
END-IF
.
CHECK-DATE-BUSINESS-RULES.
* Date must not be in the future
IF WS-CR-DATE > FUNCTION CURRENT-DATE(1:8)
SET WS-RECORD-INVALID TO TRUE
STRING WS-CR-ERROR-MSG DELIMITED SPACES
"FUTURE DATE; " DELIMITED SIZE
INTO WS-CR-ERROR-MSG
END-IF
* Date must not be more than 90 days in the past
COMPUTE WS-WK-NUM-TEST =
FUNCTION INTEGER-OF-DATE(
FUNCTION CURRENT-DATE(1:8))
- FUNCTION INTEGER-OF-DATE(WS-CR-DATE)
IF WS-WK-NUM-TEST > 90
SET WS-RECORD-INVALID TO TRUE
STRING WS-CR-ERROR-MSG DELIMITED SPACES
"DATE > 90 DAYS OLD; "
DELIMITED SIZE
INTO WS-CR-ERROR-MSG
END-IF
.
VALIDATE-REF-NUM.
* Trim and check length
MOVE FUNCTION TRIM(WS-WR-REF-NUM)
TO WS-CR-REF-NUM
MOVE FUNCTION LENGTH(
FUNCTION TRIM(WS-WR-REF-NUM))
TO WS-WK-FIELD-LEN
IF WS-WK-FIELD-LEN < 8
OR WS-WK-FIELD-LEN > 20
SET WS-RECORD-INVALID TO TRUE
STRING WS-CR-ERROR-MSG DELIMITED SPACES
"REF NUM LENGTH INVALID; "
DELIMITED SIZE
INTO WS-CR-ERROR-MSG
END-IF
* Convert to upper case for storage
MOVE FUNCTION UPPER-CASE(WS-CR-REF-NUM)
TO WS-CR-REF-NUM
.
This pattern demonstrates how multiple intrinsic functions collaborate in a single validation flow: TRIM cleans whitespace, UPPER-CASE standardizes case, TEST-NUMVAL and TEST-NUMVAL-C guard against invalid data, NUMVAL-C converts formatted amounts, and INTEGER-OF-DATE enables date arithmetic for business rule checking.
📊 Metrics from Production: After implementing this validation library, GlobalBank's wire transfer error rate dropped from 2.3% to 0.08%. The most common rejection reason is "INVALID AMOUNT FORMAT" (48%), followed by "DATE > 90 DAYS OLD" (22%) and "BENEF NAME TOO SHORT" (15%).
Safe Wrapper Functions for Repeated Use
When the same validation pattern is needed across multiple programs, Priya Kapoor encapsulates it in a called subprogram:
*============================================================
* PROGRAM: NUMCLEAN
* PURPOSE: Safe numeric conversion with validation.
* Returns cleaned numeric value and status.
*============================================================
IDENTIFICATION DIVISION.
PROGRAM-ID. NUMCLEAN.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TEST-RESULT PIC 9(4).
LINKAGE SECTION.
01 LS-INPUT PIC X(30).
01 LS-CURRENCY-SYM PIC X.
01 LS-OUTPUT PIC 9(13)V99.
01 LS-STATUS PIC 9.
88 LS-VALID VALUE 0.
88 LS-INVALID VALUE 1.
88 LS-OVERFLOW VALUE 2.
PROCEDURE DIVISION USING LS-INPUT
LS-CURRENCY-SYM
LS-OUTPUT
LS-STATUS.
0000-MAIN.
MOVE ZERO TO LS-OUTPUT
SET LS-VALID TO TRUE
* Test before convert
IF LS-CURRENCY-SYM = SPACES
COMPUTE WS-TEST-RESULT =
FUNCTION TEST-NUMVAL(LS-INPUT)
IF WS-TEST-RESULT = ZERO
COMPUTE LS-OUTPUT =
FUNCTION NUMVAL(LS-INPUT)
ON SIZE ERROR
SET LS-OVERFLOW TO TRUE
END-COMPUTE
ELSE
SET LS-INVALID TO TRUE
END-IF
ELSE
COMPUTE WS-TEST-RESULT =
FUNCTION TEST-NUMVAL-C(
LS-INPUT LS-CURRENCY-SYM)
IF WS-TEST-RESULT = ZERO
COMPUTE LS-OUTPUT =
FUNCTION NUMVAL-C(
LS-INPUT LS-CURRENCY-SYM)
ON SIZE ERROR
SET LS-OVERFLOW TO TRUE
END-COMPUTE
ELSE
SET LS-INVALID TO TRUE
END-IF
END-IF
GOBACK
.
Calling programs use it simply:
CALL "NUMCLEAN" USING WS-AMOUNT-STRING
WS-DOLLAR-SIGN
WS-AMOUNT-NUMERIC
WS-AMOUNT-STATUS
IF WS-AMOUNT-STATUS = ZERO
PERFORM PROCESS-VALID-AMOUNT
ELSE
PERFORM HANDLE-BAD-AMOUNT
END-IF
✅ Best Practice: Encapsulating intrinsic function patterns in subprograms provides three benefits: (1) consistent validation logic across all programs, (2) a single place to fix bugs or tighten rules, and (3) protection against future compiler changes to function behavior. GlobalBank has six such utility subprograms: NUMCLEAN, DATECLEAN, STRCLEAN, NAMECLEAN, ACCTVALID, and AMTFORMAT.
Complete MedClaim Validation Pipeline
At MedClaim, incoming claim records pass through a multi-stage validation pipeline that uses nearly every category of intrinsic function. Here is the complete flow for a single claim:
01 WS-RAW-CLAIM.
05 WS-RC-PATIENT-NAME PIC X(50).
05 WS-RC-DOB-STR PIC X(10).
05 WS-RC-SSN PIC X(11).
05 WS-RC-CHARGE-AMT PIC X(15).
05 WS-RC-SVC-DATE-STR PIC X(10).
05 WS-RC-DIAG-CODE PIC X(10).
05 WS-RC-PROC-CODE PIC X(10).
05 WS-RC-PAYER-ID PIC X(20).
01 WS-CLEAN-CLAIM.
05 WS-CC-PATIENT-NAME PIC X(50).
05 WS-CC-DOB PIC 9(8).
05 WS-CC-SSN PIC 9(9).
05 WS-CC-CHARGE-AMT PIC 9(7)V99.
05 WS-CC-SVC-DATE PIC 9(8).
05 WS-CC-DIAG-CODE PIC X(10).
05 WS-CC-PROC-CODE PIC X(10).
05 WS-CC-PAYER-ID PIC X(20).
05 WS-CC-ERRORS PIC X(200).
05 WS-CC-ERROR-CNT PIC 99 VALUE ZERO.
VALIDATE-CLAIM-RECORD.
MOVE ZERO TO WS-CC-ERROR-CNT
MOVE SPACES TO WS-CC-ERRORS
* Stage 1: String cleaning
* Trim and uppercase patient name
MOVE FUNCTION UPPER-CASE(
FUNCTION TRIM(WS-RC-PATIENT-NAME))
TO WS-CC-PATIENT-NAME
* Stage 2: Numeric conversions with guards
* Convert charge amount
COMPUTE WS-WK-NUM-TEST =
FUNCTION TEST-NUMVAL-C(
WS-RC-CHARGE-AMT "$")
IF WS-WK-NUM-TEST = ZERO
COMPUTE WS-CC-CHARGE-AMT =
FUNCTION NUMVAL-C(
WS-RC-CHARGE-AMT "$")
IF WS-CC-CHARGE-AMT <= ZERO
ADD 1 TO WS-CC-ERROR-CNT
STRING WS-CC-ERRORS DELIMITED SPACES
"CHARGE<=0; " DELIMITED SIZE
INTO WS-CC-ERRORS
END-IF
ELSE
ADD 1 TO WS-CC-ERROR-CNT
STRING WS-CC-ERRORS DELIMITED SPACES
"BAD CHARGE FMT; " DELIMITED SIZE
INTO WS-CC-ERRORS
END-IF
* Stage 3: Date validation
* Parse service date (may be YYYYMMDD or
* MMDDYYYY or MM/DD/YYYY)
PERFORM PARSE-FLEXIBLE-DATE
IF WS-CC-SVC-DATE > ZEROES
* Check not in future
IF WS-CC-SVC-DATE >
FUNCTION CURRENT-DATE(1:8)
ADD 1 TO WS-CC-ERROR-CNT
STRING WS-CC-ERRORS DELIMITED SPACES
"FUTURE SVC DATE; "
DELIMITED SIZE
INTO WS-CC-ERRORS
END-IF
* Check not more than 1 year old
COMPUTE WS-DATE-AGE =
FUNCTION INTEGER-OF-DATE(
FUNCTION CURRENT-DATE(1:8))
- FUNCTION INTEGER-OF-DATE(
WS-CC-SVC-DATE)
IF WS-DATE-AGE > 365
ADD 1 TO WS-CC-ERROR-CNT
STRING WS-CC-ERRORS DELIMITED SPACES
"SVC DATE>1YR OLD; "
DELIMITED SIZE
INTO WS-CC-ERRORS
END-IF
END-IF
* Stage 4: Code validation
* Uppercase and trim diagnosis code
MOVE FUNCTION UPPER-CASE(
FUNCTION TRIM(WS-RC-DIAG-CODE))
TO WS-CC-DIAG-CODE
* Verify minimum length
IF FUNCTION LENGTH(
FUNCTION TRIM(WS-CC-DIAG-CODE)) < 3
ADD 1 TO WS-CC-ERROR-CNT
STRING WS-CC-ERRORS DELIMITED SPACES
"DIAG CODE TOO SHORT; "
DELIMITED SIZE
INTO WS-CC-ERRORS
END-IF
* Stage 5: Cross-field validation
* Patient DOB must be before service date
IF WS-CC-DOB > ZEROES
AND WS-CC-SVC-DATE > ZEROES
IF WS-CC-DOB >= WS-CC-SVC-DATE
ADD 1 TO WS-CC-ERROR-CNT
STRING WS-CC-ERRORS DELIMITED SPACES
"DOB >= SVC DATE; "
DELIMITED SIZE
INTO WS-CC-ERRORS
END-IF
END-IF
.
This pipeline demonstrates the synergy between function categories: string functions (TRIM, UPPER-CASE) clean text fields; numeric functions (TEST-NUMVAL-C, NUMVAL-C) safely convert amounts; date functions (CURRENT-DATE, INTEGER-OF-DATE) enable temporal validation. Sarah Kim notes: "Every claim touches at least 8 different intrinsic functions during validation. Before we had these functions, each validation step was 20-30 lines of hand-coded logic. Now it is 2-3 lines. The reduction in code volume alone cut our bug rate by 40%."
🔴 Common Mistake: Do not use NUMVAL or NUMVAL-C without the corresponding TEST function first. James Okafor's team tracked the root cause of production abends over a six-month period and found that 31% were caused by NUMVAL/NUMVAL-C calls on fields that had not been validated. The fix was simple: make TEST-NUMVAL/TEST-NUMVAL-C mandatory in the coding standards, enforced by a pre-compile scanner.
20.10 Working with MAX and MIN on Alphanumeric Data
The MAX and MIN functions work with alphanumeric arguments as well as numeric:
MOVE FUNCTION MAX("APPLE", "BANANA", "CHERRY")
TO WS-RESULT
*> WS-RESULT = "CHERRY" (highest in collating sequence)
MOVE FUNCTION MIN("APPLE", "BANANA", "CHERRY")
TO WS-RESULT
*> WS-RESULT = "APPLE" (lowest in collating sequence)
This is useful for finding the latest or earliest date from a set of date strings:
MOVE FUNCTION MAX(WS-DATE-1, WS-DATE-2, WS-DATE-3)
TO WS-LATEST-DATE
*> Returns the latest date (works because YYYYMMDD
*> format sorts correctly as a string)
MOVE FUNCTION MIN(WS-DATE-1, WS-DATE-2, WS-DATE-3)
TO WS-EARLIEST-DATE
Using MAX/MIN for Clamping Values
A common business requirement is ensuring a value stays within bounds:
*> Ensure interest rate is between 0% and 25%
COMPUTE WS-CLAMPED-RATE =
FUNCTION MAX(0,
FUNCTION MIN(25, WS-CALCULATED-RATE))
*> Ensure payment is at least $25 but no more
*> than the balance
COMPUTE WS-PAYMENT =
FUNCTION MAX(25.00,
FUNCTION MIN(WS-BALANCE,
WS-REQUESTED-PAYMENT))
This "clamp" pattern replaces multiple IF statements with a single expression, making the logic clearer and less error-prone.
Case-Insensitive String Comparison
COBOL's string comparison is case-sensitive by default. The UPPER-CASE function enables case-insensitive comparisons:
* Case-insensitive comparison
IF FUNCTION UPPER-CASE(WS-INPUT-STATE)
= FUNCTION UPPER-CASE(WS-TABLE-STATE)
SET WS-STATE-MATCH TO TRUE
END-IF
* Case-insensitive SEARCH
* First, uppercase the search key
MOVE FUNCTION UPPER-CASE(WS-SEARCH-NAME)
TO WS-UPPER-KEY
* Then search against pre-uppercased table
SEARCH ALL WS-NAME-ENTRY
AT END
SET WS-NOT-FOUND TO TRUE
WHEN WS-NAME-UPPER(WS-NAME-IDX)
= WS-UPPER-KEY
SET WS-FOUND TO TRUE
END-SEARCH
💡 Performance Tip: If you need case-insensitive searches against a table, uppercase the table entries once at load time rather than uppercasing the search key on every comparison. For a 10,000-entry table searched 50,000 times per batch run, this saves 50,000 UPPER-CASE function calls — a measurable performance improvement on large volumes.
String Concatenation with TRIM
Building output strings by concatenating trimmed fields is a common pattern:
* Build a full name from components
STRING FUNCTION TRIM(WS-LAST-NAME)
DELIMITED SIZE
", " DELIMITED SIZE
FUNCTION TRIM(WS-FIRST-NAME)
DELIMITED SIZE
" " DELIMITED SIZE
FUNCTION TRIM(WS-MIDDLE-INIT)
DELIMITED SIZE
INTO WS-FULL-NAME
END-STRING
* Build an address line
STRING FUNCTION TRIM(WS-CITY)
DELIMITED SIZE
", " DELIMITED SIZE
FUNCTION TRIM(WS-STATE)
DELIMITED SIZE
" " DELIMITED SIZE
FUNCTION TRIM(WS-ZIP)
DELIMITED SIZE
INTO WS-CITY-STATE-ZIP
END-STRING
Without TRIM, the STRING statement would include trailing spaces from each field, producing output like "SMITH , JOHN " instead of "SMITH, JOHN". The TRIM function is essential for producing clean, readable output from fixed-length COBOL fields.
Combining MOD and INTEGER for Cyclic Patterns
The MOD and INTEGER functions combine to solve cyclic problems common in batch processing:
* Process every Nth record (for sampling)
IF FUNCTION MOD(WS-RECORD-COUNT, 100) = 0
PERFORM WRITE-SAMPLE-RECORD
END-IF
* Rotate through 5 output files
COMPUTE WS-FILE-NUM =
FUNCTION MOD(WS-RECORD-COUNT, 5) + 1
EVALUATE WS-FILE-NUM
WHEN 1 WRITE OUT-REC-1 FROM WS-RECORD
WHEN 2 WRITE OUT-REC-2 FROM WS-RECORD
WHEN 3 WRITE OUT-REC-3 FROM WS-RECORD
WHEN 4 WRITE OUT-REC-4 FROM WS-RECORD
WHEN 5 WRITE OUT-REC-5 FROM WS-RECORD
END-EVALUATE
* Check digit calculation (Luhn algorithm step)
COMPUTE WS-CHECK-DIGIT =
FUNCTION MOD(10 - FUNCTION MOD(
WS-RUNNING-SUM, 10), 10)
20.11 Function Error Handling and Edge Cases
Intrinsic functions can fail at runtime in several ways. Understanding these failure modes is essential for defensive programming.
Functions That Can Abend
| Function | Abend Condition |
|---|---|
| NUMVAL | Non-numeric input |
| NUMVAL-C | Non-numeric input (excluding currency/comma) |
| SQRT | Negative argument |
| LOG | Zero or negative argument |
| LOG10 | Zero or negative argument |
| INTEGER-OF-DATE | Invalid date (month 13, day 32, etc.) |
| INTEGER-OF-DAY | Invalid Julian date |
Safe Wrappers
Build safe wrapper paragraphs for dangerous functions:
SAFE-SQRT.
IF WS-INPUT-VAL < ZERO
MOVE ZERO TO WS-SQRT-RESULT
SET WS-SQRT-ERROR TO TRUE
ELSE
COMPUTE WS-SQRT-RESULT =
FUNCTION SQRT(WS-INPUT-VAL)
SET WS-SQRT-OK TO TRUE
END-IF
.
SAFE-NUMVAL.
IF FUNCTION TEST-NUMVAL(WS-INPUT-STR)
= ZERO
COMPUTE WS-NUM-RESULT =
FUNCTION NUMVAL(WS-INPUT-STR)
SET WS-CONVERT-OK TO TRUE
ELSE
MOVE ZERO TO WS-NUM-RESULT
SET WS-CONVERT-ERROR TO TRUE
DISPLAY "INVALID NUMERIC: ["
FUNCTION TRIM(WS-INPUT-STR)
"]"
END-IF
.
SAFE-LOG.
IF WS-INPUT-VAL <= ZERO
MOVE ZERO TO WS-LOG-RESULT
SET WS-LOG-ERROR TO TRUE
ELSE
COMPUTE WS-LOG-RESULT =
FUNCTION LOG(WS-INPUT-VAL)
SET WS-LOG-OK TO TRUE
END-IF
.
✅ Best Practice: At GlobalBank, Maria Chen requires that every call to NUMVAL, NUMVAL-C, SQRT, LOG, LOG10, and INTEGER-OF-DATE use a safe wrapper. "An abend in production costs us $500 per minute in incident response. A five-line wrapper costs nothing."
Precision and Rounding
Intrinsic functions inherit COBOL's decimal arithmetic precision. Results are truncated or rounded based on the receiving field's picture clause:
01 WS-PRECISE PIC 9(7)V9(8).
01 WS-TRUNCATED PIC 9(7)V99.
COMPUTE WS-PRECISE = FUNCTION SQRT(2)
*> WS-PRECISE = 1.41421356
COMPUTE WS-TRUNCATED = FUNCTION SQRT(2)
*> WS-TRUNCATED = 1.41 (truncated to 2 decimals)
COMPUTE WS-TRUNCATED ROUNDED =
FUNCTION SQRT(2)
*> WS-TRUNCATED = 1.41 (rounded to 2 decimals)
For financial calculations, always use the ROUNDED phrase and ensure receiving fields have sufficient decimal positions. At GlobalBank, all monetary calculations use at least V9(4) for intermediate results, then round to V99 for final amounts.
20.11 Combining Functions: Practical Patterns
The real power of intrinsic functions emerges when you combine them.
Pattern 1: Safe Numeric Conversion
*> Convert user input to number, with validation
IF WS-INPUT = SPACES
MOVE ZERO TO WS-AMOUNT
ELSE
MOVE FUNCTION TRIM(WS-INPUT) TO WS-TRIMMED
COMPUTE WS-AMOUNT =
FUNCTION NUMVAL-C(WS-TRIMMED, "$")
END-IF
Pattern 2: Case-Insensitive Comparison
IF FUNCTION UPPER-CASE(
FUNCTION TRIM(WS-STATUS-INPUT))
= "ACTIVE"
SET WS-STATUS-ACTIVE TO TRUE
END-IF
Pattern 3: Dynamic Date Arithmetic
*> Calculate days until deadline
MOVE FUNCTION CURRENT-DATE TO WS-CURRENT-DT
MOVE WS-CD-DATE TO WS-TODAY-NUM
COMPUTE WS-TODAY-INT =
FUNCTION INTEGER-OF-DATE(WS-TODAY-NUM)
COMPUTE WS-DEADLINE-INT =
FUNCTION INTEGER-OF-DATE(WS-DEADLINE-DATE)
COMPUTE WS-DAYS-LEFT =
WS-DEADLINE-INT - WS-TODAY-INT
IF WS-DAYS-LEFT < ZERO
DISPLAY "DEADLINE HAS PASSED"
ELSE IF WS-DAYS-LEFT = ZERO
DISPLAY "DEADLINE IS TODAY"
ELSE
DISPLAY WS-DAYS-LEFT " DAYS REMAINING"
END-IF
Pattern 4: Statistical Anomaly Detection
*> At GlobalBank: flag transactions > 3 std devs
COMPUTE WS-MEAN =
FUNCTION MEAN(
TRANS-AMOUNT(1) TRANS-AMOUNT(2)
TRANS-AMOUNT(3) TRANS-AMOUNT(4)
TRANS-AMOUNT(5) TRANS-AMOUNT(6)
TRANS-AMOUNT(7) TRANS-AMOUNT(8)
TRANS-AMOUNT(9) TRANS-AMOUNT(10))
COMPUTE WS-STD-DEV =
FUNCTION STANDARD-DEVIATION(
TRANS-AMOUNT(1) TRANS-AMOUNT(2)
TRANS-AMOUNT(3) TRANS-AMOUNT(4)
TRANS-AMOUNT(5) TRANS-AMOUNT(6)
TRANS-AMOUNT(7) TRANS-AMOUNT(8)
TRANS-AMOUNT(9) TRANS-AMOUNT(10))
COMPUTE WS-UPPER-LIMIT =
WS-MEAN + (3 * WS-STD-DEV)
IF WS-NEW-TRANS-AMT > WS-UPPER-LIMIT
PERFORM 8000-FLAG-FOR-REVIEW
END-IF
20.12 Complete Worked Example: Financial Calculation Suite
Let us build a comprehensive financial calculation program that demonstrates COBOL's financial intrinsic functions in a realistic banking context. This program computes loan payments, CD maturity values, and amortization schedules.
IDENTIFICATION DIVISION.
PROGRAM-ID. FINCALC.
*============================================================
* GlobalBank Financial Calculation Suite
* Demonstrates ANNUITY, PRESENT-VALUE, EXP, LOG
* in banking calculations.
*============================================================
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-LOAN-PARAMS.
05 WS-LP-PRINCIPAL PIC 9(9)V99.
05 WS-LP-ANN-RATE PIC 9(2)V9(6).
05 WS-LP-MONTHLY-RT PIC 9V9(8).
05 WS-LP-TERM-MONTHS PIC 9(3).
05 WS-LP-PAYMENT PIC 9(7)V99.
01 WS-CD-PARAMS.
05 WS-CD-DEPOSIT PIC 9(9)V99.
05 WS-CD-RATE PIC 9(2)V9(6).
05 WS-CD-MONTHS PIC 9(3).
05 WS-CD-MATURITY PIC 9(9)V99.
05 WS-CD-INTEREST PIC 9(9)V99.
01 WS-AMORT-LINE.
05 WS-AM-PERIOD PIC 9(3).
05 WS-AM-PAYMENT PIC 9(7)V99.
05 WS-AM-INTEREST PIC 9(7)V99.
05 WS-AM-PRINCIPAL PIC 9(7)V99.
05 WS-AM-BALANCE PIC 9(9)V99.
01 WS-DISPLAY-AMT PIC $$$,$$$,$$9.99.
01 WS-TOTAL-INTEREST PIC 9(9)V99.
01 WS-TOTAL-PAID PIC 9(9)V99.
PROCEDURE DIVISION.
0000-MAIN.
PERFORM 1000-MORTGAGE-CALCULATION
PERFORM 2000-CD-MATURITY
PERFORM 3000-LOAN-COMPARISON
PERFORM 4000-AMORTIZATION-SCHEDULE
STOP RUN
.
1000-MORTGAGE-CALCULATION.
DISPLAY "========================================"
DISPLAY " MORTGAGE PAYMENT CALCULATOR"
DISPLAY "========================================"
MOVE 350000.00 TO WS-LP-PRINCIPAL
MOVE 06.750000 TO WS-LP-ANN-RATE
MOVE 360 TO WS-LP-TERM-MONTHS
COMPUTE WS-LP-MONTHLY-RT =
WS-LP-ANN-RATE / 100 / 12
COMPUTE WS-LP-PAYMENT =
WS-LP-PRINCIPAL *
FUNCTION ANNUITY(WS-LP-MONTHLY-RT,
WS-LP-TERM-MONTHS)
COMPUTE WS-TOTAL-PAID =
WS-LP-PAYMENT * WS-LP-TERM-MONTHS
COMPUTE WS-TOTAL-INTEREST =
WS-TOTAL-PAID - WS-LP-PRINCIPAL
MOVE WS-LP-PRINCIPAL TO WS-DISPLAY-AMT
DISPLAY "PRINCIPAL: " WS-DISPLAY-AMT
DISPLAY "ANNUAL RATE: " WS-LP-ANN-RATE "%"
DISPLAY "TERM: 360 MONTHS"
MOVE WS-LP-PAYMENT TO WS-DISPLAY-AMT
DISPLAY "MONTHLY PAYMENT: " WS-DISPLAY-AMT
MOVE WS-TOTAL-PAID TO WS-DISPLAY-AMT
DISPLAY "TOTAL PAID: " WS-DISPLAY-AMT
MOVE WS-TOTAL-INTEREST TO WS-DISPLAY-AMT
DISPLAY "TOTAL INTEREST: " WS-DISPLAY-AMT
DISPLAY " "
.
2000-CD-MATURITY.
DISPLAY "========================================"
DISPLAY " CD MATURITY CALCULATOR"
DISPLAY "========================================"
MOVE 100000.00 TO WS-CD-DEPOSIT
MOVE 04.500000 TO WS-CD-RATE
MOVE 60 TO WS-CD-MONTHS
* Compound interest: Deposit * EXP(n * LOG(1+r/12))
COMPUTE WS-CD-MATURITY =
WS-CD-DEPOSIT *
FUNCTION EXP(
WS-CD-MONTHS *
FUNCTION LOG(
1 + WS-CD-RATE / 100 / 12))
COMPUTE WS-CD-INTEREST =
WS-CD-MATURITY - WS-CD-DEPOSIT
MOVE WS-CD-DEPOSIT TO WS-DISPLAY-AMT
DISPLAY "DEPOSIT: " WS-DISPLAY-AMT
DISPLAY "ANNUAL RATE: " WS-CD-RATE "%"
DISPLAY "TERM: 60 MONTHS"
MOVE WS-CD-MATURITY TO WS-DISPLAY-AMT
DISPLAY "MATURITY VALUE: " WS-DISPLAY-AMT
MOVE WS-CD-INTEREST TO WS-DISPLAY-AMT
DISPLAY "INTEREST EARNED: " WS-DISPLAY-AMT
DISPLAY " "
.
3000-LOAN-COMPARISON.
DISPLAY "========================================"
DISPLAY " 15 vs 30 YEAR COMPARISON"
DISPLAY "========================================"
MOVE 300000.00 TO WS-LP-PRINCIPAL
* 15-year at 6.25%
MOVE 06.250000 TO WS-LP-ANN-RATE
MOVE 180 TO WS-LP-TERM-MONTHS
COMPUTE WS-LP-MONTHLY-RT =
WS-LP-ANN-RATE / 100 / 12
COMPUTE WS-LP-PAYMENT =
WS-LP-PRINCIPAL *
FUNCTION ANNUITY(WS-LP-MONTHLY-RT, 180)
COMPUTE WS-TOTAL-INTEREST =
(WS-LP-PAYMENT * 180) - WS-LP-PRINCIPAL
MOVE WS-LP-PAYMENT TO WS-DISPLAY-AMT
DISPLAY "15-YR PAYMENT: " WS-DISPLAY-AMT
MOVE WS-TOTAL-INTEREST TO WS-DISPLAY-AMT
DISPLAY "15-YR INTEREST: " WS-DISPLAY-AMT
* 30-year at 6.75%
MOVE 06.750000 TO WS-LP-ANN-RATE
MOVE 360 TO WS-LP-TERM-MONTHS
COMPUTE WS-LP-MONTHLY-RT =
WS-LP-ANN-RATE / 100 / 12
COMPUTE WS-LP-PAYMENT =
WS-LP-PRINCIPAL *
FUNCTION ANNUITY(WS-LP-MONTHLY-RT, 360)
COMPUTE WS-TOTAL-INTEREST =
(WS-LP-PAYMENT * 360) - WS-LP-PRINCIPAL
MOVE WS-LP-PAYMENT TO WS-DISPLAY-AMT
DISPLAY "30-YR PAYMENT: " WS-DISPLAY-AMT
MOVE WS-TOTAL-INTEREST TO WS-DISPLAY-AMT
DISPLAY "30-YR INTEREST: " WS-DISPLAY-AMT
DISPLAY " "
.
4000-AMORTIZATION-SCHEDULE.
DISPLAY "========================================"
DISPLAY " AMORTIZATION (FIRST 12 MONTHS)"
DISPLAY "========================================"
DISPLAY "MTH PAYMENT INTEREST"
" PRINCIPAL BALANCE"
MOVE 100000.00 TO WS-LP-PRINCIPAL
MOVE 06.000000 TO WS-LP-ANN-RATE
MOVE 360 TO WS-LP-TERM-MONTHS
COMPUTE WS-LP-MONTHLY-RT =
WS-LP-ANN-RATE / 100 / 12
COMPUTE WS-LP-PAYMENT =
WS-LP-PRINCIPAL *
FUNCTION ANNUITY(WS-LP-MONTHLY-RT, 360)
MOVE WS-LP-PRINCIPAL TO WS-AM-BALANCE
PERFORM VARYING WS-AM-PERIOD FROM 1 BY 1
UNTIL WS-AM-PERIOD > 12
MOVE WS-LP-PAYMENT TO WS-AM-PAYMENT
COMPUTE WS-AM-INTEREST =
WS-AM-BALANCE * WS-LP-MONTHLY-RT
COMPUTE WS-AM-PRINCIPAL =
WS-AM-PAYMENT - WS-AM-INTEREST
SUBTRACT WS-AM-PRINCIPAL
FROM WS-AM-BALANCE
DISPLAY WS-AM-PERIOD " "
WS-AM-PAYMENT " "
WS-AM-INTEREST " "
WS-AM-PRINCIPAL " "
WS-AM-BALANCE
END-PERFORM
.
This program demonstrates how FUNCTION ANNUITY, FUNCTION EXP, and FUNCTION LOG work together to provide the financial calculations that are GlobalBank's core business. The CD maturity calculation uses EXP and LOG to compute compound interest — a pattern that would require an external math library in many other languages but is built into COBOL.
🔗 Cross-Reference: The amortization schedule pattern shown here is the foundation for the full loan servicing system covered in Chapter 30 (Batch Processing Patterns), where each month's calculations are applied to thousands of loan accounts in a single batch run.
20.13 GlobalBank: Transaction Analysis with Statistical Functions
GlobalBank's daily transaction monitoring system uses intrinsic functions to compute real-time statistics for fraud detection and regulatory reporting. The system processes approximately 2.3 million transactions daily across 450 branches, calculating per-branch and portfolio-level statistics that feed into the bank's risk dashboard. Derek Washington designed the original version in 2018 and estimates it has prevented over $12 million in fraudulent transactions by flagging statistical outliers before human reviewers would have caught them.
The following program loads the day's transactions for a branch into a working-storage table, computes comprehensive statistics, and flags any transactions that fall outside normal bounds. In production, this program is called by a higher-level driver program that iterates through all branches.
IDENTIFICATION DIVISION.
PROGRAM-ID. GBTXSTAT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DAILY-TRANSACTIONS.
05 WS-DT-COUNT PIC 9(5) COMP VALUE ZERO.
05 WS-DT-ENTRY OCCURS 1000 TIMES.
10 WS-DT-ACCT PIC X(10).
10 WS-DT-AMOUNT PIC 9(9)V99.
10 WS-DT-TYPE PIC X(1).
01 WS-STATISTICS.
05 WS-STAT-MEAN PIC 9(9)V99.
05 WS-STAT-MEDIAN PIC 9(9)V99.
05 WS-STAT-STD-DEV PIC 9(9)V99.
05 WS-STAT-MIN PIC 9(9)V99.
05 WS-STAT-MAX PIC 9(9)V99.
05 WS-STAT-RANGE PIC 9(9)V99.
05 WS-STAT-TOTAL PIC 9(12)V99.
PROCEDURE DIVISION.
* After loading transactions...
3000-COMPUTE-STATISTICS.
* For a fully loaded 1000-entry table:
COMPUTE WS-STAT-TOTAL =
FUNCTION SUM(WS-DT-AMOUNT(ALL))
COMPUTE WS-STAT-MEAN =
FUNCTION MEAN(WS-DT-AMOUNT(ALL))
COMPUTE WS-STAT-STD-DEV =
FUNCTION STANDARD-DEVIATION(
WS-DT-AMOUNT(ALL))
COMPUTE WS-STAT-MIN =
FUNCTION MIN(WS-DT-AMOUNT(ALL))
COMPUTE WS-STAT-MAX =
FUNCTION MAX(WS-DT-AMOUNT(ALL))
COMPUTE WS-STAT-RANGE =
WS-STAT-MAX - WS-STAT-MIN
DISPLAY "DAILY TRANSACTION STATISTICS:"
DISPLAY " TOTAL: " WS-STAT-TOTAL
DISPLAY " MEAN: " WS-STAT-MEAN
DISPLAY " STD DEV: " WS-STAT-STD-DEV
DISPLAY " MIN: " WS-STAT-MIN
DISPLAY " MAX: " WS-STAT-MAX
DISPLAY " RANGE: " WS-STAT-RANGE
.
20.10 MedClaim: Date Calculations and Numeric Validation
MedClaim uses intrinsic functions extensively for claim validation: verifying dates are within filing deadlines, converting alphanumeric amounts from EDI input, and calculating aging buckets.
* Validate claim filing deadline
3500-CHECK-FILING-DEADLINE.
COMPUTE WS-SERVICE-INT =
FUNCTION INTEGER-OF-DATE(
WS-SERVICE-DATE)
MOVE FUNCTION CURRENT-DATE(1:8)
TO WS-TODAY-DATE
COMPUTE WS-TODAY-INT =
FUNCTION INTEGER-OF-DATE(WS-TODAY-DATE)
COMPUTE WS-DAYS-SINCE-SERVICE =
WS-TODAY-INT - WS-SERVICE-INT
IF WS-DAYS-SINCE-SERVICE > 365
SET WS-CLAIM-UNTIMELY TO TRUE
MOVE "TIMELY FILING LIMIT EXCEEDED"
TO WS-DENIAL-REASON
END-IF
.
* Convert EDI amount string to numeric
3600-CONVERT-EDI-AMOUNT.
IF WS-EDI-AMOUNT-STR = SPACES
MOVE ZERO TO WS-CLAIM-AMOUNT
ELSE
COMPUTE WS-CLAIM-AMOUNT =
FUNCTION NUMVAL(
FUNCTION TRIM(
WS-EDI-AMOUNT-STR))
END-IF
.
20.15 Practical Pattern: Data Cleansing Pipeline
Data cleansing is one of the most common applications of intrinsic functions in production COBOL programs. Every enterprise system receives data from external sources — trading partners, government agencies, customers, legacy systems — and that data is never perfectly formatted. The cleansing pipeline stands between raw input and business processing, transforming messy real-world data into the clean, validated, consistently formatted records that downstream programs expect.
Let us build a realistic data cleansing pipeline that combines multiple intrinsic functions. This pattern is used at MedClaim to clean incoming provider data before loading it into the master provider table. Tomás Rivera designed this pipeline after analyzing two years of data quality incidents and identifying the most common formatting problems: inconsistent case, embedded currency symbols, invalid dates, and truncated fields.
Provider records arrive from multiple sources with inconsistent formatting: names in mixed case, phone numbers with dashes and parentheses, amounts with dollar signs and commas, and dates that may be invalid. The pipeline cleans each field using the appropriate intrinsic functions.
CLEANSE-PROVIDER-RECORD.
MOVE ZERO TO WS-ERROR-COUNT
* Step 1: Name - trim and uppercase
MOVE FUNCTION UPPER-CASE(
FUNCTION TRIM(WS-RAW-NAME))
TO WS-CLEAN-NAME
IF WS-CLEAN-NAME = SPACES
ADD 1 TO WS-ERROR-COUNT
MOVE "UNKNOWN" TO WS-CLEAN-NAME
END-IF
* Step 2: Amount - validate and convert
IF FUNCTION TRIM(WS-RAW-AMOUNT) = SPACES
MOVE ZERO TO WS-CLEAN-AMOUNT
ELSE
IF FUNCTION TEST-NUMVAL-C(
FUNCTION TRIM(WS-RAW-AMOUNT), "$")
= ZERO
COMPUTE WS-CLEAN-AMOUNT =
FUNCTION NUMVAL-C(
FUNCTION TRIM(WS-RAW-AMOUNT),
"$")
ELSE
ADD 1 TO WS-ERROR-COUNT
MOVE ZERO TO WS-CLEAN-AMOUNT
END-IF
END-IF
* Step 3: Date - validate
MOVE WS-RAW-DATE TO WS-TEMP-DATE
PERFORM VALIDATE-DATE-FIELD
IF WS-DATE-VALID
MOVE WS-TEMP-DATE TO WS-CLEAN-DATE
ELSE
ADD 1 TO WS-ERROR-COUNT
MOVE ZERO TO WS-CLEAN-DATE
END-IF
.
This pipeline demonstrates how intrinsic functions (TRIM, UPPER-CASE, TEST-NUMVAL-C, NUMVAL-C) combine to create a production-quality data cleansing system. Each step handles its own error detection and reports issues through the error counter. Sarah Kim estimated that 3-5% of incoming provider records have at least one data quality issue, and the pipeline automatically cleans 99.7% of them.
Building a Safe Conversion Library
Many programs need the same safe conversion patterns. Encapsulating them in a reusable copybook saves time and prevents errors:
*> CPYCONVT.cpy - Safe Conversion Utilities
* Call these paragraphs after setting the input
* fields. Results are in the output fields.
01 WS-CONV-INPUT.
05 WS-CI-STRING PIC X(50).
05 WS-CI-CURRENCY PIC X(1) VALUE "$".
01 WS-CONV-OUTPUT.
05 WS-CO-NUMBER PIC 9(11)V99.
05 WS-CO-STATUS PIC X(1).
88 WS-CO-OK VALUE "Y".
88 WS-CO-ERROR VALUE "N".
05 WS-CO-MESSAGE PIC X(40).
SAFE-NUMVAL-C.
SET WS-CO-ERROR TO TRUE
MOVE ZERO TO WS-CO-NUMBER
MOVE SPACES TO WS-CO-MESSAGE
IF WS-CI-STRING = SPACES
MOVE "EMPTY INPUT" TO WS-CO-MESSAGE
EXIT PARAGRAPH
END-IF
IF FUNCTION TEST-NUMVAL-C(
FUNCTION TRIM(WS-CI-STRING),
WS-CI-CURRENCY) = ZERO
COMPUTE WS-CO-NUMBER =
FUNCTION NUMVAL-C(
FUNCTION TRIM(WS-CI-STRING),
WS-CI-CURRENCY)
SET WS-CO-OK TO TRUE
ELSE
STRING "INVALID: [" DELIMITED BY SIZE
FUNCTION TRIM(WS-CI-STRING)
DELIMITED BY " "
"]" DELIMITED BY SIZE
INTO WS-CO-MESSAGE
END-STRING
END-IF
.
20.16 COBOL 2002+ Function Additions
The COBOL 2002 and 2014 standards added several useful functions. Support varies by compiler.
COBOL 2002 Additions
| Function | Purpose | Example |
|---|---|---|
| TRIM | Remove leading/trailing spaces | FUNCTION TRIM(ws-str) |
| TEST-NUMVAL | Test if string is valid for NUMVAL | Returns 0 if valid |
| TEST-NUMVAL-C | Test if string is valid for NUMVAL-C | Returns 0 if valid |
| E | Mathematical constant e | FUNCTION E |
| PI | Mathematical constant pi | FUNCTION PI |
| RANDOM | Pseudo-random number | FUNCTION RANDOM(seed) |
COBOL 2014 Additions
| Function | Purpose | Example |
|---|---|---|
| CONCATENATE | String concatenation | FUNCTION CONCATENATE(a, b) |
| FORMATTED-CURRENT-DATE | Formatted date string | FUNCTION FORMATTED-CURRENT-DATE(fmt) |
| TEST-FORMATTED-DATETIME | Validate formatted date | Returns 0 if valid |
| SUBSTITUTE | String replacement | FUNCTION SUBSTITUTE(str, old, new) |
| TRIM | Extended with LEADING/TRAILING | FUNCTION TRIM(str LEADING) |
TEST-NUMVAL and TEST-NUMVAL-C
These are the defensive programming answer to NUMVAL's abend risk:
IF FUNCTION TEST-NUMVAL(WS-INPUT) = ZERO
COMPUTE WS-AMOUNT =
FUNCTION NUMVAL(WS-INPUT)
ELSE
DISPLAY "INVALID NUMERIC: " WS-INPUT
PERFORM 9100-INPUT-ERROR
END-IF
IF FUNCTION TEST-NUMVAL-C(WS-CURR-INPUT, "$")
= ZERO
COMPUTE WS-AMOUNT =
FUNCTION NUMVAL-C(WS-CURR-INPUT, "$")
ELSE
DISPLAY "INVALID CURRENCY: " WS-CURR-INPUT
PERFORM 9100-INPUT-ERROR
END-IF
RANDOM
Generates pseudo-random numbers between 0 and 1:
*> Seed the generator
COMPUTE WS-DUMMY = FUNCTION RANDOM(12345)
*> Generate random numbers
PERFORM 10 TIMES
COMPUTE WS-RAND = FUNCTION RANDOM
DISPLAY "RANDOM: " WS-RAND
END-PERFORM
*> Generate random integer 1-100
COMPUTE WS-RAND-INT =
FUNCTION INTEGER(FUNCTION RANDOM * 100) + 1
🔵 Theme: Legacy != Obsolete. When COBOL's intrinsic functions were introduced in 1989 and expanded in 2002/2014, they addressed a legitimate criticism of the language. Today, COBOL's function library includes capabilities that rival or exceed many modern languages in the business computing domain. Statistical functions (MEAN, MEDIAN, VARIANCE, STANDARD-DEVIATION) are built-in; in Python, you need the
statisticsmodule. Financial functions (PRESENT-VALUE, ANNUITY) are built-in; in Java, you code them by hand or import a library. The point is not that COBOL is superior — it is that COBOL is purpose-built for exactly these calculations, and its intrinsic functions reflect decades of refinement for business use.
20.12 Function Quick Reference
String Functions Summary
| Function | Arguments | Returns | Notes |
|---|---|---|---|
| LENGTH | identifier or literal | Integer | Allocated length |
| REVERSE | alphanumeric | Alphanumeric | Reverses including spaces |
| UPPER-CASE | alphanumeric | Alphanumeric | |
| LOWER-CASE | alphanumeric | Alphanumeric | |
| TRIM | alphanumeric [LEADING|TRAILING] | Alphanumeric | COBOL 2002+ |
| ORD | single character | Integer | Ordinal position |
| CHAR | integer | Character | Inverse of ORD |
| CONCATENATE | two or more alphanumeric | Alphanumeric | COBOL 2014 |
Numeric Functions Summary
| Function | Arguments | Returns | Notes |
|---|---|---|---|
| NUMVAL | alphanumeric | Numeric | Abends on invalid input |
| NUMVAL-C | alphanumeric, currency-sign | Numeric | Handles $, commas |
| INTEGER | numeric | Integer | Floor (toward -infinity) |
| INTEGER-PART | numeric | Integer | Truncate (toward zero) |
| MOD | integer, integer | Integer | Modulo |
| REM | numeric, numeric | Numeric | Remainder |
| ABS | numeric | Numeric | Absolute value |
| SIGN | numeric | Integer (-1,0,1) | Sign function |
Mathematical Functions Summary
| Function | Arguments | Returns |
|---|---|---|
| SQRT | numeric (>= 0) | Numeric |
| LOG | numeric (> 0) | Numeric (natural log) |
| LOG10 | numeric (> 0) | Numeric (base-10 log) |
| EXP | numeric | Numeric (e^x) |
| EXP10 | numeric | Numeric (10^x) |
| FACTORIAL | integer (>= 0) | Integer |
Statistical Functions Summary
| Function | Arguments | Returns |
|---|---|---|
| MEAN | one or more numeric | Numeric |
| MEDIAN | one or more numeric | Numeric |
| VARIANCE | one or more numeric | Numeric |
| STANDARD-DEVIATION | one or more numeric | Numeric |
| RANGE | one or more numeric | Numeric (max-min) |
| MAX | one or more numeric/alpha | Same type |
| MIN | one or more numeric/alpha | Same type |
| SUM | one or more numeric | Numeric |
| ORD-MAX | one or more | Integer (position) |
| ORD-MIN | one or more | Integer (position) |
Date/Financial Functions Summary
| Function | Arguments | Returns |
|---|---|---|
| CURRENT-DATE | none | 21-char string |
| INTEGER-OF-DATE | YYYYMMDD | Integer day number |
| DATE-OF-INTEGER | integer day number | YYYYMMDD |
| INTEGER-OF-DAY | YYYYDDD | Integer day number |
| DAY-OF-INTEGER | integer day number | YYYYDDD |
| WHEN-COMPILED | none | 21-char string |
| PRESENT-VALUE | rate, amounts... | Numeric |
| ANNUITY | rate, periods | Numeric (ratio) |
20.13 The Student Mainframe Lab
🧪 Try It Yourself: Function Explorer
Write a COBOL program that demonstrates every category of intrinsic function: 1. Accept a string from the user; display it in upper case, lower case, reversed, and trimmed 2. Accept a numeric string; convert it using NUMVAL-C and display the result 3. Compute and display SQRT, LOG10, and FACTORIAL for a given number 4. Load 10 values into a table and compute MEAN, MEDIAN, STANDARD-DEVIATION, MIN, MAX, RANGE 5. Display the current date/time and the number of days until the end of the year 6. Calculate the monthly payment on a $100,000 loan at 6% for 30 years using ANNUITY
🧪 Try It Yourself: Statistical Report
Build a program that reads a file of daily sales figures (amount PIC 9(7)V99) and produces a statistical summary report including: - Count, Sum, Mean, Median - Min, Max, Range - Variance and Standard Deviation - Number of values above/below the mean - Position of the highest and lowest values (ORD-MAX, ORD-MIN)
20.14 Chapter Summary
COBOL's intrinsic functions provide a comprehensive library of built-in operations that eliminate hand-coded algorithms and improve program reliability:
- String functions (LENGTH, REVERSE, UPPER-CASE, LOWER-CASE, TRIM, ORD, CHAR) handle the text manipulation needed in every business program
- Numeric functions (NUMVAL, NUMVAL-C, INTEGER, MOD, ABS) bridge the gap between alphanumeric input and numeric processing, with TEST-NUMVAL/TEST-NUMVAL-C providing safe validation
- Mathematical functions (SQRT, LOG, EXP, FACTORIAL) support calculations that would otherwise require hand-coded algorithms or external libraries
- Statistical functions (MEAN, MEDIAN, VARIANCE, STANDARD-DEVIATION, RANGE, MAX, MIN, SUM, ORD-MAX, ORD-MIN) are a standout feature, providing built-in analytics capability rare in procedural languages
- Date functions (CURRENT-DATE, INTEGER-OF-DATE, DATE-OF-INTEGER, WHEN-COMPILED) enable date arithmetic and are covered in depth in Chapter 21
- Financial functions (PRESENT-VALUE, ANNUITY) directly support the loan, investment, and insurance calculations that are COBOL's core business domain
- Functions can be nested and combined for powerful patterns like safe numeric conversion, case-insensitive comparison, and statistical anomaly detection
The existence of these functions directly addresses the "Legacy = Obsolete" misconception. COBOL's intrinsic function library is a purpose-built toolkit for business computing, continuously expanded across three standard revisions and actively used in systems that process trillions of dollars daily.
"Every time someone tells me COBOL is primitive, I ask them to compute a loan amortization schedule in one line of their favorite language. FUNCTION ANNUITY has been doing it since 1989." — Priya Kapoor, Architect, GlobalBank