Blog › ICP guides

REXX developer on retainer: NUMERIC DIGITS precision, decimal arithmetic, z/OS REXX, and REXX on monthly retainer

October 3, 2026 · ~13 min read

A REXX developer was maintaining a job submission control script on z/OS TSO/E. The script evaluated a computed ratio — total processing cost divided by unit count for a given batch run — and compared the result to a predetermined threshold to decide whether to submit the next downstream job or hold it for manual review. The threshold had been computed by a separate analysis exec with high precision. The submission control script performed the division directly inline, without any explicit NUMERIC DIGITS statement. In REXX, when no NUMERIC DIGITS statement is present, the precision defaults to 9 significant digits.

The computed ratio for most batch runs had 9 or fewer significant digits, so the comparison worked correctly. But four specific batch runs produced ratios with 12 significant digits — values where the meaningful precision extended into the 10th through 12th digit positions. REXX silently rounded each of these four results to 9 significant digits before storing the value in the variable. The rounded value differed from the full-precision result only beyond the 9th significant digit, which is precisely the range where the threshold comparison was sensitive. For each of the four affected batch runs, the rounded 9-digit ratio compared as less than the threshold, triggering a job submission. The full-precision 12-digit ratio would have compared as greater than the threshold, holding the batch for review. The threshold and the computed ratio agreed in the first 9 significant digits and differed only in digits 10 through 12.

Four jobs were submitted that should have been held. Each triggered downstream processing on data that was not yet verified. The symptom was not an error or an exception: the script executed to completion, the comparison returned a result, the IF branch was taken, and the SUBMIT command was issued. There was no REXX diagnostic because REXX did not consider the rounding a problem — it is how REXX arithmetic is defined to work. Fix: added NUMERIC DIGITS 15 as the first executable statement in the script, before any calculations. With 15-digit precision, the computed ratio was stored with all 12 significant digits, the comparison with the threshold was correct, and no incorrect submission was triggered. Wrong submissions: 4 → 0.

The reason this class of bug is systematically invisible in REXX is that REXX’s string-arithmetic model makes precision a global configuration parameter, not a property of individual numbers or operations. A REXX variable that holds a numeric value is a string of decimal digits. There is no float type, no double type, no integer type. When REXX evaluates an arithmetic expression, it converts the string operands to decimal numbers, performs the operation, and converts the result back to a string of at most NUMERIC DIGITS significant digits. The rounding is built into every arithmetic operation and is silent by design. A developer who does not know to add NUMERIC DIGITS explicitly will not encounter any diagnostic that alerts them to the precision setting — the default is simply in effect, and any calculation that requires more than 9 digits is silently degraded.

REXX: Restructured Extended Executor, Mike Cowlishaw, and the IBM mainframe scripting language

REXX (Restructured Extended Executor) was designed by Mike Cowlishaw at IBM Hursley Laboratory in the United Kingdom, with an initial implementation in 1979 and the first public release in 1982 as part of IBM’s VM/CMS operating system. Cowlishaw designed REXX as a scripting language that was easy to learn and use without prior programming experience, with a natural-language-inspired syntax (no semicolons required, no mandatory variable declarations, human-readable instruction names) and a single universal data type: the character string. Every REXX variable holds a string. Numbers are strings that look like numbers. Booleans are the strings 0 and 1. Lists are strings that can be parsed with the PARSE instruction. This universality makes REXX programs easy to write and read but creates a category of invisible precision errors in numeric calculations.

REXX became the macro and automation language for IBM’s major operating systems: VM/CMS (the interactive virtual machine system), OS/2 (where REXX was the primary scripting language from 1987), and z/OS TSO/E (where TSO/E REXX execs run in the terminal session and can invoke ISPF dialog services, submit JCL jobs, read and write data sets, and call system services). On z/OS, REXX execs are stored in partitioned data sets (PDS libraries) and invoked from the TSO command line, from ISPF panels, or from JCL EXEC statements. The EXECIO host command is the primary mechanism for reading from and writing to z/OS data sets: EXECIO * DISKR INFILE (STEM RECORD. FINIS) reads all records from the DD named INFILE into the compound variable stem RECORD., with the count stored in RECORD.0 and each record in RECORD.1 through RECORD.n.

REXX’s arithmetic model is based on IBM’s General Decimal Arithmetic specification, which defines decimal arithmetic to an arbitrary number of significant digits configured by the NUMERIC DIGITS setting. The default of 9 was chosen for VM/CMS because 9 decimal digits covers the range of most practical integer and floating-point values that appeared in scripting contexts in the 1970s and 1980s. Modern financial and scientific calculations often require 12 to 18 significant digits for correct comparison. The REXX standard (American National Standard for the REXX Programming Language, ANSI/INCITS 274-1996) specifies that the default NUMERIC DIGITS is at least 9 and that implementations may have a higher default. TSO/E REXX on z/OS defaults to 9. NUMERIC DIGITS can be set to any positive integer; the upper limit is implementation-dependent but is at least 1000 in modern implementations. Setting it higher increases the precision of all arithmetic in the current exec, at the cost of slightly slower arithmetic for very large computations.

The second REXX arithmetic configuration instruction is NUMERIC FORM. NUMERIC FORM SCIENTIFIC (the default) formats large numbers in scientific notation when they exceed the current NUMERIC DIGITS setting: 1.23456789E+10. NUMERIC FORM ENGINEERING formats large numbers with exponents that are multiples of 3: 12.3456789E+9. The choice of form affects how large numbers are displayed but not how they are computed; the precision of the computation is controlled entirely by NUMERIC DIGITS. REXX also has a NUMERIC FUZZ setting: NUMERIC FUZZ n specifies that two numbers are considered equal in comparison if they agree in the first NUMERIC DIGITS - NUMERIC FUZZ digits. The default NUMERIC FUZZ is 0 (exact comparison up to the NUMERIC DIGITS precision). Setting NUMERIC FUZZ 3 with NUMERIC DIGITS 9 means numbers are compared equal if they agree in the first 6 digits. This can suppress comparison bugs caused by rounding in the last few digits, but it also suppresses legitimate close comparisons and is not a substitute for setting NUMERIC DIGITS high enough to capture all required precision.

PARSE, compound variables, EXECIO, and z/OS-specific REXX facilities

The PARSE instruction is REXX’s primary string decomposition mechanism. PARSE VAR string template applies a parsing template to the value of a variable, assigning substrings to the named variables in the template. A template can use literal delimiters, positional specifiers, and pattern matching. PARSE VAR record job_name 9 job_class 10 priority 12 . reads the variable record and assigns: job_name to characters 1–8 (up to position 9), job_class to character 9 (from position 9 to 10), priority to characters 10–11 (from position 10 to 12), and discards the rest (the . placeholder). This positional template parsing is the REXX equivalent of reading a fixed-format record layout — the same skill used in COBOL PICTURE clauses and PL/I record-format declarations.

Compound variables (stem variables) are REXX’s array mechanism. A compound variable name consists of a stem (ending with a dot) and a tail: RECORD.0 is the element with tail 0, RECORD.1 is the element with tail 1, and RECORD.N where N is a variable is the element whose tail is the current value of N. The convention for EXECIO output is that RECORD.0 holds the count of records read, and RECORD.1 through RECORD.N hold the individual records. A loop over the stem is written: DO I = 1 TO RECORD.0; PARSE VAR RECORD.I ...; END. The most common compound variable retainer bug: a developer initializes a stem using DO I = 0 TO N-1 (0-based indexing), and downstream code reads from STEM.1 through STEM.N (1-based indexing per EXECIO convention), producing an off-by-one where the last element is read from an uninitialized variable (which REXX returns as the uppercase variable name as a string, e.g., STEM.11 becomes the literal string “STEM.11” rather than raising an error).

EXECIO is the z/OS TSO/E host command for data set I/O. The full form is EXECIO lines DISKR|DISKW ddname (options). DISKR reads from the data set allocated to the named DD; DISKW writes to it. The STEM stem. option stores the read records into the named compound variable stem. The FINIS option closes the data set after the operation. A retainer bug in this area: the data set has a fixed-length record format (RECFM=FB) with LRECL=80, but the exec was written expecting LRECL=100. REXX’s EXECIO reads the records at their actual LRECL; if the exec’s PARSE VAR templates reference column positions up to 100, columns 81–100 are read from the next physical record, not from the same record. The symptom is fields read at wrong offsets for every record. Work log entry: “EXECIO * DISKR PAYFILE: expected LRECL=100; actual LRECL=80; PARSE VAR RECORD.I employee_id 11 dept 21 salary 31 . read salary field from columns 21–30 which corresponds to dept extension in actual records; 15 wrong salary reads; fix: updated PARSE template offsets to match actual LRECL=80 layout; wrong reads: 15 → 0; 2.5h.”

OUTTRAP is a TSO/E REXX built-in function that redirects the output of the next host command into a compound variable stem instead of displaying it to the terminal. TRAPOUT = OUTTRAP('CMDOUT.') starts trapping; the next host command’s output (both standard and error) goes into CMDOUT.0 through CMDOUT.N; CALL OUTTRAP 'OFF' stops trapping. SYSVAR is a TSO/E built-in function that returns system variable values: SYSVAR('SYSUID') returns the current user ID, SYSVAR('SYSPREF') returns the high-level qualifier prefix, SYSVAR('SYSENV') returns the current environment. ISPEXEC is the ISPF dialog service host command: ADDRESS ISPEXEC 'DISPLAY PANEL(MYPANEL)' displays an ISPF panel and returns the user’s input as panel variables. Return code 0 means OK; return code 8 means the user pressed END/CANCEL. A retainer bug: the exec calls ADDRESS ISPEXEC and does not check the return code, continuing to process panel variables that were never populated because the user pressed END.

Typical REXX retainer work and what it looks like in a work log

NUMERIC DIGITS precision bugs are the largest category of REXX retainer work that produces no visible artifact. The pattern is always the same: a calculation requires more precision than the default 9 digits; no NUMERIC DIGITS statement is present; the result is silently rounded; the rounded value participates in a comparison or threshold check; the comparison produces the wrong outcome for a small number of inputs where the full-precision and rounded values fall on opposite sides of the threshold. In a job submission control script, the wrong outcome is a job that should have been held but was submitted, or a job that should have been submitted but was held. Neither produces an error message. Neither appears in the REXX exec trace unless the developer adds explicit SAY statements to inspect the intermediate values. Work log entry: “JOBSUBMIT: no NUMERIC DIGITS statement; computed_ratio = total_cost / unit_count produced 12-significant-digit result; REXX rounded to 9 digits before storing; rounded value compared as less than threshold_value (computed with 12-digit precision); IF computed_ratio > threshold_value THEN HOLD = 1 branch not taken for 4 batch runs; 4 jobs submitted when they should have been held; fix: added NUMERIC DIGITS 15 before first calculation; wrong submissions: 4 → 0; 3h.”

EXECIO record format mismatch is the second category. REXX execs that read fixed-format data sets using positional PARSE templates are sensitive to the actual LRECL of the data set. If the data set’s LRECL changes (a new field is added to the record layout, or the data set is reallocated with a different LRECL), all PARSE templates that reference absolute column positions read fields from wrong positions for every record. Unlike BCPL’s hardcoded word-count bug (which fails for a fraction of inputs), an LRECL mismatch affects every record, making it immediately obvious in testing if the affected field is used in the same run. But if the affected field is only used in monthly reports or quarterly reconciliations, the mismatch can remain undetected until the next reporting cycle. Work log entry: “PAYROLL_PROC: PARSE VAR RECORD.I employee_id 11 dept 21 salary 31 ytd_earnings 45 .; data set LRECL changed from 80 to 100 when YTD_GROSS field was added at position 81; existing PARSE template was valid for old layout; SALARY still at column 21; no problem there; but NEW_BENEFIT field at column 46 in old layout now reads from column 46 of new layout which is PENSION_CODE in the new 100-character record; 3 wrong benefit records per run; fix: updated PARSE template to include new field positions; wrong records: 3 per run → 0; 2h.”

INTERPRET injection risk is the third category. The INTERPRET instruction evaluates its argument as a REXX statement: INTERPRET 'SAY "Hello"' executes SAY "Hello" at runtime. This is powerful for dynamic code generation — building a variable name from a prefix and a suffix, for example: INTERPRET FIELDNAME '= PARSE_VALUE'. But if user-supplied input participates in the interpreted string, a malicious or erroneous user can inject arbitrary REXX code. On z/OS, where REXX execs can run TSO host commands, submit JCL, and allocate data sets, an INTERPRET injection can have serious consequences: a user-supplied job name that contains ; SUBMIT DSN(USER.MALICIOUS.JCL) appended to the INTERPRET string can cause an unexpected job submission. Work log entry: “PANEL_PROC: user-supplied job name from ISPF panel variable JOB_NAME used in INTERPRET 'SUBMIT_JOB('||JOB_NAME||')'; job name containing REXX code fragments could inject statements before closing parenthesis; risk identified during security review; no exploit confirmed; fix: replaced INTERPRET with explicit CALL SUBMIT_JOB JOB_NAME with input validation of JOB_NAME before call; injection risk: eliminated; 2h.”

Compound variable off-by-one is the fourth category. The REXX convention for EXECIO stem arrays is 1-based: STEM.0 is the count, STEM.1 is the first record, STEM.N is the last. Developers coming from C, Python, or Java may write 0-based loops: DO I = 0 TO STEM.0 - 1, reading STEM.I as STEM.0 through STEM.N-1. This reads STEM.0 (the count, a number) as if it were the first data record, and misses STEM.N (the last actual record). The symptom depends on what STEM.0 (the count string) looks like when PARSE-d as a data record: if the count value happens to resemble a valid data record header, the error may go undetected until the record count changes and the count-as-record produces a different spurious value. Work log entry: “READ_INFILE: DO I = 0 TO RECORD.0 - 1; reads RECORD.0 (count=15) as first data record; PARSE VAR RECORD.0 ... reads ‘15’ as employee_id; produces nonsense employee 15 processed as first employee; last employee RECORD.15 never read; 1 spurious record + 1 missed record per run; fix: changed to DO I = 1 TO RECORD.0; wrong: 2 per run → 0; 1.5h.”

Track REXX developer retainer hours without the status emails

When a 3-hour session diagnoses a NUMERIC DIGITS precision bug in a z/OS job submission control script — adding SAY tracing to identify the four batch runs where the rounded and unrounded ratios fall on opposite sides of the threshold, confirming that NUMERIC DIGITS 15 corrects all four comparisons, and verifying no regression in the other 248 batch runs — the work log must name the exec, the missing NUMERIC DIGITS statement, the calculation, the rounded value, and the wrong submission count before and after. HourTab gives your REXX retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log that names the exec and the precision setting. No client login. No status emails. CSV in, URL out.

See HourTab pricing →

How HourTab tracks REXX developer retainer hours

REXX retainer work is invisible by the same mechanism that makes NUMERIC DIGITS precision bugs dangerous: the REXX interpreter sees no inconsistency between a calculation that requires 12 significant digits and a NUMERIC DIGITS setting of 9. There is no type system to check; there is no numeric type at all, only strings. The rounding is performed silently according to the specification. The comparison that follows uses the rounded value correctly according to REXX’s defined semantics. The resulting wrong comparison outcome is a logical error, not a REXX error. The exec produces an incorrect result without raising any exception or writing any diagnostic message to the operator console, the z/OS job log, or the REXX trace output.

The work log needs to name the mechanism: which exec, what the NUMERIC DIGITS setting was (or that it was absent), which calculation produced excess precision, how the rounding changed the comparison result, what the observable consequence was (wrong submissions, wrong holds, wrong report values), and what the fix was. A log entry that says “fixed precision bug in job submission script, 3h” is not auditable. A log entry that says “JOBSUBMIT: no NUMERIC DIGITS statement (default 9); computed_ratio = total_cost / unit_count produced 12-digit result; REXX rounded to 9 digits; rounded value compared less than threshold_value (12-digit precision) for 4 batch runs; IF computed_ratio > threshold_value THEN HOLD = 1 not taken; 4 jobs submitted when they should have been held; fix: NUMERIC DIGITS 15 added as first statement; wrong submissions: 4 → 0; 3h” is auditable.

HourTab gives REXX developers a public retainer-hours URL they send to clients — banks and insurance companies maintaining z/OS TSO/E automation scripts, manufacturers running REXX-based production control execs, and government agencies with decades-old mainframe REXX codebases that automate job scheduling, report distribution, and data set management. For REXX retainers, each work log entry should name the mechanism: which exec, which instruction was missing or wrong, which calculation was affected, and what the downstream consequence was. Comparative context: REXX retainer work has structural overlap with other mainframe and numeric environments — COBOL retainers involve the same class of fixed-precision arithmetic bugs (COBOL’s PICTURE clause defines the precision of each variable independently rather than globally, and a COMPUTE statement that silently truncates a result beyond the declared precision produces the same category of wrong comparison that NUMERIC DIGITS 9 produces in REXX); PL/I retainers cover IBM mainframe programs with silent FIXED DECIMAL truncation in division operations, where a FIXED DECIMAL(15,2) result divided by a count variable silently truncates the repeating decimal, the same precision-loss pattern as REXX’s NUMERIC DIGITS default — in both languages, the truncation is silent, the comparison is wrong, and the only diagnostic is adding explicit precision control before the calculation; and assembly language retainers on z/OS sometimes involve the REXX host environment because the assembler program that calls a REXX exec via the IRXEXEC interface must correctly set up the evaluation block for string results, and precision of numeric results returned from REXX to assembler callers is bounded by the NUMERIC DIGITS setting in effect within the exec.

FAQ: REXX developer retainers

What does a REXX developer on retainer typically do?

A REXX developer on monthly retainer covers NUMERIC DIGITS precision bug diagnosis (identifying where the default 9-digit precision silently rounds a calculated result before comparison to a higher-precision threshold; the fix is adding NUMERIC DIGITS 15 or higher before the first calculation); EXECIO record format diagnosis (identifying where a fixed-format data set’s LRECL does not match the exec’s PARSE VAR template offsets, causing field values to be read from wrong column positions); INTERPRET injection risk analysis (identifying where user-supplied ISPF panel input participates in an INTERPRET statement, allowing arbitrary REXX code execution; the fix is input validation or replacement with explicit CALL); compound variable stem off-by-one bugs (identifying where a stem loop uses 0-based indexing for a 1-based EXECIO stem); and TSO/E ISPEXEC return code handling (identifying where an ISPF service call return code is not checked, causing the exec to process stale or uninitialized panel variables).

What REXX work is most commonly underlogged?

NUMERIC DIGITS precision bugs are the most systematically underlogged REXX retainer work. REXX performs all arithmetic as decimal string arithmetic with precision set by NUMERIC DIGITS, which defaults to 9 significant digits. When a calculation produces more than 9 significant digits, REXX silently rounds to 9 before storing the result. There is no error, no warning, and no exception. The rounded value then participates in comparisons. If the threshold was computed with more than 9 digits of precision, the comparison may produce the wrong result for inputs where the full-precision and rounded values fall on opposite sides of the threshold. In a job submission control script, this means wrong job submissions for a small fraction of batch runs — those where the ratio and the threshold agree in the first 9 digits but differ in digits 10 through 12. The wrong submissions produce no REXX error and no job log diagnostic. They are discovered only when downstream processing finds data submitted before it was verified, typically days or weeks later. Diagnosis requires adding SAY tracing to compare the rounded and unrounded values, confirming the precision-loss mechanism, and verifying the fix. Two to four hours invisible per occurrence.

What are typical REXX developer retainer rates?

Entry-level REXX developers with experience in z/OS TSO/E REXX, basic PARSE instruction usage, and EXECIO data set access typically bill at $60 to $105 per hour. Mid-level REXX programmers with experience covering NUMERIC DIGITS precision control, compound variable stem management, ISPEXEC service calls, and OUTTRAP command output capture typically bill at $90 to $160 per hour. Senior REXX developers with deep knowledge of z/OS REXX internals, TSO/E host commands, NetRexx (Java bytecode REXX, Mike Cowlishaw), Open Object Rexx (ooRexx) object extensions, and REXX to modern language migration typically bill at $130 to $240 per hour. Monthly retainer ranges: $1,200 to $2,600 per month for advisory engagements covering NUMERIC DIGITS audits and EXECIO record format reviews (8 to 18 hours per month); $2,000 to $5,000 per month for active z/OS REXX maintenance including ISPEXEC service interface work, compound variable audits, and REXX to Python or Java migration.

What should a REXX developer retainer agreement include?

A REXX developer retainer agreement should specify: REXX environment scope (z/OS TSO/E REXX, Regina REXX on Unix/Windows, ooRexx, or NetRexx; implementations differ significantly in available host commands, system functions, and precision behavior); NUMERIC DIGITS audit scope (whether the retainer covers a systematic review of all REXX execs for NUMERIC DIGITS settings, identifying calculations that require more than the default 9 digits); EXECIO scope (whether EXECIO data set access, DD name management, and LRECL compatibility checking are in scope; EXECIO bugs require z/OS test access to diagnose); INTERPRET scope (whether INTERPRET usage is in scope for security review, identifying injection risks from user-supplied ISPF panel input); ISPEXEC and ISREDIT scope (whether ISPF dialog service calls and ISPF edit macro REXX are in scope; ISPF REXX requires knowledge of the ISPF dialog model); and REXX migration scope (whether the retainer covers rewriting REXX execs in Python, Java, or shell script for organizations modernizing their z/OS automation layer).

How should REXX developer retainer hours be logged?

Log each REXX retainer session with: the exec name where the bug was diagnosed (e.g., JOBSUBMIT); the NUMERIC DIGITS setting in effect (e.g., default 9 — no explicit NUMERIC DIGITS statement present); the calculation that produced excess precision (e.g., computed_ratio = total_cost / unit_count producing a 12-significant-digit result); the comparison that failed (e.g., IF computed_ratio > threshold_value where threshold_value was computed with 12-digit precision; the 9-digit rounded computed_ratio compared as less than the threshold for 4 inputs); the symptom with count (e.g., 4 job submissions triggered when they should have been held); and the fix (e.g., NUMERIC DIGITS 15 added before first calculation; wrong submissions: 4 → 0; 3h). For EXECIO LRECL bugs: the DD name, the LRECL mismatch, the PARSE VAR template that read wrong field values, and the fix. For INTERPRET injection: the user-supplied value path and the replacement logic. For compound variable stem off-by-one: the stem name, the loop bounds used, and the corrected bounds.