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
INTINT_TO_DINTINT_TO_REALINT_TO_STRINGINT_TO_BOOLINT_TO_BYTEINT_TO_WORDINT_TO_DWORD
DINTDINT_TO_INTDINT_TO_REALDINT_TO_STRINGDINT_TO_BYTEDINT_TO_WORD
REALREAL_TO_INTREAL_TO_DINTREAL_TO_STRINGREAL_TO_BYTEREAL_TO_WORDREAL_TO_DWORD
STRINGSTRING_TO_INTSTRING_TO_DINTSTRING_TO_REALSTRING_TO_BOOLSTRING_TO_BYTESTRING_TO_WORDSTRING_TO_DWORD
BOOLBOOL_TO_INTBOOL_TO_DINTBOOL_TO_STRINGBOOL_TO_BYTEBOOL_TO_WORDBOOL_TO_DWORD
BYTEBYTE_TO_INTBYTE_TO_REALBYTE_TO_STRINGBYTE_TO_WORDBYTE_TO_DWORD
WORDWORD_TO_INTWORD_TO_REALWORD_TO_STRINGWORD_TO_BYTEWORD_TO_DWORD
DWORDDWORD_TO_INTDWORD_TO_DINTDWORD_TO_REALDWORD_TO_STRINGDWORD_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