PLC Data Types Explained: BOOL, INT, DINT, REAL, and More
PLC data types explained — BOOL, INT, DINT, REAL, WORD, and strings, their sizes and ranges, signed vs unsigned, and how to choose the right type.
Every variable in a PLC program occupies a defined amount of memory and can only hold values within a fixed range. Those two facts — size and range — are what a data type encodes. Pick the wrong type and your counter overflows at 32,767 instead of the expected 65,535, or your analog scaling loses precision. Pick a type that is too large and you waste memory on controllers where RAM is measured in kilobytes. This guide walks through every IEC 61131-3 elementary type, explains the signed-versus-unsigned distinction, maps vendor naming conventions across Allen-Bradley, Siemens, and IEC-compliant platforms, and ends with a practical decision framework for real control code.
What a Data Type Is (and Why It Matters)
A data type is a contract between your program and the processor. It tells the CPU three things:
- How many bits to allocate for the variable in memory
- What range of values that bit pattern can represent
- How arithmetic and logic instructions should interpret the bits
Without a data type, the processor has no way to know whether the 32-bit value 0x00003039 is the integer 12,345, the floating-point number roughly 1.73 × 10⁻⁴¹, or four ASCII characters. The type is the interpretation layer.
This matters practically in industrial control for several reasons:
Memory constraints. Many embedded PLCs allocate a fixed data table. A small Micro820 or a safety-rated Siemens ET 200SP CPU may have only a few hundred kilobytes of work memory. Declaring a thousand variables as DINT (4 bytes each) when BOOL or INT would suffice wastes 4 MB of headroom you do not have.
Instruction compatibility. Move, compare, and math instructions are typed in every platform. A standard ADD instruction on a Logix controller operates on the declared type of the tag. Feeding a REAL into an integer ADD without an explicit conversion is either a compile error or an implicit truncation — both cause subtle bugs.
Communication and I/O mapping. Fieldbus messages, HMI tags, and historian writes all carry a type. If the PLC sends a DINT and the SCADA historian expects an INT, you get silent clipping at 32,767 — no alarm, just wrong data.
Understanding types is therefore a prerequisite for reliable programs. For context on where data types sit within the broader programming model, see the PLC programming basics fundamentals guide.
The IEC 61131-3 Elementary Types
IEC 61131-3 defines a hierarchy of elementary data types. The table below covers every type a controls engineer encounters regularly, together with its storage size and the numerical range it can hold.
| IEC 61131-3 Type | Size | Range / Notes |
|---|---|---|
| BOOL | 1 bit (1 byte allocated) | 0 or 1 (FALSE / TRUE) |
| BYTE | 8 bits | 0 to 255 (unsigned, no arithmetic intent) |
| SINT | 8 bits | −128 to +127 (signed integer) |
| USINT | 8 bits | 0 to 255 (unsigned integer) |
| WORD | 16 bits | 0 to 65,535 (bit-collection, no arithmetic intent) |
| INT | 16 bits | −32,768 to +32,767 (signed integer) |
| UINT | 16 bits | 0 to 65,535 (unsigned integer) |
| DWORD | 32 bits | 0 to 4,294,967,295 (bit-collection, no arithmetic intent) |
| DINT | 32 bits | −2,147,483,648 to +2,147,483,647 (signed integer) |
| UDINT | 32 bits | 0 to 4,294,967,295 (unsigned integer) |
| LINT | 64 bits | −9.2 × 10¹⁸ to +9.2 × 10¹⁸ (signed, not supported on all platforms) |
| REAL | 32 bits | ±1.18 × 10⁻³⁸ to ±3.40 × 10³⁸, ~7 significant decimal digits |
| LREAL | 64 bits | ±2.23 × 10⁻³⁰⁸ to ±1.80 × 10³⁰⁸, ~15–16 significant decimal digits |
| STRING | Variable (default 80 or 82 bytes on many platforms) | Zero-terminated character array |
| TIME | 32 bits | Duration: T#0ms to T#49d17h2m47s295ms |
| DATE | 32 bits | Calendar date: D#1970-01-01 to D#2106-02-06 |
| TIME_OF_DAY | 32 bits | TOD#00:00:00 to TOD#23:59:59.999 |
Key reading notes for the table:
- BYTE, WORD, and DWORD are bit-collection types, not arithmetic types. The standard intends them for bitwise operations and register mapping, not for addition or subtraction. Use USINT/UINT/UDINT when you need unsigned arithmetic.
- REAL uses the IEEE 754 single-precision format. The ~7 significant digit limit means that the value 1,234,567.89 stored as a REAL will round to 1,234,568.0. This is the most common source of precision loss in PLC programs.
- STRING size is implementation-defined. TIA Portal defaults to 254 characters; Studio 5000 Logix Designer defaults to 82 bytes (80 usable characters plus a length byte and null terminator). Always check your platform documentation.
- TIME stores a duration internally as a DINT millisecond count, which is why its maximum is approximately 49 days.
Signed vs Unsigned: The Practical Difference
Signed types can represent negative values. Unsigned types cannot hold negative values but can represent twice as many positive values in the same number of bits.
The mechanism is two's complement representation. In a signed 16-bit INT, the most significant bit is the sign bit: 0 means positive, 1 means negative. That leaves 15 bits for magnitude, giving a range of −32,768 to +32,767. In an unsigned UINT, all 16 bits encode magnitude, giving 0 to 65,535.
When to use signed types:
- Any quantity that can legitimately go negative: position error, temperature differential, torque setpoint
- Counter values that can count down through zero (setpoint minus actual)
- Offsets and trim values
When to use unsigned types:
- Physical quantities that are always non-negative by definition: elapsed time, batch count, part count
- Register mapping that must match Modbus or Profibus address offsets, which are inherently unsigned
- Bit masks and flags where the sign bit interpretation would be wrong
A subtle trap: comparing a signed INT to an unsigned UINT in code that implicitly promotes types can produce wrong comparisons when the INT is negative and the UINT is zero. Explicit casting removes ambiguity. The PLC programming languages complete guide covers how Structured Text handles implicit type promotion rules.
Bit, Word, and Double-Word Relationships
Understanding the hierarchy of bit widths helps when reading PLC manuals and mapping hardware I/O.
1 BOOL = 1 bit
8 BOOLs = 1 BYTE (or SINT/USINT)
16 bits = 1 WORD (or INT/UINT)
32 bits = 1 DWORD (or DINT/UDINT/REAL)
64 bits = 1 LWORD (or LINT/ULINT/LREAL)
This hierarchy is not merely academic. On many older platforms, memory is addressed at the word (16-bit) level. Writing a single BOOL to a 16-bit word occupies the whole word in terms of addressing, even though only one bit changes. This is why PLC documentation distinguishes between bit-addressable memory (M-bits in Siemens, bit files in PLC-5) and word-addressed memory.
In practice:
- Digital I/O arrives as BOOL or as packed BYTE/WORD from the I/O module
- Analog I/O arrives as raw INT (typically 0–27,648 on Siemens, 0–32,767 on Logix) requiring scaling to REAL engineering units — see PLC math instructions for the scaling formula
- Communication registers (Modbus, EtherNet/IP implicit messaging) use WORD or DINT as the fundamental transport unit, requiring explicit packing and unpacking of BOOL values
Vendor Naming Conventions
IEC 61131-3 type names are not universally adopted. Each major vendor introduced naming conventions before or independently of the standard. The table below maps the most common equivalents.
| Concept | IEC 61131-3 | Allen-Bradley (Logix 5000) | Siemens (TIA Portal / S7) |
|---|---|---|---|
| Single bit | BOOL | BOOL | BOOL |
| 8-bit signed | SINT | SINT | SINT |
| 8-bit unsigned | USINT | USINT | USINT |
| 16-bit signed | INT | INT | INT |
| 16-bit unsigned | UINT | UINT | UINT |
| 32-bit signed | DINT | DINT | DINT |
| 32-bit unsigned | UDINT | UDINT | UDINT |
| 32-bit float | REAL | REAL | REAL |
| 64-bit float | LREAL | LREAL | LREAL |
| 16-bit bit-collection | WORD | — (use UINT) | WORD |
| 32-bit bit-collection | DWORD | — (use UDINT) | DWORD |
| Character string | STRING | STRING | STRING |
| Duration | TIME | — (use DINT ms) | TIME |
Allen-Bradley specific notes:
Studio 5000 Logix Designer is largely IEC-aligned for the numeric types listed above. The main divergence is that Logix does not use WORD or DWORD as distinct types — the closest equivalents are UINT and UDINT respectively. Logix also adds AXIS_SERVO, MESSAGE, CONTROL, and other controller-object types that have no IEC equivalent. The older PLC-5 and SLC 500 platforms used N (integer file), F (float file), B (bit file), and T/C/R (timer/counter/control files) — a file-based addressing model entirely different from the tag-based model.
Siemens specific notes:
TIA Portal (S7-1200/1500) follows IEC 61131-3 naming closely. The S7 Classic (S7-300/400) used Memory Bytes (MB), Memory Words (MW), and Memory Double Words (MD) for direct memory access — these are addressing prefixes, not type names, but they map onto BYTE/WORD/DWORD. TIA Portal preserves these addressing prefixes in absolute address mode but the preferred approach is symbolic tag-based programming using declared types.
Arrays, Structures (UDT), and Tags
Once you understand elementary types, the natural next step is grouping related data into compound types.
Arrays
An array is an ordered collection of elements of the same type, indexed by an integer offset.
(* Structured Text declaration *)
VAR
ConveyorSpeeds : ARRAY[1..8] OF REAL; (* 8 conveyor zone speeds *)
AlarmBits : ARRAY[0..15] OF BOOL; (* 16 alarm flags *)
END_VAR
Arrays are the right choice when you have a repeating structure of the same type — multiple drive setpoints, temperature readings from a sensor array, or recipe parameters indexed by product number. Accessing elements via loop index makes array-based code compact and easy to extend.
Key constraint: array bounds are fixed at declaration time on most platforms. Dynamic arrays are supported in some modern environments (CODESYS, TIA Portal S7-1500 with OB variant) but not universally.
Structures and User-Defined Types (UDT)
A structure groups variables of different types under a single name. In IEC 61131-3 these are called derived data types; vendors typically call them User-Defined Types (UDT) or Program Data Types (PDT).
(* UDT definition *)
TYPE Motor_Data :
STRUCT
Running : BOOL;
FaultActive : BOOL;
SpeedSetpoint: REAL; (* Hz *)
SpeedActual : REAL; (* Hz *)
CurrentDraw : REAL; (* A *)
RunHours : DINT;
END_STRUCT
END_TYPE
(* Instance declaration *)
VAR
ConveyorMotor : Motor_Data;
MixerMotor : Motor_Data;
END_VAR
UDTs offer significant engineering advantages:
- Reuse: define the structure once, instantiate it for every drive, pump, or valve in the system
- Consistency: every instance of a motor UDT has the same variable names, eliminating naming inconsistencies across the program
- HMI/SCADA integration: the HMI connects to
ConveyorMotor.SpeedActual— a self-describing path that is easier to maintain than positional register addresses - Structured Text readability: code like
IF ConveyorMotor.FaultActive THENis immediately clear to anyone reading the program
Nesting UDTs inside other UDTs is supported on most modern platforms, enabling hierarchical data models that mirror the physical equipment hierarchy. For more on writing UDT-heavy programs in Structured Text, see the structured text programming guide.
Tags
In tag-based systems (Logix 5000, TIA Portal, CODESYS), every variable is a named tag with a declared type. Tags can be scoped at the program level (local) or controller level (global). The tag name, data type, and description form the "single source of truth" for that variable — PLC addressing is resolved symbolically, not by raw memory address.
Choosing the Right Data Type
Selecting a data type is an engineering decision, not an afterthought. Apply these rules in order.
Step 1 — Define what the variable represents
Is it a physical I/O point (BOOL), a count (integer), a scaled measurement (REAL), or a duration (TIME)? The physical meaning immediately narrows the candidate types.
Step 2 — Determine the required range
What are the minimum and maximum values the variable will ever hold in normal and fault operation? Pick the smallest type whose range covers that span comfortably, with a safety margin.
| Application | Recommended Type | Reason |
|---|---|---|
| Discrete input / output | BOOL | Single bit, direct I/O mapping |
| Counters (up to 32,767) | INT | Covers most production counts |
| Counters (up to 2.1 billion) | DINT | High-volume batching, odometers |
| Analog engineering units | REAL | Decimal precision needed |
| High-precision calculations | LREAL | When REAL's ~7 digits are insufficient |
| Modbus register value | UINT or INT | Matches 16-bit register width |
| Recipe parameters (mixed) | UDT containing REAL/INT/BOOL | Grouping for reuse |
| Timer preset / elapsed | TIME or DINT (ms) | TIME preferred; DINT if platform lacks TIME |
Step 3 — Consider memory and scan time
On resource-constrained PLCs, prefer smaller types. On high-performance controllers, the difference between INT and DINT is negligible — go wider if there is any doubt about range. Floating-point arithmetic (REAL) costs more scan time than integer arithmetic on some embedded processors; profile before using REAL inside tight loops.
Step 4 — Match the communication protocol
If the value travels over Modbus TCP, EtherNet/IP, or PROFINET, check what type the receiving device expects. Modbus registers are 16-bit unsigned; sending a Logix DINT over a Modbus CIP path requires explicit packing into two registers. Mismatches here are a common commissioning headache that the right type declaration prevents.
The controls engineer's quick-reference view
- BOOL — every physical I/O, every motor run/stop, every alarm flag
- INT — small counts, HMI-facing setpoints with limited range
- DINT — large counts (parts produced, encoder pulses), any integer that might exceed 32,767
- REAL — analog signals scaled to engineering units (°C, bar, m/min), PID setpoints and outputs, ratio calculations
- STRING — batch IDs, recipe names, operator messages, barcode values
- UDT — any repeated equipment type (drives, valves, pumps, zones)
One practical rule: if you are scaling a 0–27,648 raw analog INT to a 0–100.0% REAL for display and control, do the scale in REAL arithmetic and store the result as REAL. Do not try to work in fixed-point integers — the code becomes opaque and the precision savings are rarely worth the maintenance burden.
Frequently Asked Questions
What are PLC data types?
PLC data types define the size, range, and interpretation of every variable in a PLC program. Each type allocates a fixed number of bits in controller memory and restricts what values the variable can hold. IEC 61131-3 standardizes the elementary types (BOOL, INT, DINT, REAL, STRING, TIME, and others) used across most modern PLC platforms.
What is the difference between INT and DINT?
Both are signed integers. INT occupies 16 bits and can hold values from −32,768 to +32,767. DINT occupies 32 bits and can hold values from −2,147,483,648 to +2,147,483,647. Use INT when your values are guaranteed to stay within the 16-bit range; use DINT for large counters, encoder positions, or any value that might exceed 32,767. On modern 32-bit PLCs, DINT arithmetic is no slower than INT arithmetic, so many programmers default to DINT for all integer work.
What is a REAL in a PLC?
REAL is a 32-bit floating-point type following the IEEE 754 single-precision standard. It can represent fractional values and very large or very small numbers (roughly ±3.4 × 10³⁸). REAL provides approximately 7 significant decimal digits of precision. It is the standard choice for analog engineering units — temperature in °C, pressure in bar, flow in m³/h — and for PID setpoints and outputs. When more precision is needed (high-resolution positioning, laboratory measurements), use LREAL (64-bit double-precision, ~15–16 significant digits).
What is a UDT in a PLC?
A UDT (User-Defined Type) is a custom data structure that groups multiple variables of different types under a single name. It is equivalent to a struct in C or a record in Pascal. UDTs are declared once and then instantiated for each piece of equipment that shares the same data model — for example, a single Pump_Data UDT containing BOOL, INT, and REAL members can be instantiated once per pump in the system. This enforces consistent naming, simplifies HMI tag browsing, and makes code reuse straightforward.


