Expressions in Ladder

How to write an expression inside a ladder box with CPT and CMP — operator order, the REAL promotion rule, and the operators that do not behave the way maths does.

Most ladder instructions take tags: ADD(Speed, Offset, Result). Two do not. CPT and CMP take a whole expression — a piece of maths — in one box, so you can write

CPT(Result, (Speed * 60) / PulsesPerRev)

instead of chaining a MUL box into a DIV box through a scratch tag.

  • CPT (Compute) works out the expression and stores the answer in a tag. It is an output instruction, and it runs on every scan the rung is true, not once per rising edge.
  • CMP (Compare) works out the expression and uses the answer as the rung condition. It is an input instruction, so it sits on the left of the rung like a contact.

This page is about the rules expressions follow, because a few of them are not what you would guess.

What you can put in an expression

CPTCMP
+ - * / MOD, brackets, negative sign
** (raise to a power)
AND OR XOR NOTbitwise, see below
ABS SQRT TRUNC LN LOG DEG RAD SIN COS TAN ASIN ACOS ATAN ATAN2
< <= > >= = <> — comparisons
&& || ^^ !logical, see below
IsINF IsNAN — test a REAL for infinity or "not a number"

CPT has no comparisons because a comparison is not a number you can store. CMP has everything, because its answer is a yes/no.

Operator order

Brackets first, then down this list. Operators on the same line run left to right.

OrderOperators
1( )
2functions: ABS SQRT TRUNC LN LOG DEG RAD SIN COS TAN ASIN ACOS ATAN ATAN2, and IsINF IsNAN in CMP
3**
4- (negative sign), NOT, !
5* / MOD
6+ -
7AND
8XOR
9OR
10< <= > >= = <>
11&&
12^^
13||

Three rows of that table are traps. Here they are.

-2**2 is -4, not 4

** is on row 3 and the negative sign is on row 4, so the power runs first and the minus sign is applied to the answer.

You writeIt meansAnswer
-2**2-(2**2)-4
(-2)**2(-2) * (-2)4

If you want the sign to be part of the number being squared, put brackets round it.

2**3**2 is 64, not 512

Two ** in a row run left to right, like - and / do. Maths and most programming languages go right to left here, so this is the one place a ladder expression disagrees with them.

You writeIt meansAnswer
2**3**2(2**3)**2 = 8**264
2**(3**2)2**9512

AND is bitwise, && means "both"

AND, OR, XOR and NOT work on the bits inside a number, one bit at a time. They do not mean "both of these are true".

With Count = 2 (binary 10) and the number 1 (binary 01):

ExpressionWhat happensAnswer
Count AND 110 AND 01 — no bit is 1 in both0, so the rung is off
(Count > 0) && (1 > 0)both comparisons are truerung is on

To join two comparisons you must use && ("both"), \|\| ("either") or ^^ ("exactly one"), and to flip a comparison you use !. Writing Count > 1 AND Limit < 5 in a ladder box is an error, not a slow way of getting the right answer — Studio will tell you to use &&.

This is the one rule that differs from Structured Text, where AND between two comparisons does mean "both". Inside a ladder box, keep AND for bits and && for conditions.

There is a knock-on worth knowing if you write both. In Structured Text, that logical AND also splits the decimal decision below in two, so each side decides for itself. In a ladder box, && splits nothing — one decimal anywhere still reaches everything.

CMP is true for any value that is not zero

CMP does not need a comparison in it. It takes whatever number the expression produces and treats any non-zero value as true — negative numbers included.

CMP(…)Rung
CMP(5)on
CMP(0)off
CMP(-1)on
CMP(Count) with Count = -1on
CMP(2.5 - 2.5)off — the answer is zero

So CMP(Count) asks "is Count anything other than zero?", which is often what you want and occasionally not what you meant.

Whole numbers and decimals in the same expression

Studio has two kinds of number: DINT (a whole number) and REAL (a number with a decimal part). Which one an expression uses changes the answer, because dividing two whole numbers throws the fraction away.

The rule is decided once, for the whole expression, before anything is worked out. Every step of the expression uses decimals if any one of these is true:

  • any value in it is a REAL — a tag, or a number written with a decimal point like 1.5
  • it calls SIN COS TAN ASIN ACOS ATAN LN LOG DEG or RAD
  • the tag you are storing into is a REAL

Otherwise every step stays whole.

That list of functions is exact. SQRT, TRUNC and ABS are not on it, even though the first two hand back a decimal — 7/2 + SQRT(4) is 5, because nothing in it is decimal.

"Whole expression" really does mean the whole thing. It reaches through brackets, through a comparison, and in and out of a function's brackets: in TRUNC(10/4*4) * 1.5 the 1.5 at the end changes how 10/4 inside the brackets is worked out.

FormulaStored inWorked out asAnswer
7/2DINTwhole: 7/2 = 33
7/2REALdecimal, because the destination is REAL3.5
10/4*4DINTwhole: 10/4 = 2, then × 48
7/2*1.5DINTdecimal, because 1.5 is there: 3.5 × 1.5 = 5.255

That last row is the one to remember. The 1.5 sits at the end of the expression, but it still changes how 7/2 at the start is worked out. One decimal anywhere makes the whole expression decimal.

Storing a decimal answer into a DINT rounds it — and 5.25 rounds down to 5.

Storing into a whole number rounds to even

When a decimal answer lands in a DINT, INT or SINT tag it is rounded to the nearest whole number. Exact halves round to the nearest even number, so 0.5 becomes 0 and 1.5 becomes 2. This is the same rounding MOVE and ADD use, and it stops a long run of halves drifting upwards.

Dividing by zero

Dividing by zero in an expression does not stop the controller. The answer is the number you were dividing — 7 / 0 gives 7 — and the scan carries on.

The exception is dividing by zero inside square brackets, where you are working out which element of an array to use. There is no sensible element number to fall back on, so that does stop the scan. Check the divisor before you use it as an index.

Decimals are stored to about 7 digits

A REAL keeps roughly seven significant digits, so some answers are very slightly off and a few surprising things come out true:

  • 0.1 + 0.2 = 0.3 is true, because both sides land on the same stored value
  • LN(2.718281828) gives 0.99999994, not exactly 1

Never test two decimals for exact equality if either one came out of a calculation. Compare against a range instead — ABS(Measured - Target) < 0.01.

Where to go next

On this page