21 min read

> "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." —...

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

  1. FUNCTION keyword is required. You cannot write just UPPER-CASE(WS-INPUT) — it must be FUNCTION UPPER-CASE(WS-INPUT).
  2. Functions can appear anywhere an identifier or literal of the appropriate type can appear. This includes MOVE, IF, EVALUATE, COMPUTE, STRING, DISPLAY, and more.
  3. Functions return values but do not modify their arguments. They are pure functions with no side effects.
  4. Functions can be nested: FUNCTION UPPER-CASE(FUNCTION TRIM(WS-INPUT TRAILING))
  5. 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=cobol2014 or similar. The REPOSITORY paragraph 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 THRU syntax 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 statistics module. 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