Variables and Types in PL/I
Explore PL/I variable declarations, the FIXED BINARY/FIXED DECIMAL/FLOAT/CHARACTER/BIT type system, and type conversions using the Iron Spring compiler.
PL/I was designed in 1964 to serve scientific, commercial, and systems programmers all at once, and its type system reflects that ambition. Where FORTRAN gave you INTEGER and REAL and COBOL gave you PIC clauses, PL/I offers a single orthogonal type language that combines base type (BINARY, DECIMAL, CHARACTER, BIT), scale (FIXED or FLOAT), precision, and storage class. Picking the right combination lets you express anything from a 1-bit flag to a 31-digit packed decimal balance with exact cents.
As a statically and strongly typed imperative language, PL/I requires (or strongly prefers) that you declare every variable with an explicit type using the DECLARE statement — abbreviated DCL. The compiler uses those declarations to reserve storage, to pick machine instructions for arithmetic, and to insert automatic conversions when types mix. Conversions that lose information (such as floating-point into a fixed-decimal slot) happen silently unless you raise the CONVERSION or SIZE conditions.
This tutorial walks through the core scalar types, the INITIAL attribute for giving variables starting values, and how PL/I converts between types when you mix them in expressions. You will see how the type system handles signed integers, exact decimal arithmetic for money, floating-point for science, text of fixed and varying length, and bit strings that double as booleans.
The DECLARE Statement
Every PL/I variable is introduced with DECLARE (or its common abbreviation DCL). A single DECLARE can introduce one or many variables, and attributes can follow in any order:
DECLARE COUNT FIXED BINARY(15);
DECLARE (X, Y, Z) FLOAT DECIMAL(6);
DCL NAME CHARACTER(30) VARYING;
DCL FLAG BIT(1);
Attributes fall into groups: the base type (BINARY, DECIMAL, CHARACTER, BIT, PICTURE, POINTER), the scale (FIXED or FLOAT for numerics), the precision (in parentheses), a length or mode, and storage-class attributes such as AUTOMATIC, STATIC, or CONTROLLED. For a first tutorial we stay with the default AUTOMATIC storage, which gives stack-allocated locals that disappear when the procedure returns.
The Numeric Types at a Glance
| Declaration | Meaning | Typical use |
|---|---|---|
FIXED BINARY(15) | 16-bit signed integer | Loop counters, small counts |
FIXED BINARY(31) | 32-bit signed integer | General-purpose integers |
FIXED DECIMAL(p,q) | p total digits, q after the decimal point, base-10 | Money, accounting, regulated math |
FLOAT DECIMAL(p) | Floating point with p decimal digits of precision | Engineering, scientific calculation |
FLOAT BINARY(p) | Floating point with p binary digits of precision | Hardware-native floating point |
Because FIXED DECIMAL is a true base-10 type, values like 0.10 are stored exactly — unlike FLOAT, which would approximate them in binary. This is why PL/I has outlived many contemporaries in banking and insurance: financial math is exact by default.
Example 1: Declaring and Initializing Variables
The INITIAL attribute (abbreviated INIT) gives a variable a starting value at allocation time. The following program declares one variable of each core scalar type and prints its value using edit-directed output for predictable formatting.
Create a file named variables.pli:
VARIABLES: PROCEDURE OPTIONS(MAIN);
/* Numeric types */
DECLARE AGE FIXED BINARY(31) INITIAL(42);
DECLARE COUNT FIXED BINARY(15) INITIAL(1000);
DECLARE SALARY FIXED DECIMAL(10,2) INITIAL(75000.50);
DECLARE PI FLOAT DECIMAL(6) INITIAL(3.14159);
/* Character types */
DECLARE FULL_NAME CHARACTER(30) VARYING INITIAL('Ada Lovelace');
DECLARE GRADE CHARACTER(1) INITIAL('A');
/* Bit string (PL/I's boolean) */
DECLARE IS_ACTIVE BIT(1) INITIAL('1'B);
PUT EDIT('AGE = ', AGE) (A, F(6)); PUT SKIP;
PUT EDIT('COUNT = ', COUNT) (A, F(6)); PUT SKIP;
PUT EDIT('SALARY = ', SALARY) (A, F(10,2)); PUT SKIP;
PUT EDIT('PI = ', PI) (A, F(10,5)); PUT SKIP;
PUT EDIT('FULL_NAME = ', FULL_NAME) (A, A); PUT SKIP;
PUT EDIT('GRADE = ', GRADE) (A, A); PUT SKIP;
PUT EDIT('IS_ACTIVE = ', IS_ACTIVE) (A, B(1)); PUT SKIP;
END VARIABLES;
A few things to notice:
FIXED BINARY(31)reserves a 32-bit signed integer. The number in parentheses is precision in bits, not bytes.FIXED DECIMAL(10,2)stores up to 10 decimal digits with 2 after the point — exactly the shape you want for currency in the range of ±99,999,999.99.CHARACTER(30) VARYINGreserves 30 characters of space but tracks the current length separately, soFULL_NAMEreports its length as 12 even though 30 bytes are allocated.BIT(1)holds a single bit. The literal'1'B(bit-string constant) is the PL/I way to write true;'0'Bis false.- The
PUT EDITformat list(A, F(10,2))pairs each item with a format:Awrites a string (using its full length forVARYING),F(w,d)writes a right-justified fixed-point number of widthwwithddecimal digits, andB(1)writes a bit string as raw0/1characters.
Type Conversions
PL/I performs implicit conversions when you mix types in an expression or on the two sides of an assignment. The rules are permissive: assigning a FIXED BINARY into a FLOAT DECIMAL slot, for example, just works. What makes PL/I different from C is that the language also converts between numeric and character types when you ask it to, through built-in functions such as CHAR and assignment rules for PICTURE data.
The other everyday rule is string concatenation with the || operator, which works on any character or bit values and produces a result whose length is the sum of the lengths.
Create a file named conversions.pli:
CONVERT: PROCEDURE OPTIONS(MAIN);
DECLARE I FIXED BINARY(31) INITIAL(100);
DECLARE F FLOAT DECIMAL(6);
DECLARE D FIXED DECIMAL(10,2);
DECLARE GREETING CHARACTER(11) INITIAL('Hello, PL/I');
DECLARE SUFFIX CHARACTER(10) VARYING INITIAL('!');
DECLARE COMBINED CHARACTER(30) VARYING;
/* FIXED BINARY to FLOAT DECIMAL - widening conversion */
F = I;
/* FLOAT DECIMAL arithmetic then assignment to FIXED DECIMAL */
D = F * 1.5;
/* String concatenation builds a new VARYING string */
COMBINED = GREETING || SUFFIX;
PUT EDIT('I (FIXED BINARY) = ', I) (A, F(6)); PUT SKIP;
PUT EDIT('F (FLOAT DECIMAL) = ', F) (A, F(10,2)); PUT SKIP;
PUT EDIT('D (FIXED DECIMAL) = ', D) (A, F(10,2)); PUT SKIP;
PUT EDIT('COMBINED = ', COMBINED) (A, A); PUT SKIP;
END CONVERT;
What happens here:
F = I;widens a 32-bit integer into a floating-point value. PL/I handles the conversion at the machine level for you.D = F * 1.5;evaluatesF * 1.5as floating point (because at least one operand isFLOAT), then converts the result toFIXED DECIMAL(10,2)on assignment. If the value wouldn’t fit, theSIZEcondition would be raised.GREETING || SUFFIXconcatenates a fixed-length string with a varying one. The result is aVARYINGstring whose length is the full length of the fixed string plus the current length of the varying one — 11 + 1 in this case.
The INITIAL Attribute
INITIAL (or INIT) sets a starting value at allocation time:
DCL BALANCE FIXED DECIMAL(10,2) INITIAL(0);
DCL LIMIT FIXED BINARY(31) INIT(100);
DCL TITLE CHAR(20) VARYING INIT('Untitled');
You can initialize arrays with a parenthesized list, including the repetition factor for repeated values:
DCL SCORES(5) FIXED BIN(15) INIT(85, 92, 78, 96, 88);
DCL ZEROS(100) FIXED BIN(31) INIT((100) 0); /* 100 copies of 0 */
Without INITIAL, the contents of an AUTOMATIC variable are undefined on entry — don’t rely on a zero default.
PL/I’s Default Type Rules (and Why to Avoid Them)
PL/I will let you use a variable that was never declared. It infers a type from the first letter of the name:
- Names starting with I through N default to
FIXED BINARY(15,0). - All other names default to
FLOAT DECIMAL(6).
This was a nod to FORTRAN’s implicit typing, and it is a notorious source of bugs. A typo can silently create a new variable of the wrong type. Modern PL/I style uses an explicit declaration for every name, often with the DEFAULT or %INCLUDE statement at the top of a program to reject implicit typing entirely:
DEFAULT RANGE(*) NONE; /* Force explicit declarations */
Treat the I-N rule as historical trivia, not as a feature to rely on.
CONSTANT Values
PL/I represents named constants through the preprocessor. A %DECLARE with %ASSIGN creates a compile-time name that the preprocessor substitutes into the source before compilation:
%DECLARE MAX_ITEMS FIXED;
%MAX_ITEMS = 100;
DCL INVENTORY(MAX_ITEMS) FIXED BIN(31);
For run-time constants, use DECLARE with INITIAL and discipline — PL/I itself does not enforce immutability on ordinary automatic variables, though some modern compilers accept a VALUE attribute for read-only names.
Running with Docker
Use the same codearchaeology/pli:latest image from the Hello World tutorial. The plicc wrapper inside the image handles the compile-and-link step and produces an executable named after your source file:
| |
The --security-opt seccomp=unconfined flag is required when running the 32-bit Iron Spring compiler on Docker Desktop for macOS and Windows, the same workaround explained in the Hello World page.
Expected Output
Running variables.pli produces:
AGE = 42
COUNT = 1000
SALARY = 75000.50
PI = 3.14159
FULL_NAME = Ada Lovelace
GRADE = A
IS_ACTIVE = 1
Running conversions.pli produces:
I (FIXED BINARY) = 100
F (FLOAT DECIMAL) = 100.00
D (FIXED DECIMAL) = 150.00
COMBINED = Hello, PL/I!
The F format items right-justify numbers within their width, so integers appear with leading spaces. A format writes the full current length of a VARYING string, and B(1) writes a single bit as the character 0 or 1.
Key Concepts
- Declarations are orthogonal: base type (
BINARY,DECIMAL,CHARACTER,BIT), scale (FIXED,FLOAT), and precision combine freely to describe any value. FIXED DECIMALgives exact base-10 arithmetic — the reason PL/I remains dominant in financial systems. Use it for money, neverFLOAT.VARYINGcharacter strings track their current length separately from their maximum, so they behave like modernStringtypes rather than fixed C-style buffers.BIT(1)is PL/I’s boolean, with literals'1'Band'0'B. Bit strings can be longer than one bit and support the logical operators&,|, and¬.- Implicit conversions are powerful and permissive — PL/I will convert across type families (numeric to character, binary to decimal) wherever it can, so mixing types rarely causes a compile error but may raise run-time conditions such as
SIZE,CONVERSION, orSTRINGSIZE. INITIALis the only portable way to set a starting value on an automatic variable; otherwise its contents are undefined.- The I-N default type rule is historical baggage — always declare every variable explicitly, and consider
DEFAULT RANGE(*) NONE;to have the compiler enforce that for you. DECLAREandDCLare synonymous, as areBINARY/BIN,DECIMAL/DEC, andINITIAL/INIT. Pick a house style and use it consistently.
With the type system under your belt, the next tutorial will cover operators — how PL/I combines these typed values in arithmetic, comparison, logical, and string expressions, and how operator precedence interacts with the silent conversions you just saw.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/pli:latest
Comments
Loading comments...
Leave a Comment