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

10.4 System Structures: Writing an ADT for Money Quantities

In this section we develop an ADT for monetary quantities, which we shall call Currency. What is important about this ADT is that in writing operations for Currency values, we discover that not all operations make sense. An advantage of the ADT approach is that we can control the set of operations to allow only meaningful ones to be done.

REQUIREMENTS

We require a way to represent monetary values so as to ensure that calculations with these quantities make sense and are exact. Only sensible operations should be allowed. It is meaningful to compare, add, subtract, and divide monetary quantities but not to multiply them--$4.00/$2.00 is a dimensionless ratio 2.0, but $2.00 x $3.00 has no meaning. On the other hand, it is certainly sensible to multiply a currency value by a "normal" dimensionless quantity, for example, to find 25% of $150.00.

To understand the exact-result requirement, you must realize that not every fractional decimal value can be represented exactly as a binary floating-point quantity, and sometimes operations like addition and subtraction cause the result to be rounded off. While this approximation to the real numbers is often acceptable, it is unacceptable in monetary calculations--you would not be happy if the bank approximated your account balance.

ANALYSIS

We are asked to construct a software component providing a type and a set of operations. There are no specific problem inputs and outputs, but we shall need to provide input and output operations so that our user--again, another programmer--can write client programs that read and display currency values.

To ensure exact operations, we cannot simply use floating-point values. Because integer arithmetic is exact, we will represent currency as a pair of two nonnegative integer values, Dollars and Cents, and a Boolean value to indicate whether the currency value is positive or not. We will then be able to write an ADT that provides exact operations.

DESIGN

We now look at the important algorithms in currency calculations. We are allowing both positive and negative values and representing a currency value as a pair of integers. Given a currency quantity Q, denote its dollars and cents parts by Q.Dollars and Q.Cents, respectively; we carry the sign separately as a flag Q.Positive. First let us see how to convert a float value to a currency value:

Algorithm for Converting a Float F to a Currency Quantity Q

1. Q.Dollars is the integer part of ABS F; ABS means absolute value, as usual.

2. Q.Cents is 100 x (ABS F - Q.Dollars)

3. Q.Positive is True if and only if F >= 0.0

Note how the cents part of a currency value is calculated as the fractional part of the Float value, multiplied by 100.

Now let us look at key algorithms for adding and subtracting two positive currency values.

To Add Two Positive Currency Values Q1 and Q2 to produce Result:

1. Set TempCents to the sum of Q1.Cents and Q2.Cents

2. IF TempCents > 99, THEN we have a carry:

     3. Result.Cents is TempCents - 100

     4. Result.Dollars is Q1.Dollars + Q2.Dollars + 1

5. ELSE no carry:

     6. Result.Cents is TempCents

     7. Result.Dollars is Q1.Dollars + Q2.Dollars

END IF;

To Subtract Q2 from Q1 to Produce Result

1. IF Q1 < Q2 THEN

2. Result is negative:

     3. Interchange Q1 and Q2

END IF;

4. IF Q1.Cents < Q2.Cents THEN we need a borrow:

     5. Result.Cents is (100 + Q1.Cents) - Q2.Cents

     6. Result.Dollars is (Q1.Dollars - 1) - Q2.Dollars

7. ELSE no borrow:

     8. Result.Cents is Q1.Cents - Q2.Cents

     9. Result.Dollars is Q1.Dollars - Q2.Dollars

END IF;

Make sure you understand these algorithms; try some examples by hand to test yourself.

Program 10.7 shows the specification for this ADT package. The type Quantity is declared to be PRIVATE so that we can control all operations on values of this type. Note that we are also providing a subtype CentsType, which has range 0..99.

Program 10.7
Specification for Currency Package

PACKAGE Currency IS
------------------------------------------------------------------------
--| Specification of the abstract data type for representing
--| and manipulating Currency numbers.
--| All values of type Currency.Quantity are initialized to 0.0.
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  SUBTYPE CentsType IS Integer RANGE 0..99;
  TYPE    Quantity  IS PRIVATE;

  -- Operations 

  FUNCTION MakeCurrency (F : Float)    RETURN Quantity;
  -- constructor:
  -- Pre : F is defined
  -- Post: returns a Currency Quantity

  FUNCTION MakeFloat    (Q : Quantity) RETURN Float;
  -- constructor:
  -- Pre:  Q is defined
  -- Post: returns the value of Q in Float form
  
  FUNCTION Dollars   (Q : Quantity) RETURN Natural;
  FUNCTION Cents     (Q : Quantity) RETURN CentsType;
  FUNCTION IsPositive(Q : Quantity) RETURN Boolean;
  -- selectors:
  -- Pre:  Q is defined
  -- Post: Dollars returns the Dollars part of Q; Cents the Cents part
    
  FUNCTION "<" (Q1 : Quantity; Q2 : Quantity) RETURN Boolean;
  FUNCTION ">" (Q1 : Quantity; Q2 : Quantity) RETURN Boolean;
  FUNCTION "<="(Q1 : Quantity; Q2 : Quantity) RETURN Boolean;
  FUNCTION ">="(Q1 : Quantity; Q2 : Quantity) RETURN Boolean;
  -- inquiry operators:
  -- Pre : Q1 and Q2 are defined
  -- Post: return Q1 < Q2, Q1 > Q2, Q1 <= Q2, and Q1 >= Q2, respectively
   
  FUNCTION "+"  (Q  : Quantity) RETURN Quantity;
  FUNCTION "-"  (Q  : Quantity) RETURN Quantity;
  FUNCTION "ABS"(Q  : Quantity) RETURN Quantity;
  -- monadic arithmetic constructors:
  -- Pre:  Q is defined
  -- Post: return Q, -Q, ABS Q respectively
  
  FUNCTION "+"  (Q1 : Quantity; Q2 : Quantity) RETURN Quantity;
  FUNCTION "-"  (Q1 : Quantity; Q2 : Quantity) RETURN Quantity;
  FUNCTION "*"  (F  : Float;    Q  : Quantity) RETURN Quantity;
  FUNCTION "*"  (Q  : Quantity; F  : Float   ) RETURN Quantity;
  FUNCTION "/"  (Q1 : Quantity; Q2 : Quantity) RETURN Float;
  FUNCTION "/"  (Q  : Quantity; F  : Float   ) RETURN Quantity;
  -- dyadic arithmetic constructors:
  -- Pre : Q1 and Q2 are defined
  -- Post: these are the sensible arithmetic operators on Quantity.
  --   Note that multiplying two monetary values is not sensible.

PRIVATE

-- A record of type Quantity consists of a pair of Natural values
-- such that the first number represents the Dollars part
-- and the second number represents the Cents part.
-- The sign of a Quantity value is indicated by a Boolean field
-- called Positive.

  TYPE Quantity IS RECORD
    Positive: Boolean   := True;
    Dollars : Natural   := 0;
    Cents   : CentsType := 0;
  END RECORD; -- Quantity

END Currency;

Looking at the operations on the currency type, we see first that operators are provided to produce a currency quantity from its dollars and cents components and to convert in both directions between our currency type and Float values. The next group of operations are selectors to return the Dollars and Cents parts and an inquiry operator to determine whether or not a currency value is positive.

The next four operators are the usual comparison operations we saw in Ada.Calendar. Note that we can use predefined equality/inequality with no problem because two currency values are equal if and only if their dollars, cents, and signs are respectively equal. The comparison operators are followed by the three monadic arithmetic operators we saw in Rationals. Their meaning should be obvious.

The final six operators are interesting ones. Note that addition and subtraction are defined for currency values, as one would expect. But multiplication is defined only for a currency value and a Float value, not for two currency values. This is because the product of two currency values is meaningless, but finding, for example, 0.25 (which might represent 25%) of a currency value is indeed meaningful. The two multiplication operations allow the mixed operands to be presented in either order. Similarly, the division operations are meaningful ones: dividing one currency value by another gives a normal Float; dividing a currency value by a Float gives a currency value.

Defining operators as we have done here is called operator overloading. Recall the similar group of operators in Ada.Calendar; it makes no difference whether the operators are provided by a predefined package like Ada.Calendar or by a user-defined package like Currency. Operators are really nothing more than functions with an unusual syntax, appearing between their parameters instead of preceding them. Because function names can be overloaded, so can operator names. Operator overloading allows us to write operations that are mathematical in nature using the familiar mathematical symbols.

It is important to understand that Ada allows us to overload only those operator symbols already available in the language; we cannot, for example, define a new operator "?" because "?" is not already an operator in Ada. Also bear in mind that, for reasons beyond the scope of this book to explain, it is not possible under most circumstances to define our own operator "=". It is similarly prohibited (and will cause a compilation error) to overload "/=" and the two membership operators "IN" and "NOT IN".

The last part of the specification is, as usual, the PRIVATE part, in which the currency type is defined in full. Note that it is just a record with three fields and that all three fields are initialized as before.

IMPLEMENTATION

Now Program 10.8 gives the body for Currency. The key to understanding the operations is the first four function bodies. The first two, Add and Subtract, are not provided to client programs; they are there only to make writing the other operators more convenient for us.

Program 10.8
Body of Currency Package

PACKAGE BODY Currency IS
------------------------------------------------------------------------
--| Body of the abstract data type for representing
--| and manipulating Currency numbers.
--| All values of type Currency.Quantity are initialized to 0.0.
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: July 1995
------------------------------------------------------------------------

-- internal operations, not exported to the client

  SUBTYPE NonNegFloat IS Float RANGE 0.0 .. Float'Last;

  FUNCTION Add (Q1: Quantity; Q2: Quantity) RETURN Quantity IS
  -- Pre:  Q1 >= 0.0 and Q2 >= 0.0.
  -- Post: Returns the sum of Q1 and Q2.
  -- This is just an auxiliary routine used in "+" and "-" below.

    Result    : Quantity;
    TempCents : Natural;

  BEGIN -- Add

    TempCents := Q1.Cents + Q2.Cents;
    IF TempCents > 99 THEN -- we had a carry
      Result.Cents   := TempCents - 100;
      Result.Dollars := Q1.Dollars + Q2.Dollars + 1;
    ELSE
      Result.Cents   := TempCents;
      Result.Dollars := Q1.Dollars + Q2.Dollars;
    END IF;
    RETURN Result;

  END Add;

  FUNCTION Subtract (Q1: Quantity; Q2: Quantity) RETURN Quantity IS
  -- Pre:  Q1 >= 0.0 and Q2 >= 0.0.
  -- Post: Returns the difference of Q1 and Q2.
  -- This is just an auxiliary routine used in "+" and "-" below.

    Result    : Quantity;
    TempCents : Natural;

  BEGIN -- Subtract

    IF Q1 > Q2 THEN  -- Result is positive
      IF Q2.Cents > Q1.Cents THEN -- we need a borrow
        Result.Cents   := (100 + Q1.Cents) - Q2.Cents;
        Result.Dollars := (Q1.Dollars - 1) - Q2.Dollars;
      ELSE
        Result.Cents   := Q1.Cents   - Q2.Cents;
        Result.Dollars := Q1.Dollars - Q2.Dollars;
      END IF;
    ELSE             -- Result is negative
      Result.Positive := False;
      IF Q1.Cents > Q2.Cents THEN -- we need a borrow
        Result.Cents   := (100 + Q2.Cents) - Q1.Cents;
        Result.Dollars := (Q2.Dollars - 1) - Q1.Dollars;
      ELSE
        Result.Cents   := Q2.Cents   - Q1.Cents;
        Result.Dollars := Q2.Dollars - Q1.Dollars;
      END IF;
    END IF;
    RETURN Result;

  END Subtract;

  -- Exported Operators

  FUNCTION "+"(Q1 : Quantity; Q2 : Quantity) RETURN Quantity IS
  BEGIN
    IF Q1.Positive AND Q2.Positive THEN
      RETURN Add(Q1,Q2);
    ELSIF (NOT Q1.Positive) AND (NOT Q2.Positive) THEN
      RETURN -Add(-Q1, -Q2);
    ELSIF Q1.Positive AND (NOT Q2.Positive) THEN
      RETURN Subtract(Q1, -Q2);
    ELSE -- NOT Q1.Positive AND Q2.Positive;
      RETURN Subtract(Q2, -Q1);
    END IF;
  END "+";

  FUNCTION "-"(Q1 : Quantity; Q2 : Quantity) RETURN Quantity IS
  BEGIN
    RETURN Q1 + (-Q2);
  END "-";

  FUNCTION MakeCurrency (F : Float)    RETURN Quantity IS
    Result: Quantity;
    T: Float;
  BEGIN

    T := Float'Truncation(ABS F); -- get whole-number part
    Result := (Positive => True,
               Dollars =>  Natural(T),   -- just a type change
               Cents   =>  Natural(100.0 * (ABS F -  T)));

    IF F < 0.0 THEN
      Result.Positive := False;
    END IF;

    RETURN Result;
  END MakeCurrency;

  FUNCTION MakeFloat (Q : Quantity) RETURN Float IS
    Result: Float;
  BEGIN
    Result := Float(100 * Q.Dollars + Q.Cents) / 100.0;
    IF Q.Positive THEN
      RETURN Result;
    ELSE
      RETURN -Result;
    END IF;
  END MakeFloat;

  FUNCTION Dollars (Q : Quantity) RETURN Natural IS
  BEGIN 
    RETURN Q.Dollars;
  END Dollars;

  FUNCTION Cents (Q : Quantity) RETURN CentsType IS
  BEGIN 
    RETURN Q.Cents;
  END Cents;
  
  FUNCTION IsPositive(Q : Quantity) RETURN Boolean IS
  BEGIN 
    RETURN Q.Positive;
  END IsPositive;

  FUNCTION ">" (Q1 : Quantity; Q2 : Quantity) RETURN Boolean IS
  BEGIN
    RETURN MakeFloat(Q1) > MakeFloat(Q2);
  END ">";

  FUNCTION "<" (Q1 : Quantity; Q2 : Quantity) RETURN Boolean IS
  BEGIN -- stub
    RETURN True;
  END "<";

  FUNCTION "<=" (Q1 : Quantity; Q2 : Quantity) RETURN Boolean IS
  BEGIN -- stub
    RETURN True;
  END "<=";

  FUNCTION ">=" (Q1 : Quantity; Q2 : Quantity) RETURN Boolean IS
  BEGIN -- stub
    RETURN True;
  END ">=";

  FUNCTION "+"(Q  : Quantity) RETURN Quantity IS
  BEGIN
    RETURN Q;
  END "+";

  FUNCTION "-"(Q  : Quantity) RETURN Quantity IS
  BEGIN
    RETURN (Positive => NOT Q.Positive,
            Dollars  => Q.Dollars,
            Cents    => Q.Cents);
  END "-";

  FUNCTION "ABS"(Q  : Quantity) RETURN Quantity IS
  BEGIN -- stub
    RETURN Q;
  END "ABS";

  FUNCTION "*"(F  : Float;    Q  : Quantity) RETURN Quantity IS
  BEGIN
    RETURN(MakeCurrency(F * MakeFloat(Q)));
  END "*";

  FUNCTION "*"(Q  : Quantity; F  : Float   ) RETURN Quantity IS
  BEGIN -- stub
    RETURN Q;
  END "*";

  FUNCTION "/"(Q1 : Quantity; Q2 : Quantity) RETURN Float IS
  BEGIN
    RETURN MakeFloat(Q1) / MakeFloat(Q2);
  END "/";

  FUNCTION "/"(Q  : Quantity; F  : Float   ) RETURN Quantity IS
  BEGIN -- stub
    RETURN Q;
  END "/";

END Currency;
Add and Subtract are implemented following the algorithms above. The exported addition operator "+", which can handle positive or negative values, uses Add or Subtract according to the signs of its operands; the exported operator "-" just adds a negated value.

The next two operations are our constructors to convert to and from currency values. Note how these are written. In going from Float to currency, we need to find the whole-number part of the float quantity, because this will be the Dollars part of the currency quantity. We do this by using the attribute function Float'Truncation, which, as we saw in Chapter 7, does just what we want.

Finally, the remaining operators are given, mostly as stubs. You can complete the package and develop a program to test it, as an exercise. Program 10.9 and Program 10.10 give the specification and body for a child package Currency.IO. We do not show a test program; we leave its development as an exercise.

Program 10.9
Specification for Currency.IO Child Package

WITH Ada.Text_IO;
PACKAGE Currency.IO IS
------------------------------------------------------------------------
--| Specification of the input/output child package for Currency
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  -- input operations to read a Quantity from terminal or file

  PROCEDURE Get (Item : OUT Quantity);
  PROCEDURE Get (File: IN Ada.Text_IO.File_Type; Item : OUT Quantity);
  -- Pre : File is open
  -- Post: The currency quantity is read as a normal 
  --   floating point value.

  -- output operations to display a Quantity on terminal or
  -- write it to an external file

  PROCEDURE Put (Item : IN Quantity; Width: IN Natural:=8);
  PROCEDURE Put (File : IN Ada.Text_IO.File_Type;
                 Item : IN Quantity; Width: IN Natural:=8);
  -- Pre:  File is open, Item is defined
  -- Post: Displays or writes the currency quantity.
  --       Width is used by analogy with Integer_IO
  
END Currency.IO;

Program10.10
Body of Currency.IO Child Package

WITH Ada.Text_IO;
WITH Ada.Integer_Text_IO;
WITH Ada.Float_Text_IO;
PACKAGE BODY Currency.IO IS
------------------------------------------------------------------------
--| Body of the input/output child package for Currency
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  -- input procedures

  PROCEDURE Get (File: IN Ada.Text_IO.File_Type; Item : OUT Quantity) IS
    F: Float;
  BEGIN -- Get

    -- just read it as a Float quantity, then convert
    Ada.Float_Text_IO.Get(File => File, Item => F);
    Item := MakeCurrency(F);

  END Get;

  PROCEDURE Get (Item : OUT Quantity) IS
  BEGIN -- Get
    Get(File => Ada.Text_IO.Standard_Input, Item => Item);
  END Get;

  -- output procedures

  PROCEDURE Put (File : IN Ada.Text_IO.File_Type;
                 Item : IN Quantity; Width: IN Natural:=8) IS
  BEGIN -- Put

    -- dollars first
    IF IsPositive(Item) THEN
      Ada.Integer_Text_IO.Put
        (File=>File, Item=>Dollars(Item),Width=>1);
    ELSE
      Ada.Integer_Text_IO.Put
        (File=>File, Item=>-Dollars(Item),Width=>1);
    END IF;

    -- then decimal point and cents
    Ada.Text_IO.Put(File => File, Item => '.');
    IF Cents(Item) < 10 THEN
      Ada.Text_IO.Put(File => File, Item => '0');
    END IF;
    Ada.Integer_Text_IO.Put
      (File => File, Item => Cents(Item),Width => 1);

  END Put;

  PROCEDURE Put (Item : IN Quantity; Width: IN Natural:=8) IS
  BEGIN -- Put
    Put(File => Ada.Text_IO.Standard_Output, 
        Item => Item, Width => Width);
  END Put;

END Currency.IO;
This example has shown the advantage of using a PRIVATE type not just to encapsulate representation details, but also to give us complete control over the operations a client is permitted to do. As part of developing your test program, you might wish to attempt some operations not provided in the package, for example, multiplying two currency values. Attempting this will result in a compilation error; this tells you that the compiler is aiding you in controlling the client operations.

The USE and USE TYPE Clauses

The USE clause allows unqualified references to package capabilities. Given three Currency.Quantity variables C1, C2, and C3, a currency addition operation is ordinarily written
    C3 := Currency."+"(C1,C2);
that is, just writing "+" as a function. However, if a client program were preceded by
    USE Currency;
it could be written
    C1 := C2 + C3;
One of the advantages of Ada's permitting operator symbols like "+" to be defined as functions is that they can be used in expressions in infix form, as in the above line. When the expressions get more complex, this makes programs even more readable. Compare the line
    Currency.IO.Put(Item => Currency."+"(D, Currency."*"(E,F)));
with the line
    Currency.IO.Put(Item => A + E * F);
This is, however, possible only if a USE clause appears in the client program. Otherwise, the operator must not only be qualified (as in Currency."+") but also must be used as a prefix function call like any other function call.

Many in industry recommend against using the USE statement because in a program that WITHs and USEs many packages, the USEs make so many types and operations directly visible that it is very confusing to the reader. Ada 95 adds the USE TYPE statement as a compromise, so that USE can in general be avoided without losing the benefit of user-defined operators. Writing, for example,

    USE TYPE Currency.Quantity;
gives direct visibility to only the infix operators declared in the package, but to nothing else, and specifically not to other operations like MakeCurrency, Dollars, and Cents.

SYNTAX DISPLAY
Operator Overloading

Form
FUNCTION " OpSymbol "(Formal1: Type1; Formal2: Type2) 
	RETURN ReturnType ;

Interpretation:
The function, defined in a package P, will be associated with the operator OpSymbol and can be called from a client program in one of two ways. If X is of type ReturnType,

X := Actual1 OpSymbol Actual2;
can be used if a USE or USE TYPE statement appears in the client program; otherwise,

X := P."OpSymbol"(Actual1, Actual2);
is required.

Notes:
1. The quotation marks around the operator are required in the second form above and are not allowed in the first case.

2. The operators "=", "/=", "IN", and "NOT IN" cannot normally be overloaded. All other predefined operators can be overloaded.

3. The precedence of the operator cannot be changed by an overload, for example, any "+" operator will have lower precedence than any "*" operator.

PROGRAM STYLE
The USE Clause Again

The USE clause would also have allowed us to write unqualified references to all the other operations in Rationals, but we chose to leave most of the qualified references (for example, the Rationals.Put statements) as they were. This shows that qualified references are still permitted even though a USE appears.

Most Ada experts advise that qualified references should be used wherever possible because they clarify programs by always indicating the name of the package whose operation is being called. These same experts often advocate never writing a USE clause because then qualified references are optional. In this book, we use the USE where appropriate--for example, to make infix ADT operators possible--but we also use qualified reference in most cases, even where a USE is present and the qualification is optional.

When you have an ADT that provides infix operators, the Ada 95 USE TYPE clause provides a nice compromise because it allows the infix operators to be unqualified, but nothing else.

PROGRAM STYLE
Advantages of Private Types

A client program that uses ADT Rationals does not need to know the actual internal representation of data type Rational (i.e., a record with two fields). The client can call an operator function of ADT Rationals to perform an operation (e.g., rational addition) without having this knowledge. In fact, it is better to hide this information from the client program to prevent the client from directly manipulating the individual fields of a rational variable.

It is advantageous for a client program not to have direct access to the representation of a rational quantity for three reasons:

There is a fourth advantage, which would apply if the type represented something more sophisticated, say, a database record of some kind. Each record might contain information for "internal use only," that is, for use only by the data management program itself, not for use by clients. Making the record PRIVATE ensures that the entire record structure is not made available to the client, only that information which the ADT designer chooses to supply via the ADT operations. This is an important advantage for large, complicated, and secure applications.


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

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