Class Exercises |
Home Class Info Links |
Lectures Newsgroup Assignments |
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:
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:
Account constructor
should be optional and default to zero.credit() and debit() should return
true only if the transaction was successfully completed. Note that
some transactions may be successful but have no net effect on the balance.SavingsAccount constructor should take
the interest rate first (required, no default), then the initial balance
(optional, default zero).SavingsAccount::getInterestRate() should return
the interest rate on this account.SavingsAccount::calculateInterest() should calculate
the monthly interest, i.e., if the interest rate is 4.0, that
means 4% per year, so the monthly interest is the balance times (4.0 divided
by 100) divided by 12.CheckingAccount constructor should take
the fee first (required, no default), then the initial balance
(optional, default zero).CheckingAccount::getFee() should return
the fee on this account.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.
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.
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( "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( 6 ) is $6.00.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.
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.
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 ).
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 ).
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
+ and -* and //<,
<=, >, >=,
== and !=<<; values
should be printed with a $ and exactly 2 decimal places, e.g.,
$35.35 or $10.00>>; "25.20," "$25.20,"
"25.2," and "25.2000," should all be read as the same valueSee 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.
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.
Change your bank account classes to use Dollars.
First, set up a new project.
usaccounts.accounts project.Dollars.cpp and Dollars.h files to your bank account
project but not DollarsTests.cpp.Makefile to include the Dollars
code and to create the application usaccountsNext, 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.
CPPUNIT_ASSERT_DOUBLES_EQUAL calls
with CPPUNIT_ASSERT_EQUAL calls.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.
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?
Contact the Prof!