November 7, 2019

Subrange Types

Infosys it at Subrange Types or it looks like INT(1..42);

You can bound a variable of the integer type into the predefined range. In other words you can set the low limit and the hi limit for the integer variable. Thus you can create a new integer type. But is it realy? 

Actual for the following types only: SINT, USINT, INT, UINT, DINT, UDINT, BYTE, WORD, DWORD, LINT, ULINT, LWORD.

Let's play in the sandbox. For the clarity, I have used underscores in the names of subrange variables.

PROGRAM MAIN
VAR
    i: DINT;
    value: INT;
    _value_: INT(10..90);
    analog: REAL;

    pValue : PVOID;

You can do same actions with a variable of subrange type as with a variable of an integer type. In fact, this is not a new data type with a new bounds (or restrictions), but it is a suggestion or a clue to the compiler how to check some cases (I will discuss it later) or how to control variable value.
Unfortunately, you cannot do this: _analog_ : REAL (4.0 .. 20.0); and it is not about too many dots in a row but because of the inaccuracy of the floating-point types.

Truth Or Dare


Possible:

_value_ := 42;
_value_ := value;

_value_ := _value_ + 1;
_value_ := _value_ + 100; // O_o

_value_ := DINT_TO_INT(i);
_value_ := REAL_TO_INT(analog);

Are not allowed:

_value_ := 0;
FOR _value_ := 11 TO 89 DO


Excavating Memory


So I decided to check whether or not someone controls the output of the variable value for the specified range during the operation process. I wrote zero to the variable `value` through the pointers. And zero was recorded (nothing bad happened). And then I have added 1 to the variable and suddenly, the value of the variable increased by 1. Where is the bounds? 

// _value_ = 10;

value := 0;
pValue := ADR(_value_);
MEMCPY(pValue, ADR(value), SIZEOF(_value_));
// _value_ = 0;

_value_ := _value_ + 1;
// _value_ = 1;

In general, if we performe cyclically _value_ := _value_ + 1; we will see the whole range of INT values from -32768 to +32767, without any restrictions from the runtime system.

Conclusion: the runtime does not control the value of the variable; it works like a regular integer type, and nothing else.


Implicit Checks


There is a whole family of POUs for implicit checks of the variable value (e.g. division by zero checks POUs):



You have to add some special POUs to the project to control data bounds during the execution:



...then choose L|Range Checks and finally rebuild project. Newcome POUs will control the variable and limits it if the value overstep the bounds defined by you. And now if you repeat the last used trick with pointers but with already activated implicit checking you can get completely different result. Runtime system will call corresponding POU that will limit variable to the predefined bounds.


Performance


FYI, performance measurements are performed to catch whether additional work is happening behind the scenes or not. There was no point in optimizing for 1-2 nanoseconds, although who knows what's going on there. Vertical axis in hundreds of nanoseconds: 83000 = 8.3 milliseconds.

Testing ground:

// HOWMUCH: DINT := 1_000_000;

CASE state OF
1,5:; // empty loop

2: // increment
    FOR i := 0 TO HOWMUCH DO
        value := value + 1;
    END_FOR

3: // const in bounds
    FOR i := 0 TO HOWMUCH DO
        _value_ := 42;
    END_FOR

4: // increment that overstep the bounds
    FOR i := 0 TO HOWMUCH DO
        _value_ := _value_ + 1;
    END_FOR


Without POUs for implicit checks:

Added POUs for implicit checks then the project was rebuilt:

Conclusion: POUs for implicit checks cause extra load.


Resume

  • In fact, this is not a new data type, just an ordinary integer type (INTBYTE, ...) with extra information about subrange.
  • Range are clue for a compiler.
  • Range controlled by extra POUs for implicit checks. This is optional.
  • Implicit checks cause extra load because of execution extra POUs.

No comments

Post a Comment

Note: Only a member of this blog may post a comment.