Fixed-point types

Decimal fixed-point types

We have already seen how to specify floating-point types. However, in some applications floating-point is not appropriate since, for example, the roundoff error from binary arithmetic may be unacceptable or perhaps the hardware does not support floating-point instructions. Ada provides a category of types, the decimal fixed-point types, that allows the programmer to specify the required decimal precision (number of digits) as well as the scaling factor (a power of ten) and, optionally, a range. In effect the values will be represented as integers implicitly scaled by the specified power of 10. This is useful, for example, for financial applications.

The syntax for a simple decimal fixed-point type is

type <type-name> is delta <delta-value> digits <digits-value>;

In this case, the delta and the digits will be used by the compiler to derive a range.

Several attributes are useful for dealing with decimal types:

Attribute Name

Meaning

First

The first value of the type

Last

The last value of the type

Delta

The delta value of the type

In the example below, we declare two data types: T3_D3 and T6_D3. For both types, the delta value is the same: 0.001.

    
    
    
        
with Ada.Text_IO; use Ada.Text_IO; procedure Decimal_Fixed_Point_Types is type T3_D3 is delta 10.0 ** (-3) digits 3; type T6_D3 is delta 10.0 ** (-3) digits 6; begin Put_Line ("The delta value of T3_D3 is " & T3_D3'Image (T3_D3'Delta)); Put_Line ("The minimum value of T3_D3 is " & T3_D3'Image (T3_D3'First)); Put_Line ("The maximum value of T3_D3 is " & T3_D3'Image (T3_D3'Last)); New_Line; Put_Line ("The delta value of T6_D3 is " & T6_D3'Image (T6_D3'Delta)); Put_Line ("The minimum value of T6_D3 is " & T6_D3'Image (T6_D3'First)); Put_Line ("The maximum value of T6_D3 is " & T6_D3'Image (T6_D3'Last)); end Decimal_Fixed_Point_Types;

When running the application, we see that the delta value of both types is indeed the same: 0.001. However, because T3_D3 is restricted to 3 digits, its range is -0.999 to 0.999. For the T6_D3, we have defined a precision of 6 digits, so the range is -999.999 to 999.999.

Similar to the type definition using the range syntax, because we have an implicit range, the compiled code will check that the variables contain values that are not out-of-range. Also, if the result of a multiplication or division on decimal fixed-point types is smaller than the delta value required for the context, the actual result will be zero. For example:

    
    
    
        
with Ada.Text_IO; use Ada.Text_IO; procedure Decimal_Fixed_Point_Smaller is type T3_D3 is delta 10.0 ** (-3) digits 3; type T6_D6 is delta 10.0 ** (-6) digits 6; A : T3_D3 := T3_D3'Delta; B : T3_D3 := 0.5; C : T6_D6; begin Put_Line ("The value of A is " & T3_D3'Image (A)); A := A * B; Put_Line ("The value of A * B is " & T3_D3'Image (A)); A := T3_D3'Delta; C := A * B; Put_Line ("The value of A * B is " & T6_D6'Image (C)); end Decimal_Fixed_Point_Smaller;

In this example, the result of the operation 0.001 * 0.5 is 0.0005. Since this value is not representable for the T3_D3 type because the delta value is 0.001, the actual value stored in variable A is zero. However, accuracy is preserved during the arithmetic operations if the target has sufficient precision, and the value displayed for C is 0.000500.

Ordinary fixed-point types

Ordinary fixed-point types are similar to decimal fixed-point types in that the values are, in effect, scaled integers. The difference between them is in the scale factor: for a decimal fixed-point type, the scaling, given explicitly by the type's delta, is always a power of ten.

In contrast, for an ordinary fixed-point type, the scaling is defined by the type's small, which is derived from the specified delta and, by default, is a power of two. Therefore, ordinary fixed-point types are sometimes called binary fixed-point types.

Note

Ordinary fixed-point types can be thought of being closer to the actual representation on the machine, since hardware support for decimal fixed-point arithmetic is not widespread (rescalings by a power of ten), while ordinary fixed-point types make use of the available integer shift instructions.

The syntax for an ordinary fixed-point type is

type <type-name> is
  delta <delta-value>
  range <lower-bound> .. <upper-bound>;

By default the compiler will choose a scale factor, or small, that is a power of 2 no greater than <delta-value>.

For example, we may define a normalized range between -1.0 and 1.0 as following:

    
    
    
        
with Ada.Text_IO; use Ada.Text_IO; procedure Normalized_Fixed_Point_Type is D : constant := 2.0 ** (-31); type TQ31 is delta D range -1.0 .. 1.0 - D; begin Put_Line ("TQ31 requires " & Integer'Image (TQ31'Size) & " bits"); Put_Line ("The delta value of TQ31 is " & TQ31'Image (TQ31'Delta)); Put_Line ("The minimum value of TQ31 is " & TQ31'Image (TQ31'First)); Put_Line ("The maximum value of TQ31 is " & TQ31'Image (TQ31'Last)); end Normalized_Fixed_Point_Type;

In this example, we are defining a 32-bit fixed-point data type for our normalized range. When running the application, we notice that the upper bound is close to one, but not exact one. This is a typical effect of fixed-point data types — you can find more details in this discussion about the Q format.

We may also rewrite this code with an exact type definition:

    
    
    
        
procedure Normalized_Adapted_Fixed_Point_Type is type TQ31 is delta 2.0 ** (-31) range -1.0 .. 1.0 - 2.0 ** (-31); begin null; end Normalized_Adapted_Fixed_Point_Type;

We may also use any other range. For example:

    
    
    
        
with Ada.Text_IO; use Ada.Text_IO; with Ada.Numerics; use Ada.Numerics; procedure Custom_Fixed_Point_Range is type T_Inv_Trig is delta 2.0 ** (-15) * Pi range -Pi / 2.0 .. Pi / 2.0; begin Put_Line ("T_Inv_Trig requires " & Integer'Image (T_Inv_Trig'Size) & " bits"); Put_Line ("Delta value of T_Inv_Trig: " & T_Inv_Trig'Image (T_Inv_Trig'Delta)); Put_Line ("Minimum value of T_Inv_Trig: " & T_Inv_Trig'Image (T_Inv_Trig'First)); Put_Line ("Maximum value of T_Inv_Trig: " & T_Inv_Trig'Image (T_Inv_Trig'Last)); end Custom_Fixed_Point_Range;

In this example, we are defining a 16-bit type called T_Inv_Trig, which has a range from -π/2 to π/2.

All standard operations are available for fixed-point types. For example:

    
    
    
        
with Ada.Text_IO; use Ada.Text_IO; procedure Fixed_Point_Op is type TQ31 is delta 2.0 ** (-31) range -1.0 .. 1.0 - 2.0 ** (-31); A, B, R : TQ31; begin A := 0.25; B := 0.50; R := A + B; Put_Line ("R is " & TQ31'Image (R)); end Fixed_Point_Op;

As expected, R contains 0.75 after the addition of A and B.

In fact the language is more general than these examples imply, since in practice it is typical to need to multiply or divide values from different fixed-point types, and obtain a result that may be of a third fixed-point type. The details are outside the scope of this introductory course.

It is also worth noting, although again the details are outside the scope of this course, that you can explicitly specify a value for an ordinary fixed-point type's small. This allows non-binary scaling, for example:

type Angle is
  delta 1.0/3600.0
  range 0.0 .. 360.0 - 1.0 / 3600.0;
for Angle'Small use Angle'Delta;