Previous | Next | Table of Contents | Index | Program List | Copyright

2.10 Data Structures: Introduction to Data Types and Expressions

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.

Character Data Type

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
Reversing Three Letters

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.

Float Data Type

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.

Integer Data Type

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.

Expressions with Several Operators

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 ** 2
is equivalent to
    Pi * (R ** 2)
and not
    (Pi * R) ** 2

PROGRAM STYLE
Using Parentheses to Write Expressions You Can Understand

Ada has many operators; you will study most of them in this book. The compiler follows very systematic rules (known formally as precedence and association rules) in evaluating complicated expressions with many operators; these are spelled out in Chapter 7. The compiler "knows exactly what it is doing" and will always get a result that is correct by those rules.

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.

Using Integer Objects

The case study below gives an example of manipulating type Integer objects in Ada.


Case Study: Finding the Value of a Coin Collection

PROBLEM SPECIFICATION

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.

ANALYSIS

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.

DESIGN

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. TotalCents is 5 times Nickels plus Pennies.     

Step 3 Refinement:

    3.1. Dollars is the integer quotient of TotalCents and 100.

    3.2. Change is the integer remainder of TotalCents and 100.

Algorithm with Refinements

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.

TEST PLAN

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 Plan for Coin Collection

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.

IMPLEMENTATION

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
Finding the Value of a Coin Collection

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.


Literals and Tokens

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
Some Valid and Invalid 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.

Exercises for Section 2.10

Self-Check

  1. Evaluate the following expressions with 7 and 22 as operands.
         22 / 7       7 / 22        22 REM 7        7 REM 22    
    
    Repeat this exercise for the pairs of integers:
         15, 16         3, 23           4, 16
    
  2. Given the declarations

    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);            
    
  3. If we assume A is 5, B is 2, and Y is 2.0, what values are assigned by the legal statements in Exercise 2?
  4. Assume that you have the following integer variables:
    	Color, Lime, Straw, Yellow, Red, Orange
    
    and the following floating-point variables:
    	Black, White, Green, Blue, Purple, Crayon   
    
    Evaluate 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; 
    
  5. Let 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 
    

Previous | Next | Table of Contents | Index | Program List | Copyright

Copyright © 1996 by Addison-Wesley Publishing Company, Inc.