Mathematical constants
Why this page exists
IEC 61131-3 §2.5.1 specifies mathematical functions — SIN, COS, SQRT,
LN, EXP, and friends — but does not specify any constants. PLC code
that needs π or e therefore depends on whichever vendor library defines
them, and the libraries disagree:
| Source | Where PI comes from | Notes |
|---|---|---|
| Common commercial PLC stdlib | global VAR CONSTANT | Just PI (LREAL). No E, no SQRT2. |
| Open-source IEC extension library | dedicated _BASIC lib | PI, EULER, GOLDEN_RATIO, more. |
| Vendor-specific math library | global constants | Often two-precision form, e.g. LREAL_PI / REAL_PI. |
| Bare IEC 61131-3 (matiec / Beremiz) | nothing | You must declare them yourself. |
| ForgeIEC | forgeiec_math lib | All ten constants below; opt-in. |
ST code that should run on more than one of these must be explicit about where its constants come from.
Available constants in forgeiec_math
When you add forgeiec_math to your project’s library list, the following
names become available everywhere in ST code:
| Symbol | Approximate value | Type |
|---|---|---|
PI | 3.141592653589793 | LREAL |
TWO_PI | 6.283185307179586 | LREAL |
PI_HALF | 1.570796326794897 | LREAL |
E | 2.718281828459045 | LREAL |
SQRT2 | 1.414213562373095 | LREAL |
SQRT1_2 | 0.707106781186548 | LREAL |
LN2 | 0.693147180559945 | LREAL |
LN10 | 2.302585092994046 | LREAL |
GOLDEN_RATIO | 1.618033988749895 | LREAL |
EULER_MASCHERONI | 0.577215664901533 | LREAL |
All values are stored at the highest precision LREAL allows. You can assign them straight into a REAL variable and the compiler does the narrowing for you:
VAR
rPiR : REAL := PI; (* narrows to REAL precision *)
rPiLR : LREAL := PI; (* full LREAL precision *)
END_VAR
Common patterns
Circle area
FUNCTION rArea : LREAL
VAR_INPUT rRadius : LREAL; END_VAR
rArea := PI * rRadius * rRadius;
END_FUNCTION
Sine wave with stable phase
VAR
rPhase : LREAL := 0.0;
rFreq : LREAL := 50.0; (* Hz *)
rDt : LREAL := 0.001; (* 1 ms cycle *)
rOut : LREAL;
END_VAR
(* Use TWO_PI directly — avoids one ULP of rounding per cycle. *)
(* That difference accumulates over thousands of cycles and *)
(* shows up as visible drift in long-running control loops. *)
rPhase := rPhase + TWO_PI * rFreq * rDt;
IF rPhase >= TWO_PI THEN
rPhase := rPhase - TWO_PI;
END_IF;
rOut := SIN(rPhase);
Degree-to-radian conversion
There is no DEG_TO_RAD function — write it by hand:
FUNCTION rRad : LREAL
VAR_INPUT rDeg : LREAL; END_VAR
rRad := rDeg * PI / 180.0;
END_FUNCTION
Logarithmic compression
(* dB = 20 · log10(amplitude) — use LN and LN10 from the library *)
rDb := 20.0 * LN(rAmplitude) / LN10;
Don’t write 2.0 * PI — use TWO_PI
2.0 * PI is one floating-point multiplication every time it’s
evaluated, and IEEE-754 rounds the result to the nearest representable
LREAL. That rounding is the same bit each cycle, so it’s repeatable —
but a long-running phase accumulator using 2.0 * PI will drift
visibly compared to one using the pre-computed TWO_PI constant.
For one-shot uses inside an expression the difference is invisible.
For accumulators inside PROGRAM-cycle bodies, always prefer the
pre-computed form.
Portability notes
- If your code must also compile under a different vendor’s standard
library, declare the constants you actually use explicitly in a
VAR_GLOBAL CONSTANTblock. Both compilers will then converge on the same LREAL bit pattern (the decimal3.141592653589793has a unique nearest-LREAL). - If your code must also compile under matiec, never write
2.0 * PIexpecting library-suppliedPI— matiec ships no constants at all. - The
forgeiec_mathlibrary is opt-in. Without loading it, the compiler will rejectPI/E/etc. with the IEC-conformant error “undefined identifier” — no magic, no surprises.
See also
- Arithmetic functions —
SIN,COS,SQRT,LN,EXPand friends. These are in the IEC core. - Type conversion —
REAL_TO_LREAL,LREAL_TO_REALwhen you mix precisions.