Version 1.1 of acs/ac-00304.txt

Unformatted version of acs/ac-00304.txt version 1.1
Other versions for file acs/ac-00304.txt

!standard 3.5.6(3)          18-02-21 AC95-00304/00
!class Amendment 18-02-21
!status received no action 18-02-21
!status received 18-01-15
!subject Universal numeric data type
!summary
!appendix

!topic Universal numeric data type
!reference Ada 2012 RM3.5.6(3)
!from Gustavo A. Hoffmann 18-01-15
!keywords numeric types, root_real
!discussion

This is a proposal for a new universal numeric data type. This data type
abstracts floating-point and fixed-point data types. It could be said
that this data type allows for developers to implement generic
algorithms by directly accessing the root_real type mentioned in RM3.5.6.

The goal of this data type is to be able to define a range without
having to specify the actual data type used for operations. This is
relevant for algorithms where the numeric range is more important than
the precision of the actual type. This is usually the case for digital
signal processing (DSP) algorithms, for example.

The simplest implementation of universal numeric types in Ada 2012 is
the one that selects either a floating-point or fixed-point
specification at build time. This approach has been discussed in [1].

The syntax of the universal numeric data type could be as follows:

type T is range <static_simple_expression> .. <static_simple_expression>


The keyword _abstract_ could be used to make it more explicit:

type T is abstract range <static_simple_expression> ..
<static_simple_expression>


Abstract operations could be defined for it. Also, actual types could be
derived from the universal data type. This is an example:

    --  Universal numeric data type for DSP algorithms
    type T_Fract is abstract range -1.0 .. 1.0;

    --  Abstract operator for universal numeric data type
    function "*" (A, B : T_Fract) return T_Fract is abstract;

    --  Deriving fixed-point data type from universal numeric data type
    type Fixed_Fract is new T_Fract and delta 1.0 / 2.0 ** 31
      with Size => 32;

    --  Deriving floating-point data type from universal numeric data type
    type Float_Fract is new T_Fract and digits 10;


Also, the universal numeric data type could be used for generic
implementations:

generic
    type Filter_Data_Type is new T_Fract;
    type Filter_Calc_Type is new T_Fract;
package Filters is

    type Filter_Delay is
       record
          Z1 : Filter_Calc_Type := 0.0;
          Z2 : Filter_Calc_Type := 0.0;
       end record;

    function Biquad (D    : in out Filter_Delay;
                     X_In : Filter_Data_Type) return Filter_Data_Type;
end Filters;

package body Filters is

    function Biquad (D    : in out Filter_Delay;
                     X_In : Filter_Data_Type) return Filter_Data_Type is
       X  : Filter_Calc_Type;
       Y  : Filter_Calc_Type;

       --  Coefficients are 16-bit quantized
       A0 : constant Filter_Calc_Type := 0.00115966796875;
       A1 : constant Filter_Calc_Type := 0.0023193359375;
       A2 : constant Filter_Calc_Type := 0.00115966796875;
       B1 : constant Filter_Calc_Type :=
          (if Filter_Calc_Type'Is_Floating_Point
          then -1.8319091796875
          else -1.8319091796875 / 2);
       B2 : constant Filter_Calc_Type := 0.836578369140625;

    begin
       X    := Filter_Calc_Type (X_In);
       Y    := X * A0 + D.Z1;
       if Filter_Calc_Type'Is_Floating_Point then
          D.Z1 := X * A1 + D.Z2 - B1 * Y;
       else
          D.Z1 := X * A1 + D.Z2 - (B1 * Y) * 2;
       end if;
       D.Z2 := X * A2 - B2 * Y;

       return Filter_Data_Type (Y);
    end Biquad;

end Filters;


[1]
http://www.electronicdesign.com/embedded-revolution/assessing-ada-language-audio-applications

***************************************************************

From: Randy Brukardt
Sent: Friday, January 19, 2018  10:05 PM

> This is a proposal for a new universal numeric data type.
> This data type abstracts floating-point and fixed-point data types. It
> could be said that this data type allows for developers to implement
> generic algorithms by directly accessing the root_real type mentioned
> in RM3.5.6.

You probably should have made clear that this is a real type; it does not
include integer operations. Ada, of course, has such a type (universal_real),
but since you can't declare anything of this type it doesn't help.

> The goal of this data type is to be able to define a range without
> having to specify the actual data type used for operations. This is
> relevant for algorithms where the numeric range is more important than
> the precision of the actual type. This is usually the case for digital
> signal processing
> (DSP) algorithms, for example.

I find this goal unusual. One cannot reason much about types for which one
doesn't know the precision or how the operations actually work. And it seems
more appropriate for a generic formal type than a first-class type.

Certainly if you are going to do any static analysis, that needs to include
error analysis, and that requires knowing the precision and whether the
operations are floating point or fixed point. Even if the range is the primary
goal, one needs to be able to use tools to ensure that your answer is more than
just noise (significance cancelation being a real problem for real types).
Having types that are designed to prevent such analysis seems like a step in the
wrong direction.

Specifically, it's clear that your generic example subprogram is assuming a
particular range and precision even though you claim the precision isn't
important. You don't need 14 digits in the coefficients if the precision doesn't
matter. :-) Moreover, if given a type with 20 digit precision, the algorithm
would be suboptimal.

> The simplest implementation of universal numeric types in Ada
> 2012 is the one that selects either a floating-point or fixed-point
> specification at build time. This approach has been discussed in [1].
>
> The syntax of the universal numeric data type could be as follows:
>
> type T is range <static_simple_expression> ..
> <static_simple_expression>

This is the syntax of an integer type declaration. Any syntax chosen has to be
unique!

> The keyword _abstract_ could be used to make it more explicit:
>
> type T is abstract range <static_simple_expression> ..
> <static_simple_expression>

That would be OK, as there is no such thing currently. However, one cannot
declare objects of abstract types, and that's not a rule that we would want to
change so the language stays consistent. I don't know if you need that
capability or not, since you didn't give any realistic examples of use outside
of a generic unit.

> Abstract operations could be defined for it. Also, actual types could
> be derived from the universal data type. This is an example:
>
>     --  Universal numeric data type for DSP algorithms
>     type T_Fract is abstract range -1.0 .. 1.0;
>
>     --  Abstract operator for universal numeric data type
>     function "*" (A, B : T_Fract) return T_Fract is abstract;

This looks like an attempt to define OOP for numeric types in Ada. There have
previously been proposals for that, which have always been rejected because of
complexity and the fact that they have to operate differently than tagged types.

>     --  Deriving fixed-point data type from universal numeric data type
>     type Fixed_Fract is new T_Fract and delta 1.0 / 2.0 ** 31
>       with Size => 32;

This ought to be illegal, as there is no "*" matching the specification above
declared by this type. (Which is the real problem with this idea: "*" for a
fixed point type works very differently than "*" for a floating point type.)

>     --  Deriving floating-point data type from universal numeric data type
>     type Float_Fract is new T_Fract and digits 10;

The use of derived types outside of tagged types (and the semantics of those are
different) is rare. I think 90% of the use is in ACATS tests to prove that
implementations do this correctly. Most Ada users have no idea what these lines
actually do.

Also, current rules require either there to be no inherited operations for a
derived untagged type or no representation change. You have both here. There are
important reasons for that restriction; it might be possible to lift it but that
would certainly surface various other semantic issues in the Standard. (Various
rules only work because of 13.1(10) bans representation change.)

What I don't understand here is why you would want to be able to declare such a
type. There only seems to be one such type in the universe -- universal_real
(although we might want to name it something else so the extra operations of
universal_real don't muck up visibility). Most Ada users aren't going to want to
declare a universal type, and you could always add a range later with a subtype.

You don't give any examples of non-generic use in the proposal; most uses I can
think of seem to make more sense written in a generic unit. For instance:

> Also, the universal numeric data type could be used for generic
> implementations:
>
> generic
>     type Filter_Data_Type is new T_Fract;
>     type Filter_Calc_Type is new T_Fract; package Filters is
>
>     type Filter_Delay is
>        record
>           Z1 : Filter_Calc_Type := 0.0;
>           Z2 : Filter_Calc_Type := 0.0;
>        end record;
>
>     function Biquad (D    : in out Filter_Delay;
>                      X_In : Filter_Data_Type) return Filter_Data_Type;
> end Filters;
>
> package body Filters is
>
>     function Biquad (D    : in out Filter_Delay;
>                      X_In : Filter_Data_Type) return Filter_Data_Type
> is
>        X  : Filter_Calc_Type;
>        Y  : Filter_Calc_Type;
>
>        --  Coefficients are 16-bit quantized
>        A0 : constant Filter_Calc_Type := 0.00115966796875;
>        A1 : constant Filter_Calc_Type := 0.0023193359375;
>        A2 : constant Filter_Calc_Type := 0.00115966796875;
>        B1 : constant Filter_Calc_Type :=
>           (if Filter_Calc_Type'Is_Floating_Point
>           then -1.8319091796875
>           else -1.8319091796875 / 2);
>        B2 : constant Filter_Calc_Type := 0.836578369140625;
>
>     begin
>        X    := Filter_Calc_Type (X_In);
>        Y    := X * A0 + D.Z1;
>        if Filter_Calc_Type'Is_Floating_Point then
>           D.Z1 := X * A1 + D.Z2 - B1 * Y;
>        else
>           D.Z1 := X * A1 + D.Z2 - (B1 * Y) * 2;
>        end if;
>        D.Z2 := X * A2 - B2 * Y;
>
>        return Filter_Data_Type (Y);
>     end Biquad;
>
> end Filters;

It would be better and less disruptive for THIS purpose to propose a generic
real type that matched any kind of real type (much like the generic discrete
type matches any discrete type). A good syntax idea escapes me, so lets just
imagine:

    type Some_Real is delta or digits <>;

This would match any real type.

But your example shows the problem with this idea: you also are using a new
attribute "Is_Floating_Point" (which you didn't propose) in order to write code
that works with either floating point or fixed point. (And decimal fixed point
probably would require a third set of rules, should someone instantiate this
routine with those.) It's unclear this is better than simply defining separate
generics for float and fixed point types. Certainly, it is easier to reason
about float and fixed operations individually than it is about either proposal
(your universal type or my formal real type).

[Your example also seems to assume "+" and "-" operations declared for your
type, since they wouldn't exist automatically. It helps to keep examples
complete and legal assuming the new features -- although in this case I think it
is fairly obvious what is missing.]

> [1]
> http://www.electronicdesign.com/embedded-revolution/assessing-
> ada-language-audio-applications

I remember reading that article. Good job! The way you handled the choice of
floating point or fixed point types seemed exactly right to me. I recall
thinking that it isn't possible to really switch between fixed and float
perfectly, in part because the analysis needs are different for the different
kinds of types.

***************************************************************

Questions? Ask the ACAA Technical Agent