Mathematical constants

Why this page exists

IEC 61131-3 §2.5.1 specifies mathematical functionsSIN, 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:

SourceWhere PI comes fromNotes
Common commercial PLC stdlibglobal VAR CONSTANTJust PI (LREAL). No E, no SQRT2.
Open-source IEC extension librarydedicated _BASIC libPI, EULER, GOLDEN_RATIO, more.
Vendor-specific math libraryglobal constantsOften two-precision form, e.g. LREAL_PI / REAL_PI.
Bare IEC 61131-3 (matiec / Beremiz)nothingYou must declare them yourself.
ForgeIECforgeiec_math libAll 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:

SymbolApproximate valueType
PI3.141592653589793LREAL
TWO_PI6.283185307179586LREAL
PI_HALF1.570796326794897LREAL
E2.718281828459045LREAL
SQRT21.414213562373095LREAL
SQRT1_20.707106781186548LREAL
LN20.693147180559945LREAL
LN102.302585092994046LREAL
GOLDEN_RATIO1.618033988749895LREAL
EULER_MASCHERONI0.577215664901533LREAL

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 CONSTANT block. Both compilers will then converge on the same LREAL bit pattern (the decimal 3.141592653589793 has a unique nearest-LREAL).
  • If your code must also compile under matiec, never write 2.0 * PI expecting library-supplied PI — matiec ships no constants at all.
  • The forgeiec_math library is opt-in. Without loading it, the compiler will reject PI/E/etc. with the IEC-conformant error “undefined identifier” — no magic, no surprises.

See also