Variables and Types in RPG
Learn how RPG IV declares variables, the rich set of business-oriented data types, type conversions, named constants, and data structures
RPG IV is a statically and strongly typed procedural language. Every variable must be declared before use, every variable has a fixed type for its lifetime, and the compiler rejects assignments that would silently lose precision or change meaning. What makes RPG distinctive is that its type system was designed around the needs of business data processing - decimal precision, fixed-width records, and dates - rather than around the hardware-oriented integer and floating-point types that dominate most other languages.
This tutorial uses fully free-form RPG IV (the **FREE directive introduced in IBM i 7.2 TR3) and the modern DCL-S and DCL-DS declaration keywords. These replace the column-positional D-specifications that defined data items in older RPG code, while compiling to the same underlying objects on IBM i.
You will learn how to declare standalone variables with DCL-S, how to define named constants with DCL-C, how to group related fields into data structures with DCL-DS, and how RPG converts between numeric, character, and date types using built-in functions (BIFs).
Note: RPG runs exclusively on IBM i. There is no Docker image, no open-source compiler, and no port for Linux, macOS, or Windows. The examples below are syntactically correct ILE RPG IV and will compile under
CRTBNDRPGon any current IBM i release. If you do not have IBM i access, you can read along to understand how the type system works.
Declaring Variables and Basic Types
RPG groups its types into several families: character, numeric (integer, decimal, and floating-point), date and time, indicator (boolean), and pointer. Every standalone variable is declared with DCL-S followed by a name, a type keyword with its size attributes, and optional clauses such as INZ for initial value.
Create a file named variables.rpgle:
**FREE
// Variables and Types in RPG IV
// Demonstrates the major built-in data types
// --- Character types ---
dcl-s firstName char(20) inz('Ada'); // Fixed-length, blank-padded
dcl-s lastName char(20) inz('Lovelace');
dcl-s fullName varchar(50); // Variable-length character
// --- Integer types ---
dcl-s smallNum int(5) inz(123); // 2-byte signed integer
dcl-s mediumNum int(10) inz(1000000); // 4-byte signed integer
dcl-s bigNum int(20) inz(9876543210); // 8-byte signed integer
dcl-s posOnly uns(10) inz(4000000000); // 4-byte unsigned
// --- Decimal types (the workhorse of RPG) ---
dcl-s price packed(7:2) inz(1234.56); // 7 digits total, 2 decimal
dcl-s quantity zoned(5:0) inz(42); // 5 digits, 0 decimal
dcl-s rate packed(9:5) inz(0.07125); // High-precision rate
// --- Floating-point ---
dcl-s avgScore float(8) inz(98.6); // 8-byte double precision
// --- Indicator (boolean) ---
dcl-s isActive ind inz(*on); // *ON or *OFF
// --- Date, time, timestamp ---
dcl-s birthDate date(*iso) inz(d'1815-12-10');
dcl-s startTime time(*iso) inz(t'09.30.00');
dcl-s eventTs timestamp inz(z'2026-04-25-10.15.30.000000');
// --- Pointer ---
dcl-s ptr pointer inz(*null);
// Compose a full name (note: %trim removes trailing blanks from CHAR)
fullName = %trim(firstName) + ' ' + %trim(lastName);
// Display each value (DSPLY accepts any field)
dsply fullName;
dsply ('smallNum = ' + %char(smallNum));
dsply ('mediumNum = ' + %char(mediumNum));
dsply ('bigNum = ' + %char(bigNum));
dsply ('posOnly = ' + %char(posOnly));
dsply ('price = ' + %char(price));
dsply ('quantity = ' + %char(quantity));
dsply ('rate = ' + %char(rate));
dsply ('avgScore = ' + %char(avgScore));
dsply ('isActive = ' + %char(isActive));
dsply ('birthDate = ' + %char(birthDate));
dsply ('startTime = ' + %char(startTime));
dsply ('eventTs = ' + %char(eventTs));
return;
A few rules worth highlighting:
CHAR(n)is fixed-length and blank-padded tonbytes. Assigning'Ada'to aCHAR(20)stores'Ada '. Use%TRIMto strip the trailing blanks before concatenating.VARCHAR(n)stores only the characters you assign, with a 2-byte (or 4-byte) length prefix. No blank padding.PACKED(d:p)andZONED(d:p)are decimal types wheredis total digits andpis digits to the right of the decimal point. Packed decimal stores two digits per byte; zoned stores one digit per byte. These types perform exact decimal arithmetic - critical for currency and accounting.INT(n)sizes areINT(3),INT(5),INT(10),INT(20)- the digit count maps to 1-, 2-, 4-, and 8-byte two’s-complement integers.UNS(n)is the unsigned counterpart.INDis RPG’s boolean type, taking only the values*ON('1') and*OFF('0').- Date, time, and timestamp literals use the prefixes
D'...',T'...', andZ'...'respectively.
Type Conversions and Built-In Functions
RPG does not silently convert between unrelated types. Assigning a number to a character field, or parsing a string into a number, requires an explicit built-in function (BIF). All BIFs start with %. The most common conversion BIFs are %CHAR, %DEC, %INT, %FLOAT, %DATE, and %EDITC/%EDITW for formatted output.
Create a file named conversions.rpgle:
**FREE
// Type conversions in RPG IV
dcl-s amount packed(9:2) inz(1234.56);
dcl-s amountStr varchar(20);
dcl-s parsed packed(9:2);
dcl-s rounded int(10);
dcl-s formatted char(15);
dcl-s isoDateStr varchar(10) inz('2026-04-25');
dcl-s parsedDate date(*iso);
dcl-s dayCount int(10);
// --- Number to string ---
amountStr = %char(amount); // '1234.56'
dsply ('amount as string = ' + amountStr);
// --- String to packed decimal ---
parsed = %dec('98765.43' : 9 : 2); // value, total digits, decimals
dsply ('parsed decimal = ' + %char(parsed));
// --- Packed decimal to integer (truncates fraction) ---
rounded = %int(amount); // 1234 (NOT rounded; truncated)
dsply ('truncated to int = ' + %char(rounded));
// --- %INTH rounds half-away-from-zero ---
rounded = %inth(amount); // 1235
dsply ('rounded half away = ' + %char(rounded));
// --- Edit code: format with currency, commas, decimal ---
// Edit code 'J' = comma + decimal + leading sign for negatives
formatted = %editc(amount : 'J'); // ' 1,234.56'
dsply ('edited (J) =' + formatted);
// --- Edit word: custom output template ---
// ' $0. . ' inserts dollar sign and decimal point
formatted = %editw(amount : ' $0. . ');
dsply ('edited (word) =' + formatted);
// --- String to date and back ---
parsedDate = %date(isoDateStr : *iso);
dsply ('parsed date = ' + %char(parsedDate));
// --- Date arithmetic: %DIFF returns difference in given units ---
dayCount = %diff(parsedDate : %date() : *days);
dsply ('days from today = ' + %char(dayCount));
return;
Three details that often surprise newcomers:
%DECrequires the precision arguments when converting a string.%DEC('123.45' : 9 : 2)says “parse this as a 9-digit number with 2 decimals.” Without these, the compiler cannot know the result type.%INTtruncates toward zero;%INTHrounds half away from zero. There is no%INToverload that rounds.- Edit codes and edit words are RPG’s printf-equivalent for numeric formatting. They are inherited from the OUTPUT specifications of original RPG and remain the idiomatic way to format reports.
Constants, Data Structures, and Scope
RPG provides named constants through DCL-C, which bind a name to a compile-time literal. Constants have no storage at runtime - the compiler substitutes the literal at every reference. For grouping related fields, RPG offers data structures (DCL-DS), which are similar to C structs but with extra capabilities like overlay storage, qualified subfield names, and array-of-structure dimensions.
Create a file named structures.rpgle:
**FREE
// Named constants and data structures in RPG IV
// --- Named constants (no storage at runtime) ---
dcl-c MAX_RETRIES 3;
dcl-c PI 3.14159265358979;
dcl-c COMPANY_NAME 'Acme Corp';
dcl-c TAX_RATE 0.0825;
// --- Qualified data structure (subfields accessed via DS.field) ---
dcl-ds customer qualified;
id int(10);
name varchar(50);
balance packed(11:2);
active ind;
end-ds;
// --- Data structure with array subfield ---
dcl-ds order qualified;
orderId int(10);
itemCount int(5);
itemPrice packed(9:2) dim(5); // 5-element array subfield
end-ds;
dcl-s subtotal packed(11:2);
dcl-s tax packed(11:2);
dcl-s total packed(11:2);
dcl-s i int(5);
// --- Populate the customer ---
customer.id = 1001;
customer.name = COMPANY_NAME;
customer.balance = 5000.00;
customer.active = *on;
dsply ('Customer ID = ' + %char(customer.id));
dsply ('Customer name = ' + customer.name);
dsply ('Balance = ' + %char(customer.balance));
// --- Populate the order's array subfield ---
order.orderId = 42;
order.itemCount = 3;
order.itemPrice(1) = 19.99;
order.itemPrice(2) = 49.95;
order.itemPrice(3) = 12.50;
// --- Sum the array (note: 1-based indexing) ---
subtotal = 0;
for i = 1 to order.itemCount;
subtotal = subtotal + order.itemPrice(i);
endfor;
tax = subtotal * TAX_RATE; // Constant used in expression
total = subtotal + tax;
dsply ('Subtotal = ' + %char(subtotal));
dsply ('Tax (8.25%) = ' + %char(tax));
dsply ('Total = ' + %char(total));
// --- Trying to assign a wrong type fails at compile time ---
// customer.id = 'not a number'; // RNF7536: Operands not compatible
return;
Key points about the constructs above:
DCL-Cbinds a name to a literal. Unlike a variable, a constant cannot be reassigned and consumes no runtime memory.- The
QUALIFIEDkeyword on a data structure forces every reference to its subfields to use thedsName.subfieldsyntax. WithoutQUALIFIED, subfield names become global identifiers - a long-standing RPG convention that often collides with other variables in larger programs. New code should always useQUALIFIED. - Arrays use 1-based indexing with parentheses:
order.itemPrice(1), notorder.itemPrice[0]. RPG arrays inherit their indexing convention from the original specification format. - Variable scope is module-level by default. Variables declared at the top of a source member are visible throughout the program. Inside a
DCL-PROC/END-PROCsubprocedure, declarations are local to that procedure.
Running on IBM i
There is no Docker image for RPG. To compile and run any of these programs on IBM i, upload the source to the IFS (or copy it into a source physical file member) and use the CRTBNDRPG command:
| |
The same pattern applies to conversions.rpgle and structures.rpgle - just substitute the program name and source path. If you have access to PUB400.COM (a free public IBM i system) or IBM Power Virtual Server, you can upload these files via SFTP and compile them with the commands above.
Expected Output
When the variables.rpgle program runs in batch (5250 interactive sessions show each DSPLY as a message that requires Enter), the job log contains:
DSPLY Ada Lovelace
DSPLY smallNum = 123
DSPLY mediumNum = 1000000
DSPLY bigNum = 9876543210
DSPLY posOnly = 4000000000
DSPLY price = 1234.56
DSPLY quantity = 42
DSPLY rate = 0.07125
DSPLY avgScore = 9.860000000000000E+001
DSPLY isActive = 1
DSPLY birthDate = 1815-12-10
DSPLY startTime = 09.30.00
DSPLY eventTs = 2026-04-25-10.15.30.000000
The conversions.rpgle program produces (the day count depends on when you run it; the value below assumes today is 2026-04-25):
DSPLY amount as string = 1234.56
DSPLY parsed decimal = 98765.43
DSPLY truncated to int = 1234
DSPLY rounded half away = 1235
DSPLY edited (J) = 1,234.56
DSPLY edited (word) = $1,234.56
DSPLY parsed date = 2026-04-25
DSPLY days from today = 0
The structures.rpgle program produces:
DSPLY Customer ID = 1001
DSPLY Customer name = Acme Corp
DSPLY Balance = 5000.00
DSPLY Subtotal = 82.44
DSPLY Tax (8.25%) = 6.80
DSPLY Total = 89.24
Key Concepts
- RPG IV is statically and strongly typed - every variable has a fixed type declared with
DCL-S, and conversions between unrelated types require explicit built-in functions. - Decimal types are first-class.
PACKED(d:p)andZONED(d:p)perform exact decimal arithmetic and dominate business code - integers and floats are reserved for cases where decimal semantics are not needed. CHAR(n)is blank-padded whileVARCHAR(n)stores only assigned characters. Use%TRIMto strip padding when concatenating fixed-length character fields.- Date, time, and timestamp are built-in types with literal syntax (
D'...',T'...',Z'...') and dedicated arithmetic BIFs like%DIFFand%DATE. - Conversion BIFs like
%CHAR,%DEC,%INT,%INTH, and%EDITCare the only way to move data between type families. There is no implicit numeric-to-string coercion. DCL-Cnamed constants are compile-time substitutions with no runtime storage; use them for magic numbers, configuration values, and literal text.DCL-DS ... QUALIFIEDgroups related fields into a structure withdsName.subfieldaccess. Always prefer qualified data structures in new code to avoid the global-namespace pitfalls of unqualified subfields.- RPG arrays use 1-based indexing with parentheses -
arr(1)is the first element, notarr[0].
Comments
Loading comments...
Leave a Comment