Fixed Point, Modular, and Universal Types
The only fixed point type defined in package Standard is Duration. However, Ada lets us define our own fixed point types. We specify the accuracy with the reserved word delta, and a range constraint is required. For example,
type Voltage is delta 0.01 range -20.0 .. 20.0;
This guarantees that the objects of type Voltage will be represented with at least an accuracy of 1/100. Since the computer is binary, Ada will choose an internal representation at least as accurate as 1/128. It might use even greater accuracy, for example, 1/256. In any event, it's guaranteed that the accuracy is at least as good as that requested.
It's possible to make a request that a particular implementation of Ada can't handle. For example, if we write
type Voltage is delta 1.0E-10 range 0.0 .. 1.0E9;the Ada compiler that we're using may have to report that it has no internal representation that satisfies this requirement. (Almost any delta would be unreasonable if Ada didn't require range constraints on all fixed point types.)
The set of numbers that can be represented exactly by any implementation of Ada that accepts a type definition like the above is called the model numbers of that type. This applies to floating types as well as fixed, for example,
type W is digits 5 range 0.0 .. 100.0;
A particular implementation may represent additional numbers exactly; these are called safe numbers. The safe numbers are a superset of the model numbers; their range usually is a little larger.
We can add and subtract objects of a fixed point type. However, if we multiply or divide them in Ada 83, we must immediately convert the result to the same or another numeric type before we can store it. For example,
V1, V2, V3 : Voltage; ... V1 := V2+V3; -- legal V1 := V2*V3; -- illegal in Ada 83, legal in Ada 95 V1 := Voltage(V2*V3); -- legal
In Ada 95, we can multiply two numbers of a fixed point type without an explicit type conversion if the type of the multiply is uniquely determined. In the previous example, the multiplication is legal in Ada 95 because storing the result into V1 uniquely determines the type of the product of V2 and V3.
Note that V1 := V1*V2*V3 is illegal even in Ada 95, because the intermediate result has no uniquely determined type.
Also note that if we have procedure Display(V : in Voltage); then in Ada 95 we can write
Display(V2*V3);because the type of V2*V3 is uniquely determined by the procedure call.
Ada.Text_IO contains a generic package Fixed_IO for I/O of fixed point types.
Ada 95 also provides modular types. These are unsigned integers, and the arithmetic is performed modulo a specified number so that overflow can never occur. For example, in Ada 95 we can write:
type Unsigned_Byte is mod 256;
The modulus doesn't have to be a power of two, but it often is. If we now declare A : Unsigned_Byte := 100; and B : Unsigned_Byte := 200; then the result of A + B will be 300 mod 256, or 44.
In Ada 95, the package Interfaces is supplied, and it defines a modular type for each signed integer type. For example, an implementation of Ada 95 might define the types Unsigned_8, Unsigned_16, and Unsigned_32. Type Unsigned_8 corresponds to the example Unsigned_Byte above.
When we declare a variable in Ada, we give its type. But when we declare a constant, we may or may not give its type. For example,
L : constant Integer := 30; M : constant := 1000; E : constant Float := 2.718281828; Pi : constant := 3.141592654;
Also, when we write a number, such as 3.0 or 29_999, we usually don't qualify it with a type (for example, we usually don't write Float'(3.0)).
Suppose an implementation of Ada provides types Integer, Long_Integer, Float, and Long_Float. How can Ada determine the types of M, Pi, 3.0, and 29_999? M and 29_999 are said to be of type universal_integer; they can assume any integer type as required. Pi and 3.0 are said to be of type universal_real and can assume any floating or fixed point type as required.
We can't explicitly declare objects to be of universal types. However, we can write
M : constant := 1000; Pi : constant := 3.141592654; I : Integer; LI : Long_Integer; F : Float; LF : Long_Float; ... I := M; LI := M; I := 29_999; LI := 29_999; F := Pi; LF := Pi; F := 3.0; LF := 3.0;and in each case the constant assumes the correct type. The result of multiplying or dividing two numbers of a fixed point type is said to be of type universal_fixed. As we pointed out, this result must be converted to some numeric type before it can be stored.
Most of the attributes that produce integer results, like Pos, are of type universal_integer. For example, with the declarations above, we could write
I := Character'Pos('A'); LI := Character'Pos('A');
QuestionWhich one of the following declarations is illegal?
type Rate is digits 6
No, number 1 is legal. A user defined floating point type need not have a range constraint.
type Distance is digits 6 range 0.0 .. 1.0E6;
No, number 2 is legal. A user defined floating point type may have a range constraint.
type Current is delta 0.1;
You're right! A fixed point type declaration must have a range constraint.
type Temp is delta 0.05 range -200.0 .. 450.0;
No, number 4 is legal. A fixed point type declaration must have a range constraint.