The predefined data types Integer
, Natural
,
Positive,
and Float
are used to represent numeric
information. Integer
variables are used to represent data such as
exam scores that are whole numbers; Float
variables are used to
represent numeric data that may have a fractional part. The subtypes
Natural
and Positive
are used to represent integer
values that cannot sensibly be negative; a Natural
value is
allowed to be zero; a Positive
value is not.
Float
could, in theory, be
used for all numerical values. There are two important reasons why, in
practice, we do not do this.
First, it is always best to use the most appropriate type for representing the values in a program. This not only makes the program easier for the reader to understand but also makes it possible for the compiler to ensure that the values assigned to a variable are appropriate values and that the operations performed on them are appropriate operations.
Another reason for not using Float
values exclusively is that
on many computers operations involving integers are faster and less storage
space is needed to store integers. Also, operations with integers are always
precise, whereas there may be some loss of accuracy when dealing with
floating-point values.
These differences result from the way floating-point numbers and integers
are represented internally in memory. All data are represented in memory as
binary sequences, sequences of 0s and 1s. However, the binary sequence
stored for the Integer
value 13 is not the same as the binary
sequence stored for the Float
value 13.0. The actual internal
representation used is computer-dependent, but it will normally have the format
shown in Fig. 7.1. In some computers, floating-point format uses more bits than
integer format.
As Fig. 7.1 shows, integers are represented by standard binary numbers. If you are familiar with the binary number system, you know that the integer 13 is represented as the binary number 01101.
Figure 7.1
Integer and Floating-Point Formats
Floating-point format is analogous to scientific notation. The storage area occupied by a floating-point number is divided into two parts: the mantissa and the exponent. The mantissa is a binary fraction between 0.5 and 1.0 (-0.5 and -1.0 for a negative number). The exponent is a power of 2. The mantissa and exponent are chosen so that the formula
floating-point-number = mantissa × 2exponent
is correct.
Besides the capability of storing fractions, floating-point format can represent a range of numbers considerably larger than can integer format. For example, Program 7.1 shows how to find the range of integer and floating-point values provided by your Ada compiler.
Program 7.1
WITH Ada.Integer_Ada.Text_IO; WITH Ada.Float_Ada.Text_IO; WITH Ada.Text_IO; PROCEDURE First_Last IS ------------------------------------------------------------------------------ --| Displays smallest and largest values of Integer and Float --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ BEGIN -- First_Last Ada.Text_IO.Put(Item => "Smallest integer is "); Ada.Integer_Ada.Text_IO.Put(Item => Integer'First); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Largest integer is "); Ada.Integer_Ada.Text_IO.Put(Item => Integer'Last); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Bits in an integer "); Ada.Integer_Ada.Text_IO.Put(Item => Integer'Size, Width => 1); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Smallest float is "); Ada.Float_Ada.Text_IO.Put(Item => Float'First); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Largest float is "); Ada.Float_Ada.Text_IO.Put(Item => Float'Last); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Bits in a float "); Ada.Integer_Ada.Text_IO.Put(Item => Float'Size, Width => 1); Ada.Text_IO.New_Line; END First_Last;Sample Run
Smallest integer is -2147483648 Largest integer is 2147483647 Bits in an integer 32 Smallest float is -3.40282E+38 Largest float is 3.40282E+38 Bits in a float 32This program uses the attributes
'First
and 'Last
to
retrieve the smallest and largest integer and float values, and the
'Size
attribute to retrieve the number of bits used for each type
by the compiler. The sample run shows 32 bits for both types; this is typical
of current computers and compilers. Note that for the same number of bits (32),
the range of floating-point numbers is approximately -1038 ..
+1038 (a huge range), while the range of integers is only about
-109..+109.
A constant value appearing in an expression is called a
literal. In Ada, a Float
literal must have a decimal point
in it and at least one digit on either side of the point. A literal may also
have a decimal scale factor. For example, in the literal
2.998E+5
, the scale factor is 105; in the literal
3E4
, the scale factor is 104 (this is another way to
write the value 30,000). It is also possible in Ada to use underscores--not
commas-- to separate groups of digits, so that 30_000
is a valid
Integer
literal.
Type of an Expression
The type of an expression is determined by the type of its operands, and all operands of an expression must be the same type. For example, in the expression
X + 3.5
the variable X
must be the same type
(Float
) as the literal 3.5; the expression is type
Float
. If I
is an Integer
variable, the
expression
10 - Iis type
Integer
. If I
is a
Float
variable, this expression is incorrect and will lead to a
compilation error.
There are two kinds of arithmeric operators: monadic and
dyadic. A monadic operator takes a single operand; a dyadic operator
takes two operands. In Ada, the three monadic operators are +, -, and
ABS
. If X
has an integer or float value,
+X
returns the same value (essentially it has no effect),
-X
negates the value (i.e., -(-X)=X
), and ABS
X
returns the absolute value (e.g., ABS 3 = ABS (-3) = 3
).
The four dyadic arithmetic operators +, -, *, and / can be used with
integer or floating-point operands. The operands must both be
Float
(or subtypes of Float
) or both be
Integer
(or subtypes of Integer
). The division
operator /
deserves special consideration. If the operands of a
division operation are floating-point values, the full result is kept and is
also floating point. If the operands of division are integer values, the result
is an integer equal to the truncated quotient of M
divided by
N
(i.e., the integer part of the quotient). For example, if
M
is 7 and N
is 2, the value of M/N
is
the truncated quotient of 7 divided by 2 or 3. On the other hand, if
X
is 7.0 and Y
is 2.0, then X/Y
is 3.5.
Example 7.1
Table 7.1 shows some examples of valid and invalid expressions involving the integer and floating-point division operators. For integer division, the result is always 0 when the magnitude of the first operand is less than the magnitude of the second operand.
Table 7.1
The Division Operators
3 / 15 = 0 3 / -15 = 0 3.0 / 15.0 = 0.2 15 / 3 = 5 15 / -3 = -5 15 / 3.0 is invalid (mixed types) 16 / 3 = 5 16 / -3 = -5 16.0 / 3.0 = 5.333... 17 / 3 = 5 -17 / 3 = -5 -17.0 / 3.0 = -5.667... 18 / 3 = 6 -18 / -3 = 6 18.0 / 3.0 = 6.0
The remainder operator, REM,
can also be used with
integer operands. The expression A REM B
is equal to the remainder
of A
divided by B
, if A
and
B
are both positive. The following relations are satisfied by the
REM
operator:
A REM (-B) = A REM B (-A) REM B = -( A REM B)
For the MOD
operation, the following identity
holds:
A MOD B = ( A + B) MOD B
Table 7.2 shows some typical results for the integer division
and REM
operators.
Table 7.2.
Results of Integer Division and REM
Operators
A B A/B A REM B A B A/B A REM B 10 5 2 0 -10 5 -2 0 11 5 2 1 -11 5 -2 -1 12 5 2 2 -12 5 -2 -2 13 5 2 3 -13 5 -2 -3 14 5 2 4 -14 5 -2 -410 -5 -2 0 -10 -5 2 0 11 -5 -2 1 -11 -5 2 -1 12 -5 -2 2 -12 -5 2 -2 13 -5 -2 3 -13 -5 2 -3 14 -5 -2 4 -14 -5 2 -4
Note that ABS
and REM
are reserved
words that represent operators, and not function names. Thus the expression
ABS X
is correct without parentheses. In the expression
ABS(-3)
, the parentheses denote that the operator ABS
is applied after the operator -
.
Example 7.2
Program
7.2 displays each digit of its input value Decimal
in reverse
order (e.g., if Decimal
is 738, the digits printed are 8, 3, 7).
This is accomplished by displaying each remainder (0 through 9) of
Decimal
divided by 10; the integer quotient of
Decimal
divided by 10 becomes the new value of
Decimal
.
Program 7.2
WITH Ada.Text_IO; WITH Robust_Input; WITH Ada.Integer_Ada.Text_IO; PROCEDURE Display_Digits IS ------------------------------------------------------------------------ --| Displays the digits of a nonnegative integer in reverse order. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ Base : CONSTANT Natural := 10; -- number system base Decimal : Natural; -- original number Digit : Natural; -- each digit BEGIN -- Display_Digits Robust_Input.Get(Item=>Decimal, MinVal=>0, MaxVal => Natural'Last); -- Find and display remainders of Decimal divided by 10 Ada.Text_IO.Put(Item=> "The digits in reverse order are "); WHILE Decimal /= 0 LOOP -- invariant: -- Decimal in pass i is (Decimal in pass i-1) / Base (for i > 1) -- and Digit in pass i is (Decimal in pass i-1) REM Base (for i > 1) -- and Decimal >= 0 Digit := Decimal REM Base; -- Get next remainder Ada.Integer_Ada.Text_IO.Put(Item => Digit, Width => 2); Decimal := Decimal / Base; -- Get next quotient END LOOP; -- assert: Decimal is zero Ada.Text_IO.New_Line; END Display_Digits;Sample Run
Enter an integer between 0 and 2147483647 > -5 Value entered is out of range. Please try again. Enter an integer between 0 and 2147483647 > x Value entered not an integer. Please try again. Enter an integer between 0 and 2147483647 > 111222333444555 Value entered not an integer. Please try again. Enter an integer between 0 and 2147483647 > 54321 The digits in reverse order are 1 2 3 4 5The input value
Decimal
is used as the loop control variable. Within
the WHILE
loop, the REM
operator is used to assign to
Digit
the right-most digit of Decimal
, and integer
division is used to assign the rest of the number to Decimal
. The
loop is exited when Decimal
becomes 0. Table 7.3 shows a trace of
the loop execution for an input value of 43. The digits 3 and 4 are displayed.
Table 7.3
Trace of Execution of DisplayDigits
Statement Decimal Digit Effect WHILE Decimal /= 0 LOOP 43 43 /= 0 is true Digit := Decimal REM Base 43 3 Remainder is 3 Ada.Integer_Text_IO.Put (Item=>Digit, Width=>1); 3 Display 3 Decimal := Decimal / Base; 4 Quotient is 4 WHILE Decimal /= 0 LOOP 4 4 /= 0 is true Digit := Decimal REM Base 4 4 Remainder is 4 Ada.Integer_Text_IO.Put (Item=>Digit, Width=>1); 4 Display 4 Decimal := Decimal / Base; 0 Quotient is 4 WHILE Decimal /= 0 LOOP 0 0 /= 0 is false - exit
Recall from
Section
2.10 that Ada provides one more dyadic operator, exponentiation,
represented by **
. An expression X**M
means "raise
X
to the M
th power"; that is, multiply X
by itself M
times. The left operand of **
can be an
integer or floating-point value; the right operand must be an integer value.
Further, if the left operand is an integer, the right operand must not be
negative because then the result would not be an integer value.
Multiple-Operator Expressions, Revisited
Often a problem requires writing an expression that contains more
than one operator, as discussed in
Section
2.10. In such a case, it is always wise to use parentheses to show exactly
which operations apply to which operands. However, to make the result of an
expression predictable even if the programmer omits the parentheses,
programming languages, including Ada, provide rules for the order of execution
of operations. These are called precedence and associativity
rules. For example, in the expression A + B * C
, is *
performed before +
or after? Is the expression X / Y *
Z
evaluated as ( X / Y) * Z
or X / ( Y * Z)
?
Understanding these rules will help you understand expressions better.
Some expressions with multiple operators are
1.8 * Celsius + 32.0 ( Salary - 5000.00) * 0.20 + 1425.00where
Celsius
and Salary
are
Float
variables. In both these cases, the algebraic rule that
multiplication is performed before addition is applicable. The use of
parentheses in the second expression ensures that subtraction is done first.
The Ada rules for expression evaluation below are based on standard algebraic
rules.
a. All parenthesized subexpressions are evaluated first. Nested parenthesized subexpressions are evaluated inside out, with the innermost subexpression evaluated first.
b. Operator precedence--Arithmetic operators in the same subexpression are evaluated in the following order:
**,ABS first *, /, REM next +, - ( monadic) next +, - ( dyadic) last
c. Left associative--Operators in the same subexpression and at the
same precedence level (such as +
and -
, or
*
and /
) are evaluated left to right.
Note that in Ada certain combinations of operators require
parentheses. For example, A**B**C
is undefined according to Ada
syntax rules; you must write either A**( B**C)
or
( A**B)**C
.
Example 7.3:
The formula for the area of a circle
a = pi × r2
can be written in Ada as
Area := Pi * Radius ** 2 ;where
Pi
is the constant we have seen before. The
evaluation tree for this formula is shown in Fig. 7.2. In this tree, the
arrows connect each operand with its operator. The order of operator evaluation
is shown by the number to the left of each operator; the rules that apply are
shown to the right.
Figure 7.2
Evaluation Tree for Area := Pi * Radius ** 2
Example 7.4:
The formula for the average velocity, v, of a particle traveling on a line between points p1 and p2 in time t1 to t2 is
( p2 - p1) v = -------- ( t2 - t1)
This formula can be written in Ada as
V := ( P2 - P1) / (T2 - T1);
It is evaluated as shown in Fig. 7.3.
Figure 7.3
Evaluation Tree for Average Velocity
Example 7.5:
Consider the expression
Z - ( A + B / 2) + W * Ywhich contains
Integer
variables only. The
parenthesized subexpression ( A + B / 2)
is evaluated first (rule
a) beginning with B / 2
(rule b). Once the value of B /
2
is determined, it can be added to A
to obtain the value
of ( A + B / 2)
. Next the multiplication operation is performed
(rule b) and the value for W * Y
is determined. Then the value of
( A + B / 2)
is subtracted from Z (rule c), and finally this
result
is added to W * Y
. Fig. 7.4 gives an
evaluation tree.
Figure 7.4
Evaluation Tree for Z - (A + B / 2) + W * Y
Writing Mathematical Formulas in Ada
There are two problem areas in writing a mathematical formula in Ada;
one concerns multiplication and the other concerns division. In everyday
algebra, multiplication is often implied in a mathematical formula by writing
the two items to be multiplied next to each other, for example, a =
bc. In Ada, however, the *
operator must always be used to
indicate multiplication, as in
A := B * C
The other difficulty arises in formulas involving division. We normally write the numerator and denominator on separate lines:
( y - b) m = ------- ( x - a)
In Ada, all assignment statements must be written in a linear form; consequently, parentheses are often needed to enclose the numerator and the denominator and to clearly indicate the order of evaluation of the operators in the expression. The formula above would be written as
M := ( Y - B) / ( X - A);
Example 7.6
Table 7.4 illustrates how several mathematical formulas can be
written in Ada. Assume all variables except j are Float
.
Table 7.4
Mathematical Formulas and Their Corresponding Ada Expressions
Mathematical Formula Ada Expression 1. b2 - 4ac B ** 2 - 4.0 * A * C 2. a + b - c A + B - C 3. ( a + b) ------- ( A + B) / ( C + D) ( c + d) 4. 1 -------- 1.0 / ( 1.0 + A ** 2) ( 1 + a2) 5. a × -( b + c) A * (-( B + C)) 6. xj X ** J
The points illustrated are summarized as follows:
*
where needed (1).
**
whose right operand must be Integer
even if its
left operand is Float
. Thus the exponentiation in (1), (4), and
(6) is correct.
Example 7.7
This example shows the use of the monadic operator ABS
,
which computes the absolute value of its operand. If the value of
X
is -3.5, the statement
Y := 5.7 + ABS( X + 0.5)assigns a value of 8.7 to the
Float
variable
Y
. The execution of this statement is traced below.
1. The expression argument (X + 0.5
) is evaluated as -3.0.
2. The ABS
operator returns the absolute value of its operand
(3.0).
3. The sum of 5.7 and the function result (3.0) is assigned to
Y
(8.7).
An expression involving floating-point operands can be assigned to a
variable only of type Float
(or a subtype thereof). An expression
involving integer operands can be assigned to a variable of type
Integer
(or a subtype thereof). As discussed in Section 5.9, an
attempt to assign a value of the wrong type to a variable will result in a
compilation error; an attempt to assign an out-of-range value to a variable
(e.g., a negative expression result to a Positive
variable) will
result in Constraint_Error
being raised.
Conversions Among Numeric Types
Ada does not allow mixing types in an expression (except in the case
of exponentiation, as discussed above). However, Ada does provide a means for
performing explicit conversion of a value of one type into a value of
another. Specifically, Ada allows explicit conversion of float values to
integer values and vice versa. This is done using a function-call syntax, where
the name of the new type is used as the function. The result of this "function
call" is of the new type, unless the result is out of range, in which case
Constraint_Error
is raised as usual.
An integer value always has an exact equivalent in floating-point form, but a floating-point value does not always have an exact integer equivalent. Ada therefore rounds such a conversion to the nearest integer value, rounding away from zero if the float quantity is exactly halfway between two integers.[1]
Suppose we have the following declarations:
F: Float; N: NonNegFloat; I: Integer; P: Positive; T: Natural;
Here are some conversions that can be done:
F := Float( I); -- always possible N := Float( P); -- always possible I := Integer( F); -- always possible; result is rounded I := Integer( N); -- always possible, result is rounded N := NonNegFloat( I); -- raises Constraint_Error if I is negative T := Natural( F); -- raises Constraint_Error if F is negative I := Integer( 5.49); -- result is 5 I := Integer( 5.51); -- result is 6 I := Integer( 5.5); -- result is 6 I := Integer( -5.3); -- result is -5 I := Integer( -5.6); -- result is -6 I := Integer( -5.5); -- result is -6
Conversion between two subtypes of Integer
or two
subtypes of Float
is always possible and will succeed if the
result is in range. If I
happens to be -57
, for
example, then
T := Natural( I);
will raise Constraint_Error
.
Example 7.8
If NumItems
is type Positive
and
SumOfItems
is type Float
, the expression
SumOfItems / Float( NumItems)divides the value of
SumOfItems
by the
floating-point equivalent of NumItems
. This expression is used in
the assignment statement below to store the "average value" in
Average
:
Average := SumOfItems / Float( NumItems);
Note that the expression
SumOfItems / NumItemsis invalid because the types in the expression do not agree.
PROGRAM STYLE
Explicit Type Conversion
In addition to the integer and floating-point types we use so much,
Ada provides a third kind of numeric type, the fixed-point type. We do
not make much use of fixed-point types in this book, but one particular
predefined fixed-point type is important. Type Duration
is a
fixed-point predefined type and is used by package Ada.Calendar
.
Whereas Ada.Calendar.Time
represents time of day ("what
time is it now?"), Duration
represents elapsed time ("how
long before the train leaves?"). A duration value of 1.0 represents the passage
of exactly one second; a value of 0.1 represents the passage of a tenth of a
second. Package Ada.Calendar
provides a subtype of
Duration
called Day_Duration
as follows:
SUBTYPE Day_Duration IS Duration RANGE 0.0 .. 86_400.0;whose range is chosen to span exactly one day, because 86,400 is the number of seconds in 24 hours. Package
Ada.Calendar
also
provides a function to retrieve, from a value of type
Ada.Calendar.Time
, the number of seconds since midnight on the
given day:
FUNCTION Seconds ( T: Time) RETURN Day_Duration;
This function goes along with the Year
,
Month
, and Day
functions we used in Section 3.6. All
you need to know about Duration
and Day_Duration
values is that they are much easier to work with if they are first converted to
type Float
.
Example 7.9
Program
7.3 displays the time of day in hh:mm:ss
form, using European
or military 24-hour time.
Program
7.3
Time of Day
WITH Ada.Text_IO; WITH Ada.Integer_Ada.Text_IO; WITH Ada.Calendar; PROCEDURE Time_of_Day IS ------------------------------------------------------------------------ --| Displays the current time in hh:mm:ss form, 24-hour clock --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ TYPE DayInteger IS RANGE 0..86400; CurrentTime : Ada.Calendar.Time; SecsPastMidnight : DayInteger; -- could be larger than 32767 MinsPastMidnight : Natural; Secs : Natural; Mins : Natural; Hrs : Natural; BEGIN -- Time_of_Day CurrentTime := Ada.Calendar.Clock; SecsPastMidnight := DayInteger(Ada.Calendar.Seconds(CurrentTime)); MinsPastMidnight := Natural(SecsPastMidnight/60); Secs := Natural(SecsPastMidnight REM 60); Mins := MinsPastMidnight REM 60; Hrs := MinsPastMidnight / 60; Ada.Text_IO.Put(Item => "The current time is "); Ada.Integer_Ada.Text_IO.Put (Item => Hrs, Width => 1); Ada.Text_IO.Put (Item => ':'); IF Mins < 10 THEN Ada.Text_IO.Put (Item => '0'); END IF; Ada.Integer_Ada.Text_IO.Put (Item => Mins, Width => 1); Ada.Text_IO.Put (Item => ':'); IF Secs < 10 THEN Ada.Text_IO.Put (Item => '0'); END IF; Ada.Integer_Ada.Text_IO.Put (Item => Secs, Width => 1); END Time_of_Day;Sample Run
The current time is 12:40:17This program uses the package Ada.Calendar to find the time of day. Recall that
Ada.Calendar.Clock
returns a
value of type Ada.Calendar.Time
. The statement
CurrentTime := Ada.Calendar.Clock;finds the value of current date/time; the function call
.. := Ada.Calendar.Seconds( CurrentTime);returns the value of seconds since midnight as a
Duration
value. Since Ada does not provide many arithmetic
operations to deal with Duration
values, it is easier to convert
this value to integer form. Now this value could be as large as 86,400 (the
number of seconds in a day). It's risky to use the predefined
Integer
type, because the standard allows Integer
to
be a 16-bit type, which means that Integer'Last
could be as small
as 32767. This means that a time late in the day (>32767) could cause
Constraint_Error
to be raised on the conversion to
Integer
.
Because most compilers actually use 32 bits, we could take a chance. However, we can be absolutely sure by defining a new integer type. In this book, we will not do this very much, but it's interesting to note that Ada allows it. We write
TYPE DayInteger IS RANGE 0..86400;which informs the compiler to create an entirely new integer-valued type with the given range. This is not a subtype, but a new type that's incompatible with
Integer
unless we do a
conversion.
This may seem like a lot of trouble just to be sure that we have a type with a full-day range, but this kind of situation sometimes arises in real projects, and it's nice to know that Ada has a solution.
Now, declaring
SecsPastMidnight: DayInteger;
we can proceed to write
SecsPastMidnight := DayInteger( Ada.Calendar.Seconds(CurrentTime));
Note how we converted the Duration
value. A
fixed-point type can be fractional; since we are not interested in fractions of
seconds, the rounding doesn't hurt us.
We now need to find the hours, minutes, and seconds in the current time:
MinsPastMidnight := Natural( SecsPastMidnight/60); Secs := Natural( SecsPastMidnight REM 60); Mins := MinsPastMidnight REM 60; Hrs := MinsPastMidnight / 60;
As an example of these calculations, suppose that the current
time is 11:55:20 P.M. Knowing that an hour has 3600 seconds and a minute 60, we
can easily calculate the value that Calendar.Seconds
returns, the
number of seconds past midnight, as
( 3600 × 23) + ( 60 × 55) + 20 = 82800 + 3300 + 20 = 86120
We now have to go back the other way, extracting hours, minutes, and
seconds. The number of minutes past midnight is 86120/60, or 1435 (integer
division!); the number of seconds is 86120 REM
60, or 20, and so
on.
F: Float; N: NonNegFloat; I: Integer; T: Natural;and that
F
is -3.7
, and
I
is -5
. Describe the result of each of the following
assignment statements:
F := Float( I); I := Integer( F); I := Integer( N); N := NonNegFloat( I); T := Natural( F); I := Integer( 6.2); I := Integer( 100.88); I := Integer( 9.5);
Ada.Calendar.Clock
and
converted the result to Integer
. At what time of day would it make
a difference whether Integer'Last
is 32767 or something larger?
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.