First, let's clarify exactly what is meant by a data type in a programming
language. A data type is a set of values and a set of operations on
those values. The data type of the object stored in a particular memory cell
determines how the bit pattern in that cell is interpreted. For example, the
same bit pattern can represent a type Integer
object, a type
Character
object, a type Float
object, or even a
program instruction. A predefined data type is a data type that is
predefined in the programming language (for example, Integer
,
Float
, and Character
). Besides the standard data
types, programmers can define their own data types in Ada. Indeed, defining our
own types will be an important part of our study, to be started in
Chapter
3.
Our first predefined type is Character
. We have already seen (
Program
2.2) that type Character
variables can be used to store any
single-character value. A type Character
value mentioned in a
program--a literal--must be enclosed in single quotes (for example,
'A'
); however, quotes are not used when character data are entered as
tokens. When the Ada.Text_IO.Get
procedure is used to read
character data into a type Character
variable, the next character
entered at the terminal is stored in that variable. The blank character is
entered by pressing the space bar; it is written in a program as the literal
' '
.
Example 2.7
Program
2.8 first reads and echos three characters entered at the keyboard. Next,
it prints them in reverse order enclosed in asterisks. Each character is stored
in a variable of type Character
; the character value
'*'
is associated with the constant Border
. The lines
Ada.Text_IO.Put (Item=>Third); Ada.Text_IO.Put (Item=>Second); Ada.Text_IO.Put (Item=>First);display the three characters in reverse order. As shown in the program output, each character value is printed in a single print position.
Program 2.8
WITH Ada.Text_IO; PROCEDURE Reverse_Letters IS ------------------------------------------------------------------------ --| Reverses the order of three input letters --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ Border : CONSTANT Character := '*'; First, Second, Third : Character; BEGIN -- Reverse_Letters Ada.Text_IO.Put(Item => "Enter 3 characters> "); Ada.Text_IO.Get(Item => First); Ada.Text_IO.Get(Item => Second); Ada.Text_IO.Get(Item => Third); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => Border); Ada.Text_IO.Put(Item => Third); Ada.Text_IO.Put(Item => Second); Ada.Text_IO.Put(Item => First); Ada.Text_IO.Put(Item => Border); Ada.Text_IO.New_Line; END Reverse_Letters;Sample Run
Enter 3 characters> FBI *IBF*
Several operations are defined for character values; the most obvious one is assignment. An assignment statement can be used to store a literal value into a character constant or variable or to copy the value of one character variable into another. Comparison operations on character values will be introduced in Chapter 7.
The standard data types in Ada represent familiar objects. For example, the
data type Float
is the set of real numbers (in the mathematical
sense) that can be represented on the computer. Every type Float
object in Ada is a real number; however, not all real numbers can be
represented in Ada or in any programming language. Some real numbers are too
large or too small or cannot be represented precisely due to the finite size of
a memory cell (more on this in
Chapter
7). The normal arithmetic operations for real numbers (+
, -,
*
, /
) and the assignment operation (:=
)
can be performed on type Float
objects in Ada. The payroll problem
in
Section 2.9 is an example of the use of objects of type Float
; so is
the metric conversion problem discussed in
Section
2.4.
The other predefined data types that represent numbers are
Integer
, Natural
, and Positive
. Type
Integer
objects in Ada correspond to the integers in mathematics
(e.g., -77, 0, 999, +999). However, because of the finite size of a memory
cell, not all integers can be represented in Ada, and every Ada compiler has
predefined positive and negative limits on type Integer
values.
These limits are not specified in the standard and are most commonly either
-32768 and +32767. (16-bit arithmetic) or -2147483648 and 2147483647 (32-bit
arithmetic). Type Natural
objects correspond to the nonnegative
integers (including 0); type Positive
objects correspond to the
positive integers (excluding 0).
Actually the types Natural
and Positive
are
subtypes of Integer
: Every positive integer is also an
integer. We will have many occasions to use subtypes in this book. Indeed, we
already have seen the definition of a subtype of our own. Recall that in
Section
2.9, in the payroll program, a line appeared reading
SUBTYPE NonNegFloat IS Float RANGE 0.0 .. Float'Last;This line is called a subtype definition; it is necessary because even though Ada supplies us with a predefined nonnegative integer type (
Natural
), it does not provide a similar predefined type for
nonnegative floating-point values (languages are not perfect). We will
introduce a discussion of subtypes in
Chapter
3 and revisit the subject frequently. For now, be aware that we will use
the subtype NonNegFloat
wherever nonnegative floating-point
quantities are necessary.
The basic distinction between type Float
and the three integer
data types is that a number with a decimal point and fractional part can be
stored in a type Float
object, but only whole numbers can be
stored in type Integer
, Natural
, and
Positive
objects. For this reason, objects of the last three types
are more restricted in their use. We often use them to represent a count of
items because a count must always be a nonnegative whole number.
What are the operations on integer values? The operations +
,
-
, and *
have obvious meanings of sum, difference,
and product, respectively. What about division? Dividing one integer by another
always gives an integer result, which is the "whole number," or quotient, part
of the division. So 3/2
gives a result of 1
,
14/4
gives a result of 3
, and 2/3
gives
a result of 0
. The fractional part, or remainder, is lost in the
division operation.
Because the remainder is lost in an integer division, Ada provides an
operation REM
that can be applied to two integers.
REM
gives the remainder in the division operation, as you would
compute it in a "long division." Here are some examples:
3 REM 2
is 1
(dividing 3 by 2 gives a quotient of
1 and a remainder of 1).
14 REM 4
is 2
(dividing 14 by 4 gives a quotient
of 3 and a remainder of 2).
2 REM 3
is 2
(dividing 2 by 3 gives a quotient of
0 and a remainder of 3).
One last operator merits discussion here: The operator **
is
used to represent exponentiation, or raising a value to a given power.
Given a variable X
whose current value is 3
,
X ** 2
is 9
(multiply 3
by
3
).
X ** 3
is 27
(multiply 3
by
3
by 3
).
X ** 4
is 81
(multiply 3
by
3
by 3
by 3
).
and so on.
Exponentiation is also defined to raise a floating-point value to a given
power. The power must be an integer, however. If Y
is a
floating-point variable with value 1.2, then
Y ** 2
is 1.44
(multiply 1.2 by 1.2).
Y ** 3
is 1.728
(multiply 1.2 by 1.2 times 1.2).
Y ** 1.5
is not allowed by the compiler.
Ada allows us to write expressions with many variables and operators; in fact,
there is no formal limit at all on the complexity of an expression. We must
therefore know the order in which the various parts of an expression are
evaluated. We'll take a systematic look at this in
Chapter
7. To give you some help in the meantime, let X
be
3
, Y
be 4
, and Z
be
7
. Here's how Ada will evaluate some assignments to the variable
W
:
W := X * Y + Z;will store (3 × 4)+7, or 19, in
W
. The result of the
multiplication is added to Z
. It is as though the expression were
written
W := (X * Y) + Z;which is also correct Ada and gets the same result. Now
W := Z + X * Y;stores 7 + (3 × 4) in
W
. Again the result of the multiplication is
added to Z
; this is equivalent to writing
W := Z + (X * Y);which, of course, is also correct Ada. Ada follows the basic rule that multiplications and divisions are done before additions and subtractions, in the absence of parentheses. For example,
W := X * (Y + Z);causes 3 × (4 + 7), or 33, to be stored in
W
. The parentheses force
the addition to be done first and the result to be multiplied by
Z
. Consider
W := X / Y + Z;which stores (3/4) + 7, or 7, in
W
(remember division of integers!), and
W := X / (Y + Z);which stores 3/(4 + 7), or 0, in
W
(again, dividing the integers here
gives 0).
Now suppose that we have two or more addition or subtraction operators in the
same expression. In this case, the operations are done in left-to-right order.
W := X - Y + Z;stores (3 - 4)+7 or 6 in
W
; the subtraction is done first. If we had
written
W := X - (Y + Z);the result in
W
would be 3 - (4 + 7), or -8. Again, the parentheses
force the addition to be done first. Be sure you understand why
W := X - Y - Z;and
W : = X - (Y - Z);store -8 and 6, respectively, in
W
. A similar left-to-right rule applies
to multiplication and division operators. Finally, exponentiation is done even
before multiplication or division, so the expression
Pi * R ** 2is equivalent to
Pi * (R ** 2)and not
(Pi * R) ** 2
PROGRAM STYLE
Using Parentheses to Write Expressions You Can Understand
However, a human writer or reader of a program many have trouble sorting out the order of execution of the operations in an expression with more than one or two operators, and the result can sometimes be unpleasantly surprising if the human sorts it out differently than the compiler does. Remembering the precedence and association rules is difficult and also unnecessary. You should instead use two very simple rules in writing an expression: Keep it as simple as you can, and use a lot of parentheses to indicate both to the compiler and to yourself what the intention of the expression is. Using extra parentheses will save you time in debugging; using too few parentheses to save writing effort is false economy.
The case study below gives an example of manipulating type Integer
objects in Ada.
Your little sister has been saving nickels and pennies for quite a while. Because she is getting tired of lugging her piggy bank with her whenever she goes to the store, she would like to trade in her collection for dollar bills and some change. In order to do this, she would like to know the value of her coin collection in dollars and cents.
To solve this problem, we must be given the count of nickels and the count ofpennies in the collection. The first step is to determine the total value of
the collection in cents. Once we have this figure, we can do an integer
division using 100 as the divisor to get the dollar value; the remainder of
this division will be the loose change that she should receive. In the data
requirements below, we list the total value in cents (TotalCents
)
as a program variable because it is needed as part of the computation process;
it is not a required problem output.
Data Requirements and Formulas
Problem Inputs:
Nickels : Natural
(the number of nickels)
Pennies : Natural
(the number of pennies)
Problem Outputs:
Dollars : Integer
(the number of dollars she should receive)
Change : Integer
(the loose change she should receive)
Additional Program Variables
TotalCents : Integer
(the total number of cents)
Relevant Formulas
One nickel equals five pennies.
The algorithm is straightforward and is displayed next. Initial Algorithm
1. Read in the count of nickels and pennies.
2. Compute the total value in cents.
3. Find the value in dollars and loose change.
4. Display the value in dollars and loose change.
Steps 2 and 3 may need refinement. Their refinement follows.
Step 2 Refinement
2.1. Step 3 Refinement:
3.1. 3.2. TotalCents
is 5 times Nickels
plus
Pennies
.Dollars
is the integer quotient of TotalCents
and 100.Change
is the integer remainder of TotalCents
and 100.
1. Read in the count of nickels and pennies.
2. Compute the total value in cents.
2.1. TotalCents
is 5 times Nickels
plus
Pennies
.
3. Find the value in dollars and loose change.
3.1. Dollars
is the integer quotient of TotalCents
and 100.
3.2. Change
is the integer remainder of TotalCents
and 100.
4. Display the value in dollars and loose change.
In addition to testing some typical values, there are several special cases in our test plan: zero nickels and/or zero pennies, and negative input values. Let's put the test plan in the form of a table, shown as Table 2.5.
Table 2.5
Test Case Nickels Pennies Reason Expected Result
1 30 77 typical $2.27 2 0 59 no nickels $0.59 3 13 0 no pennies $0.65 4 0 0 no coins $0.00 5 13 -5 negative ? 6 xyz 4 bad input ?
The last two cases test for out of range input (a negative number when a natural number is required) and "bad" input (letters instead of digits). The question marks indicate that we won't know the result until we run the test. It is important always to test programs with "bad" as well as "good" input: The programmer cannot control which keys will be pressed by the human user, and a program's behavior must always be predictable.
Program 2.9 shows the program. The statement
TotalCents := 5 * Nickels + Pennies;implements algorithm step 2.1 and the statements
Dollars := TotalCents / 100; Change := TotalCents REM 100;implement algorithm steps 3.1 and 3.2.
Note how a value of 1 for the Width
parameter is used to format
the displayed values so that they appear just next to the title text. Can you
explain why Width=>1
accomplishes this?
Program 2.9
WITH Ada.Text_IO; WITH Ada.Integer_Text_IO; PROCEDURE Coin_Collection IS ------------------------------------------------------------------------ --| Finds the value of a coin collection, --| given pennies and nickels --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ Pennies : Natural; -- input - number of pennies Nickels : Natural; -- input - number of nickels Dollars : Natural; -- output - value in dollars Cents : Natural; -- output - value in cents TotalCents : Natural; BEGIN -- Coin_Collection -- prompt user for number of nickels and pennies Ada.Text_IO.Put (Item => "How many nickels do you have? "); Ada.Integer_Text_IO.Get (Item => Nickels); Ada.Text_IO.Put (Item => "How many pennies do you have? "); Ada.Integer_Text_IO.Get (Item => Pennies); Ada.Text_IO.New_Line; -- compute total value in cents TotalCents := 5 * Nickels + Pennies; -- find the value in dollars and change Dollars := TotalCents / 100; Cents := TotalCents REM 100; -- display the value in dollars and change Ada.Text_IO.Put (Item => "Your collection is worth "); Ada.Integer_Text_IO.Put (Item => Dollars, Width => 1); Ada.Text_IO.Put (Item => " dollars and "); Ada.Integer_Text_IO.Put (Item => Cents, Width => 1); Ada.Text_IO.Put (" cents."); Ada.Text_IO.New_Line; END Coin_Collection;Sample Run, Case 1
How many nickels do you have? 30 How many pennies do you have? 77 Your collection is worth 2 dollars and 27 cents.Sample Run, Case 2
How many nickels do you have? 0 How many pennies do you have? 59 Your collection is worth 0 dollars and 59 cents.Sample Run, Case 3
How many nickels do you have? 13 How many pennies do you have? 0 Your collection is worth 0 dollars and 65 cents.Sample Run, Case 4
How many nickels do you have? 0 How many pennies do you have? 0 Your collection is worth 0 dollars and 0 cents.TESTING
This test run shows input of test cases 1 through 4 from the test plan. These results agree with the expected results. We defer the two error cases until the next section when we discuss errors in general.
Objects of a data type can be variables, constants, or literals. A
literal is a value that appears directly in a program. For example, a
Float
literal is a number that begins with a digit and contains a
decimal point (e.g., 0.112
, 456.0
,
123.456
). A type Float
literal may have a scale
factor, which is the capital letter E
followed by an optional
sign and an integer (e.g., 0.112E3
, 456.0E-2
). The
scale factor means "multiply the preceding real number by 10 raised to the
power appearing after the letter E
(e.g.,, 0.112E3
is
112.0
, 456.0E-2
is 4.56
). A
Float
literal may be preceded by a +
or
-
sign when it appears in a program. Examples of valid and invalid
Float
literals are shown in
Table
2.6.
Table 2.6
Float
Literals
Valid Float Literals Invalid Float Literals
3.14159 150 ( no decimal point) 0.005 .12345 ( no digit before .) 12345.0 12345. ( no digit after .) 15.0E-04 ( value is 0.0015) 15E-03 ( 15 invalid Float) 2.345E2 ( value is 234.5) 12.5E.3 ( .3 invalid exponent)The last valid literal in Table 2.6,
1.15E-3
, has the same value as 1.15 x 10-3 in
normal scientific notation where the exponent -3 causes the decimal point to be
moved left three digits. A positive exponent causes the decimal point to be
moved to the right; the +
sign may be omitted when the exponent is
positive.
The preceding example has concentrated on Float
literals;
Integer
, Character
, String
, and,
enumeration literals are also commonly used.
You might be wondering what the difference is between the terms literal and token. Conventionally, a sequence of characters representing a value is called a literal when it appears within the text of a program, and such a sequence is called a token when it is read from an input device or displayed on an output device.
One practical difference between literals and tokens is that Ada permits an
integer token to be entered when Ada.Float_Text_IO.Get
requires a value but does not allow an integer literal to appear in an
assignment statement to a Float
variable.
22 / 7 7 / 22 22 REM 7 7 REM 22Repeat this exercise for the pairs of integers:
15, 16 3, 23 4, 16
Pi : CONSTANT Float := 3.14159;
MaxI : CONSTANT Integer := 1000;
the Float
variables X
and
Y
, and the Integer
variables A
,
B
, and I,
indicate whether each of the following
assignments is valid, and, if so, what its value is. Assume that A is 3, B is
4, and Y is -1.0.
a. I := A REM B; j. I := (MaxI - 990) / A; b. I := (990 - MaxI) / A; k. X := A / Y; c. I := A REM Y; l. X := Pi ** 2; d. X := Pi * Y; m. X := Pi ** Y; e. I := A / B; n. X := A / B; f. X := A / B; o. I := (MaxI - 990) REM A; g. X := A REM (A / B); p. I := A REM 0; h. I := B / 0; q. I := A REM (MaxI - 990); i. I := A REM (990 - MaxI);
Color, Lime, Straw, Yellow, Red, Orangeand the following floating-point variables:
Black, White, Green, Blue, Purple, CrayonEvaluate each of the statements below given the following values:
Color
is 2, Black
is 2.5, Crayon
is -1.3, Straw
is 1, Red
is 3, Purple
is
0.3E1.
a. White := Crayon * 2.5 / Purple; b. Green := Black / Purple; c. Orange := Color / Red; d. Orange := (Color + Straw) / (2*Straw); e. Lime := Red / Color + Red REM Color ; f. Purple := Straw / Red * Color;
A
, B
, C
, and X
be
the names of four Float
variables and let I
,
J
, and K
be the names of three Integer
variables. Each of the statements below contains a violation of the rules for
forming arithmetic expressions. Rewrite each statement so that it is consistent
with these rules.
a. X := 4.0 A * C d. K := 3(I + J) b. A := AC e. X := 5A / BC c. I := 2 * -J f. I := 5J3
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.