Skip to main content

ControlForge Math, String & Conversion Reference

James M. Belcher Founder, JMB Technical Services LLC June 2026 | ControlForge v1.0.1064


1. Math & Trigonometry​

Basic Math​

FunctionSignatureReturnsDescription
ABS(x)REAL/INTAbsolute value
SIGN(x)INT-1, 0, or 1
SQRT(x)REALSquare root
POW(base, exp)REALPower (base^exp)
EXPT(base, exp)REALSame as POW
EXP(x)REALe^x
LN(x)REALNatural logarithm
LOG(x)REALBase-10 logarithm
FMOD(x, y)REALFloating-point modulus
distance := SQRT(POW(x2 - x1, 2) + POW(y2 - y1, 2));
gain_db := 20.0 * LOG(vout / vin);

Rounding​

FunctionSignatureReturnsDescription
CEIL(x)REALRound up to integer
FLOOR(x)REALRound down to integer
ROUND(x)REALRound to nearest integer
TRUNC(x)REALTruncate toward zero
pages := CEIL(total_items / 10.0); (* 25 items → 3 pages *)
whole := TRUNC(3.7); (* 3.0 *)

Trigonometry​

All angles in radians.

FunctionSignatureReturnsDescription
SIN(rad)REALSine
COS(rad)REALCosine
TAN(rad)REALTangent
ASIN(x)REALArc sine (returns radians)
ACOS(x)REALArc cosine
ATAN(x)REALArc tangent
ATAN2(y, x)REALTwo-argument arc tangent
DEG_TO_RAD(deg)REALDegrees → radians
RAD_TO_DEG(rad)REALRadians → degrees
angle_rad := DEG_TO_RAD(45.0);
x := COS(angle_rad) * radius;
y := SIN(angle_rad) * radius;
heading := RAD_TO_DEG(ATAN2(dy, dx));

2. Selection & Comparison​

FunctionSignatureReturnsDescription
MIN(a, b)ANYSmaller of two values
MAX(a, b)ANYLarger of two values
LIMIT(min, value, max)ANYClamp value to range
CLAMP(value, min, max)ANYSame as LIMIT (different arg order)
SEL(condition, false_val, true_val)ANYConditional select (like ternary)
MUX(index, val0, val1, val2, ...)ANYIndexed multiplexer
(* Clamp output to 0-100% *)
output := LIMIT(0.0, pid_output, 100.0);

(* Select based on condition *)
mode_name := SEL(auto_mode, 'MANUAL', 'AUTO');

(* Multiplexer — select by index *)
recipe_temp := MUX(recipe_id, 150.0, 180.0, 200.0, 220.0);

3. Statistics​

FunctionSignatureReturnsDescription
MEAN(array)REALArithmetic mean
MEDIAN(array)REALMiddle value
VARIANCE(array)REALPopulation variance
STDDEV(array)REALStandard deviation
PERCENTILE(array, pct)REALNth percentile (0-100)
MOVING_AVG(array, window)ARRAYSimple moving average of a series
SMA(array, window)ARRAYAlias of MOVING_AVG
EMA(array, alpha)ARRAYExponential moving average of a series

These smooth a whole array and keep no state between scans. There is no handle-based streaming filter — for a live scalar, hold the previous value yourself (see §below). A stateful filter family is on the deferred list in TODO.md.

temps := ARRAY_CREATE(68.2, 72.1, 71.5, 73.0, 69.8);
avg := MEAN(temps); (* 70.92 *)
med := MEDIAN(temps); (* 71.5 *)
sd := STDDEV(temps); (* ~1.8 *)
p95 := PERCENTILE(temps, 95); (* ~72.8 *)

(* Smoothing operates on an ARRAY and returns the smoothed series — these are
not named streaming filters, so they hold no state between scans. Keep your
own history buffer and pass it in. *)
smooth_series := MOVING_AVG(temps, 3); (* simple moving average, window 3 *)
filtered := EMA(temps, 0.1); (* alpha 0.1 = heavy smoothing *)

4. Interpolation & Scaling​

FunctionSignatureReturnsDescription
LERP(a, b, t)REALLinear interpolation (t=0→a, t=1→b)
MAP(raw, raw_min, raw_max, eu_min, eu_max)REALMap range to range
RANDOM()REALRandom 0.0–1.0
RAND()REALAlias for RANDOM
RANDOM_RANGE(min, max)REALRandom in range
(* Scale 0-4095 ADC to 0-100 PSI *)
pressure_psi := MAP(adc_raw, 0, 4095, 0.0, 100.0);

(* Fade between two colors over 100 steps *)
brightness := LERP(0.0, 100.0, INT_TO_REAL(step) / 100.0);

(* Simulate sensor noise *)
noisy := actual_temp + (RANDOM_RANGE(-0.5, 0.5));

5. Unit Conversions​

FunctionSignatureReturnsDescription
C_TO_F(celsius)REALCelsius → Fahrenheit
F_TO_C(fahrenheit)REALFahrenheit → Celsius
C_TO_K(celsius)REALCelsius → Kelvin
K_TO_C(kelvin)REALKelvin → Celsius
KMH_TO_MS(kmh)REALkm/h → m/s
MS_TO_KMH(ms)REALm/s → km/h
DEG_TO_RAD(degrees)REALDegrees → radians
RAD_TO_DEG(radians)REALRadians → degrees

6. String Functions​

Length & Access​

FunctionSignatureReturnsDescription
LEN(str)INTString length
LEFT(str, count)STRINGFirst N characters
RIGHT(str, count)STRINGLast N characters
MID(str, start, count)STRINGSubstring (1-based start)
CHR(code)STRINGASCII code → character
ORD(str)INTFirst character → ASCII code
name := 'ControlForge-Plant1';
prefix := LEFT(name, 5); (* "ControlForge" *)
suffix := RIGHT(name, 6); (* "Plant1" *)
mid := MID(name, 7, 6); (* "Plant1" *)
newline := CHR(10); (* \n *)
FunctionSignatureReturnsDescription
FIND(str, search)INTPosition of substring (0 = not found)
CONTAINS(str, search)BOOLSubstring exists?
STARTS_WITH(str, prefix)BOOLStarts with prefix?
ENDS_WITH(str, suffix)BOOLEnds with suffix?
IF CONTAINS(alarm_text, 'HIGH') THEN
severity := 3;
END_IF;

Modify​

FunctionSignatureReturnsDescription
CONCAT(str1, str2, ...)STRINGJoin strings (variadic)
REPLACE(str, search, replacement)STRINGReplace all occurrences
INSERT(str, position, insert_str)STRINGInsert at position
DELETE(str, position, count)STRINGDelete characters
UPPER(str)STRINGUppercase
LOWER(str)STRINGLowercase
TRIM(str)STRINGStrip whitespace both ends
LTRIM(str)STRINGStrip leading whitespace
RTRIM(str)STRINGStrip trailing whitespace
REVERSE(str)STRINGReverse string
REPEAT(str, count)STRINGRepeat N times
PAD_LEFT(str, width, pad_char)STRINGLeft-pad to width
PAD_RIGHT(str, width, pad_char)STRINGRight-pad to width
SPLIT(str, delimiter)ARRAYSplit into array
FORMAT(template, args...)STRINGPrintf-style formatting
msg := CONCAT('Temperature: ', REAL_TO_STRING(temp), ' F');
csv := REPLACE(raw_data, ';', ',');
parts := SPLIT('10.0.0.50:502', ':'); (* ["10.0.0.50", "502"] *)
padded := PAD_LEFT(INT_TO_STRING(batch), 6, '0'); (* "000042" *)
line := FORMAT('%s,%d,%.2f', tag_name, count, value);

7. Type Conversions​

Numeric​

From \ ToINTDINTREALSTRINGBOOLBYTEWORDDWORD
INT—INT_TO_DINTINT_TO_REALINT_TO_STRINGINT_TO_BOOLINT_TO_BYTEINT_TO_WORDINT_TO_DWORD
DINTDINT_TO_INT—DINT_TO_REALDINT_TO_STRING—DINT_TO_BYTEDINT_TO_WORD—
REALREAL_TO_INTREAL_TO_DINT—REAL_TO_STRING—REAL_TO_BYTEREAL_TO_WORDREAL_TO_DWORD
STRINGSTRING_TO_INTSTRING_TO_DINTSTRING_TO_REAL—STRING_TO_BOOLSTRING_TO_BYTESTRING_TO_WORDSTRING_TO_DWORD
BOOLBOOL_TO_INTBOOL_TO_DINT—BOOL_TO_STRING—BOOL_TO_BYTEBOOL_TO_WORDBOOL_TO_DWORD
BYTEBYTE_TO_INT—BYTE_TO_REALBYTE_TO_STRING——BYTE_TO_WORDBYTE_TO_DWORD
WORDWORD_TO_INT—WORD_TO_REALWORD_TO_STRING—WORD_TO_BYTE—WORD_TO_DWORD
DWORDDWORD_TO_INTDWORD_TO_DINTDWORD_TO_REALDWORD_TO_STRING—DWORD_TO_BYTEDWORD_TO_WORD—

Extended Integer Types​

FunctionDescription
UINT_TO_INT, INT_TO_UINTUnsigned ↔ signed 16-bit
UDINT_TO_DINT, DINT_TO_UDINTUnsigned ↔ signed 32-bit
LINT_TO_INT, INT_TO_LINT64-bit ↔ 32-bit
ULINT_TO_UINT, UINT_TO_ULINTUnsigned 64-bit ↔ 16-bit
SINT_TO_STRING, USINT_TO_STRINGShort int → string

Date & Time​

FunctionDescription
TIME_TO_STRING, STRING_TO_TIMETIME ↔ STRING
TIME_TO_DINT, DINT_TO_TIMETIME ↔ milliseconds
TIME_TO_REAL, REAL_TO_TIMETIME ↔ seconds (float)
DATE_TO_STRING, STRING_TO_DATEDATE ↔ STRING
DT_TO_STRING, STRING_TO_DTDATE_TIME ↔ STRING
DT_TO_DATE, DATE_TO_DTExtract/build date portion
DT_TO_TOD, TOD_TO_TIMEExtract/build time-of-day
DATE_TO_DWORD, DWORD_TO_DATEDATE ↔ binary
DT_TO_DWORD, DWORD_TO_DTDATE_TIME ↔ binary
TOD_TO_DINT, DINT_TO_TODTime-of-day ↔ integer

Number Base​

FunctionDescription
INT_TO_HEX, HEX_TO_INTInteger ↔ hex string
DINT_TO_HEX, HEX_TO_DINT32-bit ↔ hex string
INT_TO_BIN, BIN_TO_INTInteger ↔ binary string
DINT_TO_BIN, BIN_TO_DINT32-bit ↔ binary string
INT_TO_OCT, OCT_TO_INTInteger ↔ octal string
DINT_TO_OCT, OCT_TO_DINT32-bit ↔ octal string
hex := DINT_TO_HEX(255); (* "FF" *)
val := HEX_TO_INT('1A'); (* 26 *)

8. Bitwise Operations​

OperatorSyntaxDescription
ANDa AND bBitwise AND
ORa OR bBitwise OR
XORa XOR bBitwise XOR
NOTNOT aBitwise NOT
SHLSHL(value, bits)Shift left
SHRSHR(value, bits)Shift right
ROLROL(value, bits)Rotate left
RORROR(value, bits)Rotate right
SET_BITSET_BIT(value, bit)Set bit N
(* Extract bits from a status word *)
motor_running := (status_word AND 16#0001) > 0; (* Bit 0 *)
fault_active := (status_word AND 16#0002) > 0; (* Bit 1 *)

(* Build a command word *)
cmd := 0;
IF start THEN cmd := cmd OR 16#0001; END_IF;
IF forward THEN cmd := cmd OR 16#0004; END_IF;

9. Complete Example: Sensor Signal Processing​

PROGRAM POU_SignalProcessing
VAR
(* Raw inputs *)
adc_raw : INT; (* 0-4095 from ADC *)
pressure_raw : REAL;

(* Processed outputs *)
pressure_psi : REAL;
temp_f : REAL;
temp_c : REAL;
filtered_pressure : REAL;
alarm_active : BOOL;

(* Statistics *)
pressure_avg : REAL;
pressure_sd : REAL;
samples : STRING; (* ARRAY handle *)
sample_count : INT := 0;
END_VAR

(* Scale ADC to engineering units *)
pressure_psi := MAP(INT_TO_REAL(adc_raw), 0.0, 4095.0, 0.0, 100.0);

(* Temperature conversion *)
temp_c := 25.0;
temp_f := C_TO_F(temp_c);

(* Exponential smoothing of a live scalar. EMA() smooths an ARRAY and holds no
state between scans, so a per-scan filter is one line of ST: *)
filtered_pressure := filtered_pressure + 0.15 * (pressure_psi - filtered_pressure);

(* Hysteresis on alarm — prevents chatter near threshold *)
alarm_active := HYSTERESIS(filtered_pressure, 85.0, 90.0, alarm_active);

(* Clamp output to valid range *)
pressure_psi := CLAMP(pressure_psi, 0.0, 100.0);

(* Collect samples for statistics *)
sample_count := sample_count + 1;
IF sample_count = 1 THEN
samples := ARRAY_CREATE(filtered_pressure);
ELSE
samples := ARRAY_APPEND(samples, filtered_pressure);
IF ARRAY_LENGTH(samples) > 100 THEN
samples := ARRAY_SLICE(samples, 1, 100); (* Keep last 100 *)
END_IF;
END_IF;

IF ARRAY_LENGTH(samples) >= 10 THEN
pressure_avg := MEAN(samples);
pressure_sd := STDDEV(samples);
END_IF;

(* Format for display *)
display_text := FORMAT('Pressure: %.1f PSI (avg: %.1f, sd: %.2f)',
filtered_pressure, pressure_avg, pressure_sd);

END_PROGRAM

ControlForge v1.0.1064 | Math, String & Type Conversion Reference | IEC 61131-3 + Extensions

© 2026 JMB Technical Services LLC. All rights reserved. Back to All Guides