Type Conversions¶
IronPLC follows the IEC 61131-3 type hierarchy for conversions between elementary data types. Some conversions happen implicitly (the compiler inserts them automatically); others require an explicit conversion function call.
Implicit Type Widening¶
A value of a narrower type can be passed wherever a wider type is expected. This applies to function arguments, function return values assigned to variables, and bare integer literals.
The general rule is: an implicit conversion is allowed when the source type’s full value range is exactly representable in the target type.
IronPLC supports four categories of implicit widening:
Integer widening — within signed, unsigned, and cross-sign integer types
Integer to real widening — when the conversion is lossless
Real widening —
REALtoLREAL, which is always losslessBit-string widening — within the
ANY_BITfamily (BOOLexcluded)
Integer widening¶
SINT |
INT |
DINT |
LINT |
USINT |
UINT |
UDINT |
ULINT |
|
|---|---|---|---|---|---|---|---|---|
SINT |
= |
Yes |
Yes |
Yes |
No |
No |
No |
No |
INT |
No |
= |
Yes |
Yes |
No |
No |
No |
No |
DINT |
No |
No |
= |
Yes |
No |
No |
No |
No |
LINT |
No |
No |
No |
= |
No |
No |
No |
No |
USINT |
No |
Yes |
Yes |
Yes |
= |
Yes |
Yes |
Yes |
UINT |
No |
No |
Yes |
Yes |
No |
= |
Yes |
Yes |
UDINT |
No |
No |
No |
Yes |
No |
No |
= |
Yes |
ULINT |
No |
No |
No |
No |
No |
No |
No |
= |
Read each row as “source type” and each column as “target type”. Yes means the compiler accepts the conversion implicitly. No means an explicit conversion function is required.
Integer to real widening¶
An integer type can implicitly widen to a real type when every value of the
source type is exactly representable in the target type. REAL is a 32-bit
float with a 23-bit mantissa, so it covers integers up to 16 bits. LREAL
is a 64-bit float with a 52-bit mantissa, so it covers all standard integer
types.
REAL |
LREAL |
|
|---|---|---|
SINT |
Yes |
Yes |
INT |
Yes |
Yes |
DINT |
No |
Yes |
LINT |
No |
Yes |
USINT |
Yes |
Yes |
UINT |
Yes |
Yes |
UDINT |
No |
Yes |
ULINT |
No |
Yes |
DINT, LINT, UDINT, and ULINT cannot implicitly widen to
REAL because their value ranges exceed the 23-bit mantissa precision. Use
an explicit conversion such as DINT_TO_REAL(x) or widen to LREAL
instead.
Real to integer is never implicit. Use explicit conversion functions such as
REAL_TO_INT.
Real widening¶
A REAL value (from a variable, field, or return value, not just a bare
literal) can implicitly widen to LREAL. This is always lossless: every
32-bit REAL value is exactly representable in 64-bit LREAL.
The reverse direction, LREAL to REAL, is never implicit because it can
lose precision. Use an explicit conversion such as LREAL_TO_REAL(x).
Bit-string widening¶
Bit-string types widen by zero-extending to a wider container. BOOL is
excluded because it has boolean semantics (TRUE/FALSE), not
numeric bit-container semantics.
BYTE |
WORD |
DWORD |
LWORD |
|
|---|---|---|---|---|
BYTE |
= |
Yes |
Yes |
Yes |
WORD |
No |
= |
Yes |
Yes |
DWORD |
No |
No |
= |
Yes |
LWORD |
No |
No |
No |
= |
Widening chains¶
Signed:
SINT→INT→DINT→LINTUnsigned:
USINT→UINT→UDINT→ULINTCross-sign: an unsigned type can widen to a signed type of strictly greater bit width (e.g.
USINT→INT,UINT→DINT).Bit-string:
BYTE→WORD→DWORD→LWORDInteger to REAL (lossless):
SINT,INT,USINT,UINT→REALInteger to LREAL (lossless): all integer types →
LREALReal (lossless):
REAL→LREAL
Example¶
FUNCTION TAKES_DINT : BOOL
VAR_INPUT
in : DINT;
END_VAR
TAKES_DINT := in > 0;
END_FUNCTION
PROGRAM main
VAR
i : INT := 5;
r : BOOL;
END_VAR
(* Accepted: INT widens to DINT *)
r := TAKES_DINT(in := i);
END_PROGRAM
Arithmetic on mixed types¶
The same widening decides an arithmetic expression whose operands have
different types. The narrower operand widens to the wider one, the operation
is carried out at the wider type, and the result has that type: INT + DINT
is a DINT, and INT + REAL is a REAL computed in floating point. A
bare integer literal takes the other operand’s type, so d + 1 is a
DINT and r * 2 a REAL.
When neither operand widens to the other, the expression is an error
(P4049). DINT + REAL is one
such pair, because a 32-bit integer does not fit exactly in REAL; so is
DINT + UDINT, because neither type holds every value of the other.
Convert one operand explicitly, or widen to a type that holds both, such as
LREAL or LINT.
The result is then assigned like any other value, so assigning a DINT
result to an INT needs an explicit conversion:
VAR
i : INT := 3;
d : DINT := 100000;
r : REAL := 1.5;
x : REAL;
y : DINT;
END_VAR
x := i + r; (* 4.5: i widens to REAL *)
y := i + d; (* a DINT result *)
i := DINT_TO_INT(i + d); (* narrowing needs a conversion *)
Narrowing Conversions¶
Converting from a wider type to a narrower type (e.g. DINT to INT) can
lose data, so IronPLC requires an explicit conversion function:
VAR
big : DINT := 100000;
small : INT;
END_VAR
small := DINT_TO_INT(big); (* explicit narrowing *)
Bit-String Types¶
Bit-string types (BYTE, WORD, DWORD, LWORD) support implicit
widening within the bit-string family — see the bit-string widening matrix
above.
BOOL is excluded from bit-string widening. Although IEC 61131-3 places
BOOL under ANY_BIT, it has boolean semantics and is not treated as a
numeric bit container.
Cross-family widening¶
Widening from a bit-string type to an integer type crosses the ANY_BIT /
ANY_INT boundary and is not part of the IEC 61131-3 standard. IronPLC
supports this as an extension behind three flags, one per rule:
--allow-cross-family-widening for widening to a strictly wider integer,
--allow-cross-family-conversion for the one equal-width pair, and
--allow-int-literal-to-bit-string for bare integer literals. All three are
enabled by default in the Rusty, CODESYS and TwinCAT dialects. See
Enabling Dialects and Features for how to enable them.
When the flag is enabled, a bit-string type can widen to an integer type of
strictly greater bit width. DWORD to UDINT is the one equal-width
exception, described after the table:
SINT |
INT |
DINT |
LINT |
USINT |
UINT |
UDINT |
ULINT |
|
|---|---|---|---|---|---|---|---|---|
BYTE |
No |
Yes |
Yes |
Yes |
No |
Yes |
Yes |
Yes |
WORD |
No |
No |
Yes |
Yes |
No |
No |
Yes |
Yes |
DWORD |
No |
No |
No |
Yes |
No |
No |
Yes |
Yes |
LWORD |
No |
No |
No |
No |
No |
No |
No |
No |
The reverse direction (integer to bit-string) is not implicit, apart from one
pair. Use explicit conversion functions such as INT_TO_BYTE.
UDINT and DWORD are the one pair that converts implicitly at equal
width, and the one case where integer to bit-string is implicit. Both
directions require the --allow-cross-family-conversion flag — this
converts rather than widens, which is why it is not the widening flag. The two types
share a 32-bit slot, so the conversion leaves the bit pattern unchanged:
VAR
udValue : UDINT := 3000000000;
dwValue : DWORD := 16#FFFFFFFF;
dwFromUdint : DWORD;
udFromDword : UDINT;
END_VAR
dwFromUdint := udValue; (* 16#B2D05E00, the same 32 bits *)
udFromDword := dwValue; (* 4294967295, the same 32 bits *)
The exception covers exactly this pair. The other equal-width bit-string and
unsigned-integer pairs — BYTE and USINT, WORD and UINT,
LWORD and ULINT — need an explicit conversion function in both
directions, as do all signed integer types. Without the flag, IronPLC reports
P4035 for UDINT and DWORD
too.
Bare integer literals (e.g. 0) can also be passed to bit-string parameters
when --allow-int-literal-to-bit-string is enabled. This is literal typing
rather than widening, so it has its own flag.
Bit-string arithmetic¶
IEC 61131-3 defines no arithmetic on bit strings, so counter + 1 where
counter is a BYTE is an error
(P4049) unless
--allow-bit-string-arithmetic is enabled. See
Enabling Dialects and Features for how to enable it.
With the flag, a BYTE, WORD, DWORD or LWORD operand of +,
-, * or / is treated as the unsigned integer of its width
(USINT, UINT, UDINT, ULINT), and the ordinary widening rules
apply to it:
Two bit strings give the wider bit string:
BYTE + WORDis aWORD.A bit string and a literal keep the bit-string type:
BYTE#255 + 1isBYTE#0, wrapping at the width.A bit string and a number give what the widening picks:
BYTE + INTis anINT, becauseUSINTwidens toINT, butWORD + INTis an error, becauseUINTandINTdo not widen to each other.
BOOL is never treated as a number, and MOD is not included: b MOD 2
is an error with or without the flag, as MOD(b, 2) is.
Integer and Real Types¶
Integer types can implicitly widen to REAL or LREAL when the conversion
is lossless — see the integer to real widening matrix above.
Converting from a real type to an integer type is never implicit because it
truncates the fractional part. Use explicit conversion functions such as
REAL_TO_INT.
Bare (untyped) integer literals like 0 or 42 can be passed to REAL
or LREAL parameters because the literal has no declared type and the
compiler infers the target type.
Explicit Conversion Functions¶
IEC 61131-3 defines conversion functions for all type pairs. The naming
convention is <SOURCE>_TO_<TARGET>:
INT_TO_REAL(x) (* integer to real *)
DINT_TO_INT(x) (* narrowing: DINT to INT *)
BYTE_TO_INT(x) (* bit-string to integer *)
REAL_TO_INT(x) (* real to integer, truncates *)