2

Let me first provide some context in hope it will make the problem clearer: I am receiving byte vector data from Hardware which I wish to operate on. I do not wish to convert the date to larger size due to size and time constraints. I want to allow for intermediate values of a calculation to exceed byte range. This is not a problem with scalars (intermediate values are kept in registers and compiler does not issue a constraint error for intermediate values).

However, When working on user defined operators, it is more tricky. We can promote the result to a larger size, but then the assignment back to the original type will require an explicit conversion (subtype cannot have mixed sizes). For example in the code below line 24 would become Z := To_Point((X + Y) / 2); It is a solution, but I hope to find one that does not require adding the "To_Point" function.

I looked at the implementation of vectors in Ada.Numerics it is using real values, and does not promote intermediate values for example:

function "+" (Left, Right : Real_Vector) return Real_Vector;

This may lead to constraint error, but more likely it may lead to some loss of accuracy (because of the way real number are represented) comparing to scalar calculation (machine dependent).

     1. pragma Ada_2012;
     2. with Ada.Text_IO; use Ada.Text_IO;
     3.
     4. procedure Inter_Value is
     5.     type Byte is new Integer Range 0..255 with Size => 8;
     6.     A, B, C : Byte;
     7.
     8.     type Point is array(1..2) of Byte with Convention => C, Size => 2*8;
     9.     X, Y, Z : Point;
    10.
    11.     function "+" (Left, Right : Point) return Point is (Left (1) + Right (1), Left (2) + Right(2));
    12.     function "/" (Left : Point; Right : Byte) return Point is (Left (1) / Right, Left (2) / Right);
    13.
    14. begin
    15.     Put_Line(C'Size'Image);
    16.     A := 100;
    17.     B := 200;
    18.     C := (A + B) / 2;           -- Ok, intermediate value in register
    19.     Put_Line("C = " & C'Image);
    20.
    21.     Put_Line(X'Size'Image);
    22.     X := (100, 100);
    23.     Y := (200, 200);
    24.     Z := (X + Y) / 2;           -- CONSTRAINT_ERROR, intermediate value in Point
    25.     Put_Line("Z = " & Z(1)'Image & Z(2)'Image);
    26. end;
PolarBear2015
  • 745
  • 6
  • 14
  • The reason `(A + B) / 2` works has nothing to do with registers. It works because the `"+"` invoked is defined as `function "+" (Left : in Integer; Right : in Integer) return Integer;` – Jeffrey R. Carter May 23 '21 at 10:01
  • 2
    Sorry, actually the types in the definition of `"+"` for `Byte` are defined as operating on and returning `Byte'Base`, and `Byte'Base` is declared to have the same representation as `Integer`. – Jeffrey R. Carter May 23 '21 at 10:10
  • @JeffreyR.Carter, thank you - this makes sense (Integer and Byte aren't compatible, but apparently Byte'Base and Byte are). If I would could only extent it to vectors... – PolarBear2015 May 23 '21 at 10:33

2 Answers2

4

To recap from the comments: the declaration of Byte is equivalent to

type Byte'Base is new Integer;
subtype Byte is Byte'Base range 0 .. 255 with Size => 8;

The important thing about this is that the predefined operators are defined for Byte'Base. To get similar behavior for Point you have to explicitly mimic this:

type Point_Base is array (1..2) of Byte'Base;

function "+" (Left : in Point_Base; Right : in Point_Base) return Point_Base is
   (Left (1) + Right (1), Left (2) + Right(2) );
function "/" (Left : in Point_Base; Right : in Byte'Base) return Point_Base is
   (Left (1) / Right, Left (2) / Right);

subtype Point is Point_Base with
   Dynamic_Predicate => (for all P of Point => P in Byte);

Now (X + Y) gives a Point_Base that is passed to "/", which also gives a Point_Base. Then a check is made that the result satisfies the constraints of the subtype of Z before the assignment.

Jeffrey R. Carter
  • 3,033
  • 9
  • 10
  • Nice answer and a good (i.e. useful to me!) illustration of Ada 2012 predicates. The intermediate, (X+Y) is then a Point_Base, and cannot be stored in a Point without (dynamically, IF overflow occurs) raising Constaint Error. –  May 24 '21 at 15:21
  • The Dynamic Predicate checks that the value does not exceed the Byte range, However, the Size of "Point" is still the same Size as that of "Point_Base" so a conversion of the input is still needed (it is not binary compatible, cannot be applied directly to a buffer sent by the Hardware) -- That said - a single conversion of the input to the a wider size may still be the right way to go - it is simpler, and avoids many intermediate conversions later on. – PolarBear2015 May 24 '21 at 19:59
  • 2
    I ignored the size complications to concentrate on the functionality. The basic rule when dealing with this sort of thing is to have a type that corresponds to the H/W layout and another that has ranges that correspond to those needed by the processing and a layout determined by the compiler. Input is immediately converted to the internal type and the internal type is converted to the external type just before being output. So your internal type would probably be `Point` as above (so it checks that all final values are in `Byte`, and you'd add a type `HW_Point` for input and output. – Jeffrey R. Carter May 25 '21 at 08:16
1

X+Y should be a Point so each component should stay within the Byte range which is not the case.

Why don't you rewrite the line 24 like this?:

    Z := (X/2 + Y/2);

As each X and Y component can't exceed 255, adding half of these always stays in the range.

Frédéric Praca
  • 1,620
  • 15
  • 29