Arithmetic and Comparison Operators
Reference for arithmetic and comparison operators in Structured Text on rungs.dev, including built-in math functions and DINT/REAL rounding rules.
Most logic eventually comes down to math: counting, scaling, comparing. This page covers the operators you'll use, the built-in math functions, and the rounding rules you need to know when integers and decimals meet.
Modeled on Studio 5000 Logix Designer
rungs.dev models its math behavior on Studio 5000 Logix Designer® — division, rounding, and type coercion follow the same conventions. Rare edge cases may differ.
Arithmetic Operators
| Operator | Description | Example |
|---|---|---|
| + | Addition | Total := A + B; |
| - | Subtraction | Delta := A - B; |
| * | Multiplication | Product := A * 5; |
| / | Division | Average := Sum / Count; |
| MOD | Remainder | Remainder := A MOD B; |
| ** | Raise to a power | Squared := Value ** 2; |
Use parentheses to control the order, just like in regular math:
Result := (Value1 + Value2) * 3;The power runs before a minus sign, and two powers run left to right
Both are worth committing to memory, because brackets are the only way to get the other reading:
Result := -2 ** 2; // -4, because it means -(2 ** 2)
Result := (-2) ** 2; // 4 — brackets make the sign part of the number
Result := 2 ** 3 ** 2; // 64, because it means (2 ** 3) ** 2
Result := 2 ** (3 ** 2); // 512 — what maths would give youComparison Operators
Comparison operators ask a true/false question. They show up most often inside an IF.
| Operator | Description | Example |
|---|---|---|
= | Equal to | IF Mode = 1 THEN |
<> | Not equal to | IF State <> 0 THEN |
< | Less than | IF Temp < 100 THEN |
> | Greater than | IF Speed > Limit THEN |
<= | Less than or equal | IF Level <= 50 THEN |
>= | Greater or equal | IF Count >= Max THEN |
Comparing BOOLs
Look closely at the table — every example compares numbers. That's the rule: comparison operators only work on numeric values. Comparing two BOOL tags with = or <> is a compile error.
For bits, the question "are these two different?" already has an operator — XOR:
IF In_A <> In_B THEN // ✗ compile error — BOOLs can't use comparison operators
IF In_A XOR In_B THEN // ✓ true when the two bits differ
IF NOT (In_A XOR In_B) THEN // ✓ true when they matchDivision: integers vs decimals
Division is the one operator whose result depends on what you're dividing into. The destination tag's type decides whether you keep the fraction or lose it.
Try this in your head: 7 / 2. The exact answer is 3.5. What you get in the tag depends on where you're putting it:
DintResult := 7 / 2; // DintResult = 3 — integer truncation
RealResult := 7 / 2; // RealResult = 3.5 — fraction preserved
DintResult := 7.0 / 2; // DintResult = 4 — see "Banker rounding" belowThe full rule:
| Left operand | Right operand | Destination | What you get |
|---|---|---|---|
DINT | DINT | DINT | Integer division (truncates to zero) |
DINT | DINT | REAL | Float division (fraction preserved) |
any REAL | any | DINT | Float division, then banker rounding |
any REAL | any | REAL | Float division |
A small detail worth knowing: integer division always truncates toward zero, not down. So -7 / 2 is -3, not -4.
One decimal changes the whole expression
The choice between whole-number and decimal maths is made once, for the whole expression, before anything is worked out — not step by step. If a decimal appears anywhere in it, every step uses decimals, even the ones written before it:
DintResult := 7 / 2 * 1.5; // 5 — the 1.5 makes 7 / 2 a decimal divide: 3.5 * 1.5 = 5.25
DintResult := 10 / 4 * 4; // 8 — no decimal anywhere: 10 / 4 is 2, then × 4That first line is the one to remember. The 1.5 is at the far end of the expression and it
still changes how 7 / 2 at the start is worked out.
It holds even where the decimal is hidden inside something that answers a whole number. The bitwise operators always answer a whole number, but a decimal inside one still switches the rest of the expression to decimals:
DintResult := 7 / 2 + (1.5 AND 1); // 4Working that through: the 1.5 becomes 2 before AND can use it, and 2 AND 1 is 0. But
its presence made 7 / 2 a decimal divide, so the sum is 3.5 + 0 = 3.5, which rounds to
4. Without the 1.5 in there, the same shape would give 3.
The three things that switch an expression to decimals, then, are: a REAL value anywhere in
it, a call to SIN, COS, TAN, ASIN, ACOS, ATAN, LN, LOG, DEG or RAD, or a
REAL destination.
That list of functions is exact. SQRT, TRUNC and ABS are not on it, even though the
first two hand back a decimal:
DintResult := 7 / 2 + SIN(0.0); // 4 — SIN switches the whole thing to decimals: 3.5 + 0.0
DintResult := 7 / 2 + SQRT(4.0); // 6 — the 4.0 did that, not SQRT
DintResult := 7 / 2 + SQRT(4); // 5 — nothing decimal anywhere: 3 + 2One exception: AND, OR and XOR between two comparisons
Everything above holds right across an expression — through brackets, through a comparison,
even inside a function's brackets. The single exception is a logical AND, OR or XOR
joining two comparisons. Those split the expression in two, and each side decides for itself:
IF (Ratio > 1.0) AND (Count / Divisor > 3) THEN // Count / Divisor stays wholeThe Ratio on the left does not reach the division on the right. Note this is the logical use
of AND — joining two comparisons. The bitwise use, on two numbers, splits nothing, which is
the (1.5 AND 1) example above.
Banker rounding (REAL into DINT)
When you put a decimal value into a DINT, Studio uses banker rounding — also called round half to even.
You probably learned in school: round halves up (0.5 → 1, 1.5 → 2, 2.5 → 3, 3.5 → 4). Banker rounding does something slightly different — when the value is exactly halfway, it rounds to the nearest even number:
| Value | School rounding | Banker rounding |
|---|---|---|
0.5 | 1 | 0 |
1.5 | 2 | 2 |
2.5 | 3 | 2 |
3.5 | 4 | 4 |
-2.5 | -2 | -2 |
-3.5 | -3 | -4 |
For values that aren't exactly halfway, banker rounding works the same as normal rounding: 1.4 → 1, 1.6 → 2.
Why use it? Because rounding all halves up adds a small bias: if you round many measurements over and over, the total drifts a little high. Banker rounding splits halves evenly between rounding up and rounding down, so the bias cancels out. Industrial controllers and financial systems use it for the same reason.
DintSetpoint := RealSetpoint; // banker rounding applied automaticallyDivision by zero
Dividing by zero is normally an error. Studio handles it without crashing the program. The result depends on the operand types and the destination type:
| What you wrote | What you get |
|---|---|
DintDest := 5 / 0; | DintDest = 5 — Source A copied (DINT/DINT) |
DintDest := 0 / 0; | DintDest = 0 — Source A copied |
DintDest := 5.0 / 0.0; | DintDest = 2147483647 — +Infinity clamps to MAX_DINT |
DintDest := -5.0 / 0.0; | DintDest = -2147483648 — -Infinity clamps to MIN_DINT |
DintDest := 0.0 / 0.0; | DintDest = 0 — NaN becomes 0 |
RealDest := 5.0 / 0.0; | RealDest = +Infinity |
RealDest := 0.0 / 0.0; | RealDest = NaN ("Not a Number") |
RealDest := 5 / 0; | RealDest = +Infinity — see the note below |
For a DINT destination, all-DINT division copies the left operand (Source A); when any operand is REAL, the float result is clamped into the DINT range so you never see Infinity in an integer tag. A REAL destination keeps the raw +Infinity, -Infinity, or NaN value. The program keeps running in every case in this table.
Watch the last row. 5 / 0 looks like whole-number division, but the destination type decides the kind of maths, not just the operands. Store into a DINT and you get integer division, which copies Source A and gives you 5. Store the very same expression into a REAL and it becomes float division, which gives you +Infinity. The text you wrote is identical; only the tag on the left changed.
One place it does stop the program
Everything above is about division in ordinary maths. Divide by zero inside an array index and the controller stops the scan instead:
Buffer[Count / 0] := 1; // the scan stops hereThere is no sensible element to reach, so nothing is written and the rest of the routine never runs. Check the divisor before using it as an index:
IF Divisor <> 0 THEN
Buffer[Count / Divisor] := 1;
END_IF;If you want to handle a possible zero divisor explicitly, check for it first:
IF Divisor <> 0 THEN
Result := Numerator / Divisor;
ELSE
Result := 0; // or whatever fallback makes sense
END_IF;MOD mostly follows the same rules — the result's sign follows the left operand
(-7 MOD 2 is -1) and REAL operands keep the fraction (7.5 MOD 2.0 is 1.5) —
but MOD by zero differs from division by zero: integer x MOD 0 writes 0
(division copies the left operand), and REAL x MOD 0.0 gives Infinity with
the sign of x.
Built-in math functions
These are the math functions you can call from any expression:
| Function | What it does |
|---|---|
ABS(x) | Absolute value (drops the minus sign). |
SQRT(x) | Square root. Negative inputs become SQRT(ABS(x)). |
SIN(x) | Sine of x, where x is in radians. |
COS(x) | Cosine of x, in radians. |
TAN(x) | Tangent of x, in radians. |
ASIN(x) | The angle in radians whose sine is x. |
ACOS(x) | The angle in radians whose cosine is x. |
ATAN(x) | The angle in radians whose tangent is x. |
ATAN2(y, x) | The angle in radians from the X axis to the point (x, y). Both signs are used, so it can tell one quadrant from another. |
LN(x) | Natural logarithm (base e). |
LOG(x) | Logarithm base 10. |
DEG(x) | Converts radians to degrees. |
RAD(x) | Converts degrees to radians. |
TRUNC(x) | Drops the fractional part, rounding toward zero. |
DintDest := ABS(-7); // 7
RealDest := SQRT(-1.0); // 1.0 — never NaN, never an error
RealDest := SIN(0.0); // 0.0
RealDest := DEG(3.14159274); // 180.0
DintDest := TRUNC(-2.7); // -2, not -3: it rounds toward zeroTRUNC is not the same as storing a REAL in a DINT. Storing rounds to the nearest whole
number; TRUNC always heads toward zero:
DintDest := 2.7; // 3 — rounded
DintDest := TRUNC(2.7); // 2 — truncated
DintDest := -2.7; // -3 — rounded
DintDest := TRUNC(-2.7); // -2 — truncatedLogarithms and the trigonometric functions answer a REAL, which keeps about seven digits, so
an answer you would expect to be exact often is not. LN(2.718281828) gives 0.99999994, not
1.0. Never compare one for exact equality — compare against a range instead.
There is no MIN or MAX function. Use IF/ELSE:
// "Smaller" = MIN(A, B)
IF A < B THEN
Smaller := A;
ELSE
Smaller := B;
END_IF;Numeric literals
Numbers you type directly into your code are called literals.
| Form | Type | Example |
|---|---|---|
42 | DINT | Counter := 42; |
-42 | DINT | Offset := -42; |
3.14 | REAL | Pi := 3.14; |
1.0e3 | REAL | Scale := 1.0e3; |
2.5E-2 | REAL | Gain := 2.5E-2; |
16#00FF | DINT | Mask := 16#00FF; |
2#1010 | DINT | Pattern := 2#1010; |
8#377 | DINT | Legacy := 8#377; |
Scientific notation must include a decimal point: write 1.0e3, not 1e3. The decimal point is what tells Studio you mean a REAL value.
The 16# (hexadecimal), 2# (binary), and 8# (octal) forms are radix literals — they
spell out a DINT's bit pattern instead of its decimal value. Underscores may group digits
for readability: 2#1010_1010 is the same value as 170. See
Writing DINT literals for the details.
Putting it together
// Add two values and scale the result
Result := (Value1 + Value2) * 3;
// Trigger an alarm if temperature exceeds 100
IF Temperature > 100 THEN
Alarm := 1;
END_IF;
// Convert raw ADC count (0-4095) into a percentage (0-100)
// REAL destination keeps the fraction — RawCount = 2047 gives ~49.988
ScaledReal := (RawCount * 100) / 4095;
// Same expression into a DINT — RawCount = 2047 gives 49 (truncated, not rounded)
DintScaled := (RawCount * 100) / 4095;
// Round a REAL setpoint to the nearest DINT (banker rounding applies)
DintSetpoint := RealSetpoint;Practice
Try the arithmetic in the Scale exercise — convert a 4–20 mA reading into 0–100 °C — then the comparisons in the Sign Check exercise — flag a number as negative, zero, or positive.
Variables and Assignment
Declare and assign values to variables in Structured Text on rungs.dev. Learn naming rules, := assignment semantics, and how variables map to PLC tags during execution.
Logical Operators
Logical operators AND, OR, XOR, and NOT combine BOOL expressions in Structured Text on rungs.dev, and double as bitwise operators on numeric values for masking and flag manipulation.