Fixed-point types
In this chapter, we discuss fixed-point types, which can be classified in two categories: decimal fixed-point types and ordinary (binary) fixed-point types. Afterward a brief overview of each category, we discuss some differences between fixed-point and floating-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.
Decimal delta
The delta determines the required decimal precision for the type. For example,
if we want to be able to use two digits after the decimal point, we would write
delta 10.0 ** (-2) — which is equivalent to delta 0.01.
(You can use any of those definitions: both delta 10.0 ** (-2) and
delta 0.01 are correct.)
    
    
    
        with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
   type Decimal is
     delta 10.0 ** (-1) digits 3;
   --  Alternatively:
   --  type Decimal is
   --    delta 0.1 digits 3;
begin
   Put_Line
     ("The decimal precision of Decimal is "
      & Decimal'Delta'Image);
end Decimal_Fixed_Point_Types;
    
    
    
        
        
            
                
            
        
    
    
        
            
        
    
    
        
        
            
                
            
            
            
        
    
In this example, we declare the Decimal type, which has a decimal
precision of 0.1. We use the 'Delta attribute to show the decimal
precision of the type.
Decimal digits
Unsurprisingly, the digits part of the type declaration determines
the number of digits that a type is able to represent. For example, by writing
digits 3, we're able to represent values with three digits ranging from
-999 to 999. For example:
    
    
    
        with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
   type Decimal is
     delta 10.0 ** (0) digits 3;
begin
   Put_Line ("The minimum value of Decimal is "
             & Decimal'First'Image);
   Put_Line ("The maximum value of Decimal is "
             & Decimal'Last'Image);
end Decimal_Fixed_Point_Types;
    
    
    
        
        
            
                
            
        
    
    
        
            
        
    
    
        
        
            
                
            
            
            
        
    
In this example, we declare the Decimal type, which has a range from
-999 to 999. We use the 'First and 'Last attributes to show
the first and last value of the range, respectively.
For further reading...
When running the application above, we see that the first and last values
are -999.0 and 999.0, respectively — i.e. values with the decimal
point. Strictly speaking, however, the actual first and last values are
-999 and 999 because we selected a delta of 1.0. The decimal point
(.0) we see in the application output (in the values -999.0 and
999.0) is only there to indicate that this is not an integer value
— however, it doesn't indicate an extended decimal precision at all.
    
    
    
        with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
   type Decimal is
     delta 10.0 ** (0) digits 3;
   D : Decimal := 0.1;
   --             ^^^
   --  ERROR: value cannot be represented
   --         by Decimal type.
begin
   Put_Line (D'Image);
end Decimal_Fixed_Point_Types;
    
    
    
        
        
            
                
            
        
    
    
        
            
        
    
    
        
        
            
                
            
            
            
        
    
Assigning the value 0.1 to D is wrong because the Decimal
type cannot represent this value.
For further reading...
The Decimal type above is similar to — but far from being
equivalent to — the following floating-point type declaration:
    
    
    
        with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
   type Float_999 is
     digits 3
     range -999.0 .. 999.0;
begin
   Put_Line ("The minimum value of Float_999 is "
             & Float_999'First'Image);
   Put_Line ("The maximum value of Float_999 is "
             & Float_999'Last'Image);
end Decimal_Fixed_Point_Types;
    
    
    
        
        
            
                
            
        
    
    
        
            
        
    
    
        
        
            
                
            
            
            
        
    
The Float_999 type from this example has (roughly) the same range as
the Decimal type that we declared in the previous example: -999 to
999. However, there are substantial
differences between fixed-point and floating-point types,
so we cannot say that these type declarations are equivalent.
Decimal delta and digits
By combining those three digits (i.e. digits 3) with a decimal precision
of two digits after the decimal point (delta 10.0 ** (-2)), we get a
range from -9.99 to 9.99. For example:
    
    
    
        with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
   type Decimal is
     delta 10.0 ** (-2) digits 3;
begin
   Put_Line ("The minimum value of Decimal is "
             & Decimal'First'Image);
   Put_Line ("The maximum value of Decimal is "
             & Decimal'Last'Image);
end Decimal_Fixed_Point_Types;
    
    
    
        
        
            
                
            
        
    
    
        
            
        
    
    
        
        
            
                
            
            
            
        
    
In this example, we declare the Decimal type, which has a range from
-9.99 to 9.99 (as expected).
Requirements for the delta
Note that the delta expression for decimal fixed-point types must be a power of 10. Using a different value for the power leads to compilation errors. For example:
    
    
    
        package Decimal_Fixed_Point_Type_Error is
   type Decimal_Error_1 is
     delta 2.0 ** (-1) digits 3;
   --      ^^^^^^^^^^^
   --  ERROR: not power of ten
   type Decimal_Error_2 is
     delta 0.125 digits 3;
   --      ^^^^^
   --  ERROR: not power of ten
end Decimal_Fixed_Point_Type_Error;
    
    
    
        
        
            
                
            
        
    
    
        
            
        
    
    
        
        
            
                
            
            
            
        
    
In this example, the type declarations (of Decimal_Error_1 and
Decimal_Error_2) are wrong because the delta expression is not a power
of 10.
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 delta isn't limited
to power of 10 values, but it can have any arbitrary base.
For further reading...
When representing ordinary fixed-point types on the machine, the compiler
selects a scaling factor derived from the value of delta specified
in the type declaration. This compiler-selected scaling factor is, by
default, a power of two — even if the value provided for the
delta isn't a  power of two. Therefore, ordinary fixed-point types
are sometimes called binary fixed-point types.
The syntax for an ordinary fixed-point type is
type <type-name> is
  delta <delta-value>
  range <lower-bound> .. <upper-bound>;
For example, we can define an ordinary fixed-point type T_Inv_Trig for
inverse trigonometric calculations:
    
    
    
        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 0.0005
     range -Pi / 2.0 .. Pi / 2.0;
begin
   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 the T_Inv_Trig type with a range from
-π/2 to π/2, and a delta of 0.0005. Note that, in this case, the delta is
neither a power of ten nor a power of two. (In fact, this value corresponds to
2000.0 ** (-1).)
Fixed-point vs. floating-point types
The main difference between fixed-point and floating-point types is that
fixed-point types don't have an exponent. This has an impact on calculations
using small values: while they might still be representable with floating-point
types, those small values might simply disappear (i.e. become zero) in the
fixed-point representation. Let's see an example where we compare the decimal
type Decimal to the floating-point type Float_32:
    
    
    
        with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Vs_Floating_Point_Types is
   type Decimal is
     delta 10.0 ** (-2) digits 9;
   type Float_32 is
     digits 6
     range -9999999.99 .. 9999999.99;
   D : Decimal  := 0.01;
   F : Float_32 := 0.01;
begin
   Put_Line ("D = " &
             D'Image);
   Put_Line ("F = " &
             F'Image);
   D := D / 2.0;
   --   ^^^^^^^
   --  Value becomes zero.
   F := F / 2.0;
   --   ^^^^^^^
   --  Exponent is used to
   --  represent smaller
   --  value.
   Put_Line ("D = " &
             D'Image);
   Put_Line ("F = " &
             F'Image);
end Decimal_Vs_Floating_Point_Types;
    
    
    
        
        
            
                
            
        
    
    
        
            
        
    
    
        
        
            
                
            
            
            
        
    
Both types in this example have roughly the same size and range. However, the
result of the divide-by-two operation isn't the same: because of the exponent,
F has the expected value (0.005) after the operation. while the value of
D is zero. The reason is that the resulting value 0.005 cannot be
represented by the decimal precision of the Decimal type. In the case of
F, however, the value can be represented due to a simple change in the
exponent.
This lack of precision we just described might seem like a drawback for fixed-point types. However, depending on the algorithm and its field of application, this is the exact behavior that we might be looking for. As mentioned in the beginning of this chapter, financial applications benefit from decimal types, while using floating-point type for these applications can lead to unpredictable (or undesirable) behavior.
Another major difference concerns the way fixed-point operations translate into machine operations. In most cases, operations on fixed-point types are modeled in a processor by using integer registers and instructions. Essentially, the compiler maps fixed-point types to integer types, but it uses slightly different numeric rules. This fact can be an advantage for specific embedded applications where a floating-point unit might be either non-existent or its usage might have a higher associated cost in terms of CPU cycles or power consumption. Therefore, for these specific applications, using fixed-point types could be considered as an alternative.