Listing 2 shows the generic INTEGER_UNITS package. Take some time here to read through it before we discuss it.
There are three generic parameters. The first is the integer type. The four logical choices for this parameter are integer, Integer_8, Integer_16, and Integer_32. Of these, Integer_32 is the one I generally use. I would use the others only in those applications where 1) the range of values is small enough to be represented by 8 or 16 bits, 2) there is a significant speed advantage associated with a smaller size, and 3) speed is important to that application.
Notice that I didn't make Integer_32 the default data type. That's because Ada doesn't allow a default for a generic type. Notice, too, that I can't instantiate this package for type float because the type is range <>. The range <> must always be replaced with an integer type.
The other two generic parameters are MIN and MAX. I expect that most of the time you will want the full range of values so I made the default range as large as possible. There are times when you may wish to restrict the range, and the MIN and MAX parameters give you the option of doing that.
There are three type conversion functions named Type_Convert, "+", and "-". They all do the same thing. They all add units to a pure number. That is, they change "5" to "5 feet". Assume that DISTANCE has been declared to be of type Feet, where Feet has been derived from an instantiation of this package, it would not be legal to say DISTANCE := 5;. That's because 5 is a universal_integer, and DISTANCE is derived from a private type. The private type happens to be an integer, but that is irrelevant. Ada sees them as different types, and that's exactly what I want. I want to make a distinction between Feet and pure numbers.
Somehow we have to be able to jump the barrier between Feet and pure numbers. The Type_Convert function does that. We can say DISTANCE := Type_Convert(5); and Ada will know that we mean 5 feet. I usually use the long phrase Type_Convert because I want to make it obvious that I am dimensioning a number. Sometimes, however, that makes a program line awfully long, and distracts from other things in the line that I feel are more important. In those cases I use the "+" or "-" operator to do the same thing. (Note: the Meridian AdaVantage version 2.1 compiler sometimes has trouble differentiating the + type convert from the unary + operator. It generates an error message, which is easily eliminated by replacing the + with Type_Convert.)
There is one other type conversion routine, called Dimensionless. It is the opposite of the three routines we have just discussed because it removes dimensional units instead of adding them. I'm going to delay the discussion of this function for a moment because it will make more sense after we have discussed dimensioned arithmetic.
Most of the arithmetic functions are self-explanatory. It should be obvious why you need the common operators like addition and subtraction, so let's skip over them. Things don't get interesting until we get down to the multiplication and division operators.
Conspicuously absent is function "*"(LEFT, RIGHT : Units) return Units;. That's because the product of two dimensioned quantities has different dimensions. (5 feet X 2 feet is not 10 feet, it's 10 square feet.) Multiplication is legal only when one of the numbers is a pure (dimensionless) number. (5 times 2 feet = 10 feet.) The pure number can be on the left or right side of the multiplication operator, so there are two definitions of multiplication of a dimensioned quantity times a pure number.
There are three division operators. The first division operator divides a dimensioned quantity by a dimensionless number, yielding a dimensioned result. (For example, 10 feet / 5 = 2 feet.) Ada's predefined division operator for integer types truncates toward zero, rather than rounding. `Therefore, 9 feet / 5 = 1 foot.
Unlike multiplication, division isn't symmetrical. Dividing by a dimensioned quantity changes the units. (1_000_000 / 1 Second = 1 Mega_Hertz.) That's why there isn't a mixed division operator with Integer_type on the left and Units on the right, as there is for multiplication.
The second division operator divides two dimensioned quantities (feet/feet, for example) and returns a dimensionless result. Just like Ada's predefined integer division, the result is truncated toward zero rather than rounding.
It is possible that you would like to get an exact ratio. The third division operation does that. The third division operation can be used to divide 10 feet by 3 feet to determine the ratio of the lengths is 3.333 to 1. If the predefined type float doesn't have enough digits of precision for you (which I think is unlikely), the Dimensionless function (described later) can be used to get more precise results.
If you want a division routine that rounds instead of truncating, use the third division routine to get the exact ratio and do an explicit type conversion to an integer type. (Explicit integer type conversions automatically round the result for you.) For example, if X and Y are of type Feet, INT is type integer, and F is type float, you can say F := X/Y; INT := integer(F);.
Rem and mod and all the relational operators work just like you would expect them to, so lets skip down to the Dimensionless function.
The Dimensionless function is the inverse of Type_Convert. It converts dimensioned quantities to pure numbers. It should be used with caution because it defeats the strong type checking we have worked so hard to achieve. Normally you will use it inside a special arithmetic function. For example, if you want to write a function that divides objects of type Feet by objects of type Seconds and produces a result in type Feet_per_sec, you will have to use Dimensionless to convert to pure numbers for the intermediate calculations and then use Type_Convert to change the result into Feet_per_sec. Figure 7 shows how to do this. The Dimensionless function could also be used to find a high precision ratio, as in Figure 8.
One of the most common uses for the Dimensionless function is for output. You can't instantiate the TEXT_IO package INTEGER_IO for any dimensional data type, such as Feet, because Feet is a private type, not an integer type. (I wouldn't instantiate INTEGER_IO even if I could, but that's a story we will save for the section on user interfaces.) A good way to print a dimensioned integer is shown in Figure 9. An even fancier way is shown in Figure 10. (These examples use the IMAGE attribute, but you will see something even better than the IMAGE attribute in the ASCII_UTILITIES package.)
Let's return to Listing 2 again. This is one of the rare instances where I put the package body in the same file as the package specification. Normally I separate the package body from the specification. I can't always do that with generic packages. The Ada language specification expressly allows vendors to require all generic parts (bodies and subunits) to be in a single file, and some vendors have taken advantage of that. That's a real nuisance for programmers, but it apparently makes it much easier for vendors to implement generics.
The package body is trivial, even if lengthy. In general, each function body just converts to the Integer_type, does the operation, and converts the output to the appropriate type.
Generic packages need to be instantiated before they are used. I instantiated the package for 32-bit integers and put it in a library, and called its instantiation DIM_INT_32. You've seen it used in five of the last six figures. Now it is time to see the package itself. Its simple listing is given in Listing 3. It uses integers with 32-bit range regardless of the target computer. Since I did not include values for MIN and MAX, the default values (Integer_32'FIRST and Integer_32'LAST) are used.