Beginner

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

DeclarationMeaningTypical use
FIXED BINARY(15)16-bit signed integerLoop counters, small counts
FIXED BINARY(31)32-bit signed integerGeneral-purpose integers
FIXED DECIMAL(p,q)p total digits, q after the decimal point, base-10Money, accounting, regulated math
FLOAT DECIMAL(p)Floating point with p decimal digits of precisionEngineering, scientific calculation
FLOAT BINARY(p)Floating point with p binary digits of precisionHardware-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) VARYING reserves 30 characters of space but tracks the current length separately, so FULL_NAME reports 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'B is false.
  • The PUT EDIT format list (A, F(10,2)) pairs each item with a format: A writes a string (using its full length for VARYING), F(w,d) writes a right-justified fixed-point number of width w with d decimal digits, and B(1) writes a bit string as raw 0/1 characters.

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; evaluates F * 1.5 as floating point (because at least one operand is FLOAT), then converts the result to FIXED DECIMAL(10,2) on assignment. If the value wouldn’t fit, the SIZE condition would be raised.
  • GREETING || SUFFIX concatenates a fixed-length string with a varying one. The result is a VARYING string 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:

1
2
3
4
5
6
7
8
# Pull the image (first run only)
docker pull codearchaeology/pli:latest

# Compile and run variables.pli
docker run --rm --security-opt seccomp=unconfined -v $(pwd):/app -w /app codearchaeology/pli:latest sh -c 'plicc variables.pli && ./variables'

# Compile and run conversions.pli
docker run --rm --security-opt seccomp=unconfined -v $(pwd):/app -w /app codearchaeology/pli:latest sh -c 'plicc conversions.pli && ./conversions'

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 DECIMAL gives exact base-10 arithmetic — the reason PL/I remains dominant in financial systems. Use it for money, never FLOAT.
  • VARYING character strings track their current length separately from their maximum, so they behave like modern String types rather than fixed C-style buffers.
  • BIT(1) is PL/I’s boolean, with literals '1'B and '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, or STRINGSIZE.
  • INITIAL is 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.
  • DECLARE and DCL are synonymous, as are BINARY/BIN, DECIMAL/DEC, and INITIAL/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
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining