EECS 211
Class Exercises

Below are exercises suitable for submission to the Code Critic after reading Chapters 1 through 12 of the textbook. Don't forget the rules of the queue.

The exercises are:


Exercise: Bank Accounts

Relevant Readings: Chapters 10, 11, and 12.

Use TDD to implement a small hierarchy of bank account classes, mostly as described in Exercise 12.10. You've already implemented much of this code in the previous Account exercise. You should copy those files to this new project to get started.

Call the project accounts.

A few modifications or clarifications:

Use TDD. Start with Account and test just construction at first, then credit and debit. Be sure to test for all the requirements given in the exercise description and above.

Be sure to test for a complete account life cycle, with several credits and debits interleaved, including a failed attempt to withdraw too much. The last operation should be a debit that closes the account by taking the balance to zero.

Then add savings accounts, which are a little simpler than checking. First, just add tests for constructing a savings account, making sure the interest rate is correctly set, and doing some credits and debits. After those tests pass, add tests for calculating the interest rate.

Finally, add checking accounts. First, just add tests for constructing a checking account, and making sure the fee is correctly set. After those tests pass, add tests for calculating the results of successful and unsuccessful credits and debits. Use your account credit and debit tests, updated to factor in the fee cost.

Be sure that the fee is properly included in the last debit of the lifecycle test that closes the account. (Yes, this can raise a Catch-22 in some cases. Those bankers!)

There should be no printing except for error messages.

Submit to Code Critic:

Clearly label each section of code.

Wrap Up

When the above has been approved via the Code Critic, run make handin.

Email the Zip archive produced by your Makefile with the Subject EECS 211: Bank Accounts.


Exercise: Dollars Class

Relevant Readings: Chapter 11

Use TDD to implement a Dollars class that accurately tracks decimal money values like $25.22. A key goal is that normal operators for arithmetic, comparison, and input/output can be used with decimal values, as described below.

A key need for such a class is to handle monetary amounts, as in the bank account code. The book uses double for this purposes but notes that this is not a good idea. In fact, it's a terrible idea. Your code may print $25.20 but if you stored 25.20 in a double, you really only have something like 25.19999999. The difference may seems small but not over 100s and 1000s of transactions.

C++ has no standard class for this, though many have been built. Java has the BigDecimal class. However, you can't use the normal operators with BigDecimal values, so arithmetic calculations can get pretty verbose. In C++, you overload the operators so that formulas with dollar values are as simple as formulas with regular numbers.

Use TDD to design and implement the class Dollars. Put this in a project dollars. Here's the API:

Dollars( const char *s )
Constructs a dollar value from the number given in the C-style string. The string may begin with an optional plus or minus sign, followed by an optional dollar sign, followed the value, which may or may not include a decimal value, and the decimal value need not be exactly 2 digits. Examples: Dollars( "25.20" ), Dollars( "$25.20" ), Dollars( "-$25.20" ). Ill-formed examples like Dollars( "$25.2" ) and Dollars( "$25.203" ) should be allowed and tested for. Fractions of a cent can be ignored.
Dollars( string s )
Constructs a dollar value from the number given in the C++ string.
Dollars( int n )
Constructs a dollar value from the integer, e.g., Dollars( 6 ) is $6.00.
Dollars( double n )
Constructs a dollar value from the double. This should only be used for doubles that have exact values in binary, e.g., 6.00 and 25.25, but not things like 25.20 or 100.55.
double asDouble()
Returns the double equivalent of the dollar value. This is may be slightly inaccurate, because of the limitations of double values.
long asLong()
Returns the internal long integer representation of the value
long scale()
Returns the internal scale factor
+, -, *, /, < ==, ...
the common operators for arithmetic, comparison and input/output

The reason for so many constructors is so that you can define a class constructor or member function to take Dollars, and just pass it a number or string. Don't duplicate code! These constructors should all be short one-liners, using, as necessary, some private member function to handle number parsing.

If "-$25.20" looks wrong to you, implement what accountants use for negative money: Dollars d( "($25.20)" );. Whatever you do, write tests for printing and reading it.

Internal Representation

The most common way to represent decimal numbers exactly is with long integers, plus a scale factor that says where the decimal point goes. For example, $25.20 could be represented as 2520 with a scale factor of 100 (cents), or 25200 with a scale factor of 1000 (tenths of a cent).

For simplicity, we'll use a fixed scale of 100. Put it in a static class constant so that it wouldn't be hard to change the code later to allowing variable scales.

Constructing from a string

The constructor may seem surprising at first. Why use a string to specify the number? Why not just use

Dollars d( 25.20 );

This won't work! The problem is that 25.20 is not exactly representable in binary, so the above passes 25.1999999 to the constructor. In other words, the correct number is already lost before you get started.

Using a string avoids this problem. This idea is taken from Java's BigDecimal class. It means you have to write a little number parser, but it also means that there's no lost precision.

Another alternative would be to define a constructor that takes a long integer directly, e.g., Dollars( 2520 ) would construct $25.20. While simpler to implement, this would be very easily misinterpreted as $2520.00. Better to do a bit of work implementing Dollars than to make every program that uses Dollars confusing and error-prone. So, in the system designed here, Dollars( 2520 ) is equivalent to Dollars( 2520.00 ).

Constructing from a double

The constructor that takes a double is purely for convenience. It lets you write Dollars( 4 ) and Dollars( 12.25 ) and other values that have exact binary representations. Note that truncation will still occur for exact values like Dollars( 3.125 ).

Overloading operators

One of the nice things about C++ is that you can overload the standard operators to work with user-defined classes. In particular, you can make dollar values work like other numbers, e.g.,:

Dollars d1( "25.20" );
Dollars d2( "-10.15" );
if ( d1 > d2 ) {
  cout << d2 - d1 << endl;
}
else {
  cout <<  d1 + d2 * 4.5 << endl;
}

Specifically, you should implement overloaded operators so that you can

See the notes on overloading operators for how to easily get derived operators like !=.

Notice the rules for multiplication and division. You can't multiply $3.15 by $.55 -- what would that mean? But you can multiply $3.15 by 3 to get $9.45. This logically implies that $9.45 divided by $3.15 is 3.0 (not $3.00!).

Use stringstreams to do automated testing of your input and output operators. For example,

istringstream in("1.32 $10.50");
Dollars d1, d2;
in >> d1 >> d2;
CPPUNIT_ASSERT_EQUAL( Dollars( "1.32" ), d1 );
CPPUNIT_ASSERT_EQUAL( Dollars( "10.50" ), d2 );

ostringstream out;
out << d1 << " " << d2;
CPPUNIT_ASSERT_EQUAL( string( "$1.32 $10.50" ), out.str() );

The ability to overload the input/output operators is why these are preferred to C's printf function. printf has built-in formats for printing numbers and C-style strings, but there's no way to tell it how to print user-defined classes.

CPPUNIT_ASSERT_EQUAL only works with printable values, i.e., ones for which operator<< has been defined. That's because it generates code to print those values if a failure occurs. So if you haven't defined those yet for dollar values, you'll need to test results using asLong().

Submit to Code Critic:

Clearly label each section of code.

Wrap Up

When the above has been approved via the Code Critic, run make handin.

Email the Zip archive produced by your Makefile with the Subject EECS 211: Dollars Class.


Exercise: Banks and Dollars

Change your bank account classes to use Dollars.

First, set up a new project.

Next, change your test code. It's important to be sure your current tests have the right kind of failures, so that you can be sure the Dollars are doing the right thing.

  1. Replace any CPPUNIT_ASSERT_DOUBLES_EQUAL calls with CPPUNIT_ASSERT_EQUAL calls.
  2. If you didn't have any, go to step 4.
  3. Run your tests. Did some tests fail? Good. Skip the remaining steps.
  4. Your tests are bad. They only check for results that have exact values in binary, like 120.00 or 35.50. Change several of your tests to generate results that are not exactly representable in binary, i.e., the fractional part must not be equivalent to fraction with a denominator that is a power of 2, such as 1/2, 1/4,, 3/4, 1/8, etc. For example, 25.20 is not representable exactly in binary.
  5. Run the tests. Some should fail. If not, tweak some more.

When your tests fail with doubles, change the tests to use dollars. To reduce editing, define this helper function:

void ASSERT_DOLLARS_EQUAL( Dollars expected, Dollars actual )
{
    CPPUNIT_ASSERT_EQUAL( expected, actual );
}

Then change your test code to create and test dollar values. For example, if your test code was

Account acct( 100.55 );
CPPUNIT_ASSERT_EQUAL( 100.55, acct.getBalance() );
CPPUNIT_ASSERT( acct.credit( 221.00 ) );
CPPUNIT_ASSERT_EQUAL( 321.55, acct.getBalance() );
CPPUNIT_ASSERT( acct.debit( 321.55 ) );
CPPUNIT_ASSERT_EQUAL( 0.0 , acct.getBalance() );

you would change it to

Account acct( "100.55" );
ASSERT_DOLLARS_EQUAL( Dollars( "100.55" ), acct.getBalance() );
CPPUNIT_ASSERT( acct.credit( 221.00 ) );
ASSERT_DOLLARS_EQUAL( Dollars( "321.55" ), acct.getBalance() );
CPPUNIT_ASSERT( acct.debit( Dollars( "321.55" ) ) );
ASSERT_DOLLARS_EQUAL( 0 , acct.getBalance() );

Make minimal changes to your tests. Only change what you need to to use dollar values. I still used 221.00 in the third line because that's an exact number.

Try to compile and run and make sure things don't work. Then change your bank code until the tests pass. Internally, all code should use Dollars, so if doubles need to be converted. Also, change your Dollars to support any additional operators needed for bank arithmetic. If your bank code did something like:

balance += deposit;

then it should still work with dollar values. That means you need to add operator+= to the overloaded operators in your Dollars code. Classes should make client code simpler, not more complicated.

Submit to Code Critic:

Clearly label each section of code.

Wrap Up

When the above has been approved via the Code Critic, run make handin.

Email the Zip archive produced by your Makefile with the Subject EECS 211: Banks and Dollars.


Comments? comment icon Contact the Prof!

Valid HTML 4.01 Transitional