Type Conversions
TSharkRex is a strongly typed language - you cannot freely mix data types in expressions without telling the compiler how to interpret the conversion. This chapter covers all available conversion functions, from standard named conversions to low-level bit manipulation, float conversions, and the implicit conversions the compiler handles automatically.
Standard Conversions
Standard conversion functions follow the naming pattern
SOURCE_TO_TARGET(value). They convert a value from one integer or boolean
type to another:
| Function | Conversion | Example |
|---|---|---|
BYTE_TO_BOOL(x) |
BYTE → BOOL | flag := BYTE_TO_BOOL(value); |
INT_TO_BOOL(x) |
INT → BOOL | flag := INT_TO_BOOL(count); |
DINT_TO_BOOL(x) |
DINT → BOOL | flag := DINT_TO_BOOL(result); |
INT_TO_BYTE(x) |
INT → BYTE | b := INT_TO_BYTE(value); |
UDINT_TO_INT(x) |
UDINT → INT | i := UDINT_TO_INT(time); |
SINT_TO_DINT(x) |
SINT → DINT | d := SINT_TO_DINT(small); |
Practical Examples
Boolean to integer for output control
VAR
doorOpen : BOOL;
outputValue : INT;
canData : ARRAY[0..7] OF BYTE;
END_VAR;
// BOOL is implicitly 0 or 1 - multiply for PWM range
outputValue := doorOpen * 1000;
// Or use explicit conversion when needed in expressions
outputValue := INT_TO_BOOL(canData[3]) * 500;
Narrowing a larger type to BYTE
VAR
fullValue : INT := 1234;
lowByte : BYTE;
END_VAR;
// Extract low byte (loses upper bits)
lowByte := INT_TO_BYTE(fullValue);
// lowByte = 210 (1234 AND 0xFF = 210)
Widening SINT to DINT for arithmetic
VAR
temperature : SINT := -20; // Signed byte: -128 to 127
tempInMillideg : DINT;
END_VAR;
// Widen to DINT before multiplication to avoid overflow
tempInMillideg := SINT_TO_DINT(temperature) * 1000;
// tempInMillideg = -20000
BOOL, any non-zero value becomes
TRUE and zero becomes FALSE. When converting from
BOOL, TRUE becomes 1 and FALSE becomes 0.
Low-Level Conversions
Low-level conversion functions give you explicit control over how bits are interpreted when changing type size. These are essential when working with CAN data where byte ordering and sign handling matter:
| Function | Description | Example |
|---|---|---|
TRUNC(val, type) |
Truncate to a smaller type - keeps only the low bits | b := TRUNC(dint_val, BYTE); |
S_EXT(val, type) |
Sign-extend to a larger type - preserves the sign bit | d := S_EXT(sint_val, DINT); |
Z_EXT(val, type) |
Zero-extend to a larger type - fills upper bits with zeros | d := Z_EXT(byte_val, DINT); |
TRUNC - Truncation
TRUNC discards the upper bits, keeping only the bits that fit in the target
type. This is equivalent to a bitwise AND with the target type’s mask:
VAR
bigValue : DINT := 0xAABBCCDD;
lowWord : INT;
lowByte : BYTE;
END_VAR;
lowWord := TRUNC(bigValue, INT); // 0xCCDD
lowByte := TRUNC(bigValue, BYTE); // 0xDD
S_EXT - Sign Extension
S_EXT widens a signed value while preserving its sign. The sign bit (most
significant bit) is copied into all the new upper bits:
VAR
signed_byte : SINT := -50; // 0xCE in binary
wide_result : DINT;
END_VAR;
wide_result := S_EXT(signed_byte, DINT);
// wide_result = -50 (0xFFFFFFCE - sign bit extended)
This is critical when decoding signed CAN data. Many vehicle signals transmit signed
values as single bytes. Without sign extension, a negative value like -50
(stored as 0xCE) would be interpreted as 206:
VAR
canData : ARRAY[0..7] OF BYTE;
temperatureDINT : DINT;
temperatureWrong : DINT;
END_VAR;
// CAN byte representing temperature: 0xCE = -50°C (signed)
temperatureDINT := S_EXT(canData[2], DINT);
// Correct: temperatureDINT = -50
// WRONG: without S_EXT, zero-extension gives +206
temperatureWrong := Z_EXT(canData[2], DINT);
// Wrong: temperatureWrong = 206
Z_EXT - Zero Extension
Z_EXT widens an unsigned value by filling the upper bits with zeros. Use
this for unsigned data such as CAN IDs, counters, and positive-only measurements:
VAR
canByte : BYTE := 0xFF;
fullValue : DINT;
END_VAR;
fullValue := Z_EXT(canByte, DINT);
// fullValue = 255 (0x000000FF - upper bits zeroed)
S_EXT for signed data (temperatures, angles, offsets) and
Z_EXT for unsigned data (counters, IDs, percentages). Getting this wrong
will produce wildly incorrect values for negative numbers.
Float Conversions
TSharkRex supports IEEE 754 floating-point arithmetic via the REAL type.
Use these functions to convert between integer and floating-point representations:
| Function | Description |
|---|---|
FP_TO_SINT(val, type) |
Float → signed integer (truncates toward zero) |
FP_TO_UINT(val, type) |
Float → unsigned integer (truncates toward zero) |
SINT_TO_FP(val, type) |
Signed integer → float |
UINT_TO_FP(val, type) |
Unsigned integer → float |
FP_TRUNC(val, type) |
Float truncation - LREAL → REAL (double to single precision) |
FP_EXT(val, type) |
Float extension - REAL → LREAL (single to double precision) |
Float Precision Conversion
To convert between single-precision (REAL) and double-precision (LREAL):
VAR
single : REAL;
double : LREAL;
back : REAL;
END_VAR;
single := 3.14;
// REAL -> LREAL (extend precision)
double := FP_EXT(single, LREAL);
// LREAL -> REAL (truncate precision, may lose accuracy)
back := FP_TRUNC(double, REAL);
Float Conversion Examples
Integer to float for precise division
VAR
numerator : DINT := 7;
denominator : DINT := 3;
result : REAL;
resultInt : DINT;
END_VAR;
// Convert to float for division, then back to integer
result := SINT_TO_FP(numerator, REAL) / SINT_TO_FP(denominator, REAL);
// result = 2.333...
resultInt := FP_TO_SINT(result, DINT);
// resultInt = 2 (truncated toward zero)
Scaling a CAN signal with float precision
VAR
rawValue : INT; // Raw CAN value (0-4095)
scaledFloat : REAL;
temperature : DINT; // Final temperature in °C
END_VAR;
// Scale: temp = (raw * 0.1) - 40.0
scaledFloat := SINT_TO_FP(rawValue, REAL) * 0.1 - 40.0;
temperature := FP_TO_SINT(scaledFloat, DINT);
Unsigned to float
VAR
rpmRaw : UDINT := 3200;
rpmFloat : REAL;
END_VAR;
rpmFloat := UINT_TO_FP(rpmRaw, REAL);
// rpmFloat = 3200.0
FP_TO_SINT and FP_TO_UINT truncate
toward zero (not round). A value of 2.9 becomes 2, and -2.9 becomes -2. If you need
rounding, add 0.5 before converting: FP_TO_SINT(val + 0.5, DINT).
Sign Conversions
These functions reinterpret a value’s sign without changing its bit pattern. The underlying bits stay the same - only the compiler’s interpretation changes:
| Function | Description |
|---|---|
SINT_TO_UINT(val) |
Reinterpret signed as unsigned (same bit pattern) |
UINT_TO_SINT(val) |
Reinterpret unsigned as signed (same bit pattern) |
Sign Conversion Examples
VAR
signed_val : DINT := -1;
unsigned_val : UDINT;
END_VAR;
unsigned_val := SINT_TO_UINT(signed_val);
// unsigned_val = 4294967295 (0xFFFFFFFF - same bits, different meaning)
VAR
unsigned_val : UDINT := 0xFFFFFFF0;
signed_val : DINT;
END_VAR;
signed_val := UINT_TO_SINT(unsigned_val);
// signed_val = -16 (same bits, now interpreted as signed)
Sign conversions are primarily needed when interfacing with CAN data that stores signed values in unsigned byte arrays, or when passing values to functions that expect a different signedness:
// CAN data gives us a two-byte unsigned value
VAR
canData : ARRAY[0..7] OF BYTE;
rawUnsigned : UINT;
steeringAngle : INT; // Signed: negative = left, positive = right
END_VAR;
rawUnsigned := canData[0] * 256 + canData[1];
steeringAngle := UINT_TO_SINT(rawUnsigned);
// If rawUnsigned = 0xFF00, steeringAngle = -256 (hard left)
Implicit Conversions
TSharkRex automatically performs certain “safe” conversions where no information is lost. These implicit conversions happen transparently in expressions:
BOOL to Integer Types
BOOL values are implicitly converted to integer types in arithmetic
expressions. TRUE becomes 1 and FALSE becomes 0:
VAR
ignitionOn : BOOL := TRUE;
outputPWM : INT;
END_VAR;
// BOOL * INT is implicitly converted - no cast needed
outputPWM := ignitionOn * 1000;
// outputPWM = 1000 (TRUE * 1000 = 1 * 1000)
This is the foundation of the idiomatic TSharkRex pattern for driving outputs from boolean signals (see Outputs & PWM).
Small to Large Integer Types
In mixed-type arithmetic, smaller integer types are implicitly widened to match the larger type:
VAR
byteVal : BYTE := 200;
intVal : INT := 1000;
result : INT;
END_VAR;
result := byteVal + intVal;
// byteVal implicitly zero-extended to INT before addition
// result = 1200
DINT to a
BYTE without an explicit conversion will produce a compiler error. Always
use TRUNC or the appropriate TYPE_TO_TYPE function when
narrowing:
VAR
big : DINT := 1000;
small : BYTE;
END_VAR;
// Compiler error: cannot implicitly narrow DINT to BYTE
// small := big;
// Correct: explicit truncation
small := TRUNC(big, BYTE); // small = 232 (1000 AND 0xFF)
Conversion Quick Reference
Use this guide to choose the right conversion for your situation:
| Situation | Function | Why |
|---|---|---|
| Unsigned byte → larger signed integer | Z_EXT(val, DINT) |
Preserve unsigned value (no sign bit to extend) |
| Signed byte → larger signed integer | S_EXT(val, DINT) |
Preserve negative values (extend sign bit) |
| Large integer → byte | TRUNC(val, BYTE) |
Keep only the low 8 bits |
| Integer → float | SINT_TO_FP(val, REAL) |
Enable precise arithmetic / division |
| Float → integer | FP_TO_SINT(val, DINT) |
Convert result back (truncates toward zero) |
| Reinterpret sign | UINT_TO_SINT(val) |
Same bits, different sign interpretation |
| Any value → BOOL | DINT_TO_BOOL(val) |
Non-zero = TRUE, zero = FALSE |
S_EXT or
Z_EXT.