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

  • Give them names that say what they mean: bMotorRunning, not M001.
  • Initialize BOOLs explicitly, so the start-up value is written in the declaration.
  • Break deeply nested boolean expressions into intermediate variables.
  • Integer types: INT, SINT, DINT, LINT, UINT

    IEC 61131-3 defines several integer types, each with its own range:

    TypeSizeRange
    SINT8-bit signed-128 to 127
    INT16-bit signed-32,768 to 32,767
    DINT32-bit signed-2,147,483,648 to 2,147,483,647
    LINT64-bit signed±9.2 × 10¹⁸
    USINT8-bit unsigned0 to 255
    UINT16-bit unsigned0 to 65,535
    UDINT32-bit unsigned0 to 4,294,967,295
    ULINT64-bit unsigned0 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_VAR

    Temperature := 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

    FunctionDescriptionExample
    INT_TO_REALInteger to floatrVal := INT_TO_REAL(iCount);
    REAL_TO_INTFloat to integer (IEC rounds; some platforms truncate)iVal := REAL_TO_INT(rTemp);
    BOOL_TO_INTFALSE→0, TRUE→1iFlag := BOOL_TO_INT(bRun);
    INT_TO_BOOL0→FALSE, else→TRUEbActive := INT_TO_BOOL(iState);
    DINT_TO_REALLarge int to floatrTotal := DINT_TO_REAL(diCount);
    INT_TO_STRINGInteger to textsDisplay := INT_TO_STRING(iVal);
    STRING_TO_INTText to integeriVal := STRING_TO_INT(sInput);
    INT_TO_DINTWiden integerdiVal := 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_VAR

    iResult := 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_TYPE

    VAR 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.