In English, this statement says that if the balance is less than 0, the program prints out a message to that effect and makes the appropriate charge against the balance. The last line within the body of the if statement is
balance -= 10;
which is a shorthand for the longer balance = balance - 10;
1-4 An exercise in debugging
This change in the checkbook-balancing program seems so simple that it hardly merits a second thought. All too often, programmers make changes that appear small and innocuous without bothering to test the resulting program thoroughly. Failure to test code is a very serious error. A more important error, however, is the failure to recognize that all code, no matter how simple it seems, needs testing. The program shown in Figure 3-5 contains a subtle bug. Finding the bug is complicated by the fact that the program seems to work if you test it superficially. For example, the following sample run makes it seem as if the program is functioning correctly:
This program helps you balance your checkbook.
Enter each check and deposit during the month.
To indicate a check, use a minus sign.
Signal the end of the month with a 0 value.
Enter the initial balance: 100
Enter check (-) or deposit: -50
Current balance = 50 Enter check (-) or deposit: -60
This check bounces. $10 fee deducted.
Current balance = -20 Enter check (-) or deposit: 50
Current balance = 30 Enter check (-) or deposit:
FIGURE 3-5
balance2.c(buggy version) /** File: balance2.c
* ---
* This file contains a buggy second attempt at a program to
* balance a checkbook.
*/
#include <stdio.h>
#include “genlib.h”
#include “simpio.h”
main() {
double entry, balance;
printf(“This program helps you balance your checkbook.\n”);
printf(“Enter each check and deposit during the month.\n”);
printf(“To indicate a check, use a minus sign.\n”);
printf(“Signal the end of the month with a 0 value.\n”);
printf(“Enter the initial balance: \n”);
balance = GetReal();
while (TURE) {
printf(“ Enter check (-) or deposit: “);
entry = GetReal();
if (entry == 0) break;
balance += entry;
if (balance < 0) {
printf(“This check bounces. $10 fee deducted.\n”);
balance -= 10;
}
printf(“Current balance = %g\n”, balance);
}
printf(“The final balance = %g\n”, balance);
}
When the user enters the $60 check, the program correctly determines that this amount is more than there is in the account because the value of balance becomes negative. To let the user know about this state of affairs, the program writes out a message and deducts the $10 charge, as instructed. So far, so good.
If you decided to end your testing here, you would never discover the bug in this program. Let’s try a different ser of input data, which is the same except that the last deposit is $10, not $50. This time the sample run looks like this:
This program helps you balance your checkbook.
Enter each check and deposit during the month.
To indicate a check, use a minus sign.
Signal the end of the month with a 0 value.
Enter the initial balance: 100
Enter check (-) or deposit: -50
Current balance = 50 Enter check (-) or deposit: -60
This check bounces. $10 fee deducted.
Current balance = -20 Enter check (-) or deposit: 10
This check bounces. $10 fee deducted.
Current balance = -20 Enter check (-) or deposit:
The sample run reveals a serious problem: when the user makes the $10 deposit trying to move the account back into the black, the program decides that user has bounced a check and promptly charges another $10 fee.
After you discover the symptoms of the failure, the problem is easy to identify. For a check to bounce, two things must be true. First, the user must have just written a check.
Second, the act of writing that check must have resulted in a negative balance. Your program tests only the second condition. To correct the error, you must include both of these conditions in your test. In particular, the program must determine whether a check was written before looking to see whether that check might have bounced. To test for both conditions, you use the && operator, which is C’s way of spelling ―and‖:
if (entry < 0 && balance < 0) {
printf(“This check bounces. $10 fee deducted.\n);
balance = 10 }
Making this change in the program results in the corrected checkbook-balancing program shown in Figure 3-6.
FIGURE 3-6
balance3.c (corrected version) /** File: balance3.c
* ---
* This file contains a corrected version of a program to
* balance a checkbook, including a working bounced-check
*feature.
*/
#include <stdio.h>
#include “genlib.h”
#include “simpio.h”
main() {
double entry, balance;
printf(“This program helps you balance your checkbook.\n”);
printf(“Enter each check and deposit during the month.\n”);
printf(“To indicate a check, use a minus sign.\n”);
printf(“Signal the end of the month with a 0 value.\n”);
printf(“Enter the initial balance: \n”);
balance = GetReal();
while (TURE) {
printf(“ Enter check (-) or deposit: “);
entry = GetReal();
if (entry == 0) break;
balance += entry;
if (balance < 0 && entry < 0) {
printf(“This check bounces. $10 fee deducted.\n”);
balance -= 10;
}
printf(“Current balance = %g\n”, balance);
}
printf(“The final balance = %g\n”, balance);
}
Are you finished with the program? Probably not. All you’ve done so far is discover and fix one bug. To be confident that your program works, you should test it more thoroughly. In particular, you should see if it works correctly on the example for which it failed before. Running the same set of data through the balance.c program yields the following sample run:
So far, so good. The $10 deposit is handled correctly, indicating that the bug you sought to fix is indeed gone. But what about other bugs? When you are writing a program, how can you be sure that you have found all the problems?
The short answer is: you can’t. Many programs that have gone through years and years of testing without any apparent problems suddenly fail when a previously untested condition occurs. The best you can do with a program is to be as thorough as possible in your testing so that the chance of leaving in one of these lingering bugs is minimized.
In the case of the checkbook balancer, it certainly pays to attempt some additional tests. So far, all the numbers used in the examples have been integers. To test it properly, you need to run the program using values that include cents. Suppose, for example, that, after again starting from and initial balance of $100, the user writes checks for $49.95 and
$19.95. The following sample run shows the balance after these two checks.
The last balance display seems a little odd at first glance. The balance in the account after the two checks is 30 dollars and 10 cents, and it is somewhat disconcerting to see that vale displayed as 30.1 instead of 30.10. When working with dollars and cents, it is customary to write out exactly two digits after the decimal point. Unfortunately, the %g specification in printf, which is used here to display the floating-point values, always shows the result in the shortest possible form. Numerically, 30.1 and 30.10 are equivalent, and printf chooses the first one, even though it is not appropriate to the application.
This program helps you balance your checkbook.
Enter each check and deposit during the month.
To indicate a check, use a minus sign.
Signal the end of the month with a 0 value.
Enter the initial balance: 100
Enter check (-) or deposit: -50
Current balance = 50 Enter check (-) or deposit: -60
This check bounces. $10 fee deducted.
Current balance = -20 Enter check (-) or deposit: 10
Current balance = -10 Enter check (-) or deposit:
This program helps you balance your checkbook.
Enter each check and deposit during the month.
To indicate a check, use a minus sign.
Signal the end of the month with a 0 value.
Enter the initial balance: 100.00
Enter check (-) or deposit: -49.95
Current balance = 50.05 Enter check (-) or deposit: -19.95
Current balance = 30.1 Enter check (-) or deposit:
The fact that the balance. c program displays 30.10 might not be a bug in the technical sense. The answer is, after all, mathematically correct. On the other hand, it is almost certainly not what the user wants to see. To satisfy the user, you need to correct this deficiency.
Fortunately, changing the program to display two digits after the decimal point is easy.
All you need to do is replace the %g in the two printf calls with %.2f. This format code tells printf to display floating-point output with two digits to the tight of the decimal point. Thus, the final statement in the program should look like this:
printf(“final balance =%.2\n”, balance);
But what do the characters in the %.2f specification mean? What other options exist for controlling the format of the output data? These questions are important of you want to deign programs that will satisfy your users, who often have exacting requirements concerning how output is displayed. The next section answers these questions by looking more closelfy at printf and its operation.