Fundamentals
PLC Data Types Explained: INT, REAL, BOOL & Type Conversion in Structured Text
Understand every IEC 61131-3 data type, when to use each one, and how to safely convert between them in Structured Text programs.
What goes wrong with the wrong data type
Pick the wrong data type and you get overflow, lost precision or behaviour that is hard to debug on a live machine: an INT counter that wraps from 32,767 to -32,768, or a division that silently drops the fraction. IEC 61131-3 defines a large set of types. Below are the ones you use every day and the conversions between them.
BOOL: one TRUE/FALSE value
BOOL is the most basic PLC data type. It holds a single TRUE or FALSE value, and every digital I/O point maps to one.
VAR
StartButton : BOOL; // Digital input
MotorRunning : BOOL; // Internal state
AlarmActive : BOOL := FALSE; // Initialized
END_VAR// Boolean operations
MotorRunning := StartButton AND NOT AlarmActive;
Habits for BOOLs
bMotorRunning, not M001.Integer types: INT, SINT, DINT, LINT, UINT
IEC 61131-3 defines several integer types, each with its own range:
| Type | Size | Range |
|---|---|---|
| SINT | 8-bit signed | -128 to 127 |
| INT | 16-bit signed | -32,768 to 32,767 |
| DINT | 32-bit signed | -2,147,483,648 to 2,147,483,647 |
| LINT | 64-bit signed | ±9.2 × 10¹⁸ |
| USINT | 8-bit unsigned | 0 to 255 |
| UINT | 16-bit unsigned | 0 to 65,535 |
| UDINT | 32-bit unsigned | 0 to 4,294,967,295 |
| ULINT | 64-bit unsigned | 0 to 18.4 × 10¹⁸ |
VAR
PartCount : INT; // Typical counter
BatchTotal : DINT; // Large accumulator
SensorRaw : UINT; // Analog input (0–65535)
MotorSpeed : INT; // Signed speed reference
END_VAR// Overflow danger! INT max is 32767
PartCount := PartCount + 1;
IF PartCount > 30000 THEN
// Consider switching to DINT before overflow
END_IF;
Choosing an integer type
INT (16-bit) is the default for counters, indexes and small quantities. Use DINT (32-bit) for production totals, encoder positions and large accumulators.
UINT and UDINT suit values that are never negative: analog raw values, bit masks and addresses. SINT and USINT are for memory-constrained systems and byte-level communication.
Floating-point types: REAL and LREAL
REAL (32-bit) and LREAL (64-bit) hold decimal numbers. You use them for analog processing, PID control and engineering calculations.
VAR
Temperature : REAL; // 32-bit float
PreciseAngle : LREAL; // 64-bit double
ScaleFactor : REAL := 0.00610352; // 20mA / 3277 counts
END_VARTemperature := INT_TO_REAL(SensorRaw) * ScaleFactor;
Floating-point pitfalls
Never compare REALs for equality. Floating-point precision errors make an exact match unreliable, so compare against a tolerance band:
// WRONG — floating-point precision errors
IF Temperature = 25.0 THEN ...// CORRECT — use a tolerance band
IF ABS(Temperature - 25.0) < 0.01 THEN ...
REAL also loses precision on large values, because it carries only about seven significant digits:
// REAL has ~7 significant digits
// Values like 16777217.0 cannot be represented exactly
// Use LREAL for high-precision applications
Type conversion functions
IEC 61131-3 is strongly typed, so write your conversions explicitly. Each one uses a function named SourceType_TO_TargetType.
Common conversion functions
| Function | Description | Example |
|---|---|---|
| INT_TO_REAL | Integer to float | rVal := INT_TO_REAL(iCount); |
| REAL_TO_INT | Float to integer (IEC rounds; some platforms truncate) | iVal := REAL_TO_INT(rTemp); |
| BOOL_TO_INT | FALSE→0, TRUE→1 | iFlag := BOOL_TO_INT(bRun); |
| INT_TO_BOOL | 0→FALSE, else→TRUE | bActive := INT_TO_BOOL(iState); |
| DINT_TO_REAL | Large int to float | rTotal := DINT_TO_REAL(diCount); |
| INT_TO_STRING | Integer to text | sDisplay := INT_TO_STRING(iVal); |
| STRING_TO_INT | Text to integer | iVal := STRING_TO_INT(sInput); |
| INT_TO_DINT | Widen integer | diVal := INT_TO_DINT(iSmall); |
VAR
RawAnalog : UINT; // 0-65535 from ADC
EngValue : REAL; // Scaled engineering units
DisplayStr : STRING; // For HMI display
END_VAR// Step 1: Convert unsigned integer to REAL for math
EngValue := UINT_TO_REAL(RawAnalog) * 100.0 / 65535.0;
// Step 2: Format for display
DisplayStr := CONCAT(REAL_TO_STRING(EngValue), ' %');
REAL_TO_INT: truncation or rounding
REAL_TO_INT is a common source of bugs because platforms do not all agree on what it does. IEC 61131-3 says it rounds to the nearest integer, and the TryPLC editor follows the standard. Some platforms truncate (round toward zero) instead. The listing below shows what a truncating platform gives, so check your vendor's manual before you rely on either behaviour:
VAR
rValue : REAL := 3.7;
iResult : INT;
END_VARiResult := REAL_TO_INT(rValue); // 3 where the platform truncates; IEC rounding gives 4
// To round properly, add 0.5 before converting:
iResult := REAL_TO_INT(rValue + 0.5); // Result: 4
Byte and bit-level types: BYTE, WORD, DWORD
You need these types for communication protocols, register mapping and bit manipulation. AND and OR with a mask read, set and clear single bits:
VAR
StatusWord : WORD; // 16-bit register
ErrorByte : BYTE; // 8-bit status
bBit3 : BOOL; // Extracted bit
END_VAR// Extract bit 3 from a status word
bBit3 := (StatusWord AND 16#0008) <> 0;
// Set bit 5
StatusWord := StatusWord OR 16#0020;
// Clear bit 5
StatusWord := StatusWord AND NOT 16#0020;
TIME, DATE and STRING types
TIME holds a duration you can add to, DATE holds a calendar date, and STRING holds text, with an optional length in brackets:
VAR
CycleTime : TIME := T#100ms;
StartDate : DATE := D#2026-01-15;
Greeting : STRING := 'Hello PLC';
LongText : STRING(200); // Extended length
END_VAR// TIME arithmetic
CycleTime := CycleTime + T#50ms; // T#150ms
// STRING operations
LongText := CONCAT('Batch: ', INT_TO_STRING(BatchNum));
Structured types: STRUCT and ARRAY
A STRUCT groups related data under one name, and an ARRAY holds several of the same thing:
TYPE MotorData :
STRUCT
Speed : REAL;
Current : REAL;
Running : BOOL;
FaultCode : INT;
END_STRUCT
END_TYPEVAR
Motor1 : MotorData;
Drives : ARRAY[1..4] OF MotorData;
END_VAR
Motor1.Speed := 1500.0;
Drives[2].Running := TRUE;
Common type conversion mistakes
1. Integer overflow
VAR iCount : INT; END_VAR
// If iCount = 32767:
iCount := iCount + 1; // Wraps to -32768!
// Fix: Use DINT for large accumulators
2. Precision lost in division
VAR
iNumerator : INT := 7;
iDenominator : INT := 2;
rResult : REAL;
END_VAR// WRONG: integer division happens first → 3.0
rResult := INT_TO_REAL(iNumerator / iDenominator);
// CORRECT: convert before dividing → 3.5
rResult := INT_TO_REAL(iNumerator) / INT_TO_REAL(iDenominator);
3. BOOL-to-INT ambiguity
// Some platforms: TRUE = 1, others: TRUE = -1 (16#FFFF)
// Always use explicit conversion:
iVal := BOOL_TO_INT(bFlag); // Guaranteed 0 or 1
You can try these conversions in the online ST editor and watch INT, REAL and STRING values change as the program runs.