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
CPT | CMP | |
|---|---|---|
+ - * / MOD, brackets, negative sign | ✓ | ✓ |
** (raise to a power) | ✓ | ✓ |
AND OR XOR NOT — bitwise, 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.
| Order | Operators |
|---|---|
| 1 | ( ) |
| 2 | functions: 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 | + - |
| 7 | AND |
| 8 | XOR |
| 9 | OR |
| 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 write | It means | Answer |
|---|---|---|
-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 write | It means | Answer |
|---|---|---|
2**3**2 | (2**3)**2 = 8**2 | 64 |
2**(3**2) | 2**9 | 512 |
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):
| Expression | What happens | Answer |
|---|---|---|
Count AND 1 | 10 AND 01 — no bit is 1 in both | 0, so the rung is off |
(Count > 0) && (1 > 0) | both comparisons are true | rung 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 = -1 | on |
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 like1.5 - it calls
SINCOSTANASINACOSATANLNLOGDEGorRAD - 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.
| Formula | Stored in | Worked out as | Answer |
|---|---|---|---|
7/2 | DINT | whole: 7/2 = 3 | 3 |
7/2 | REAL | decimal, because the destination is REAL | 3.5 |
10/4*4 | DINT | whole: 10/4 = 2, then × 4 | 8 |
7/2*1.5 | DINT | decimal, because 1.5 is there: 3.5 × 1.5 = 5.25 | 5 |
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.3is true, because both sides land on the same stored valueLN(2.718281828)gives0.99999994, not exactly1
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.