The Sales-Average Program That Was Right by Luck
A small business centre in Bamenda keeps daily sales in a plain text file, SALES.TXT, with one whole amount in francs on each line and no header. The manager wants a program that prints the number of days recorded, the average daily sale, and how many days exceeded 50,000 FCFA. An intern produced this pseudocode:
total = 0 count = 0 above = 0 WHILE NOT EOF(SALES.TXT) READ amount total = total + amount count = count + 1 ENDWHILE average = total / count IF amount > 50000 THEN above = above + 1 ENDIF PRINT count, average, above
On a test file containing 60000, 40000 and 80000 the program printed 3, 60000, 1. The manager says the '1' is right by luck and wants the logic corrected before the file is used with 300 days of data, some of which may contain blank lines.
1. Trace the pseudocode with the values 60000, 40000 and 80000, state exactly what is printed, and explain why the 'above' result is unreliable.
Read the worked answer
Trace:
| Pass | amount read | total | count |
|---|---|---|---|
| 1 | 60000 | 60000 | 1 |
| 2 | 40000 | 100000 | 2 |
| 3 | 80000 | 180000 | 3 |
After the third pass the end of file is reached, so the loop exits. average = 180000 / 3 = 60000. The IF statement then executes once, after the loop, using the last value held in amount, which is 80000. 80000 > 50000, so above becomes 1. Output: 3, 60000, 1.
Why it is unreliable: the count is not made per record. It reflects only whichever value happened to be read last. If the file ended with 40000 instead, the output would be 3, 60000, 0 - although two days (60000 and 80000) exceeded the threshold. In a 300-day file the answer would almost never be correct. The test data hid the bug because the last value happened to be above 50,000.
Lesson: a correct-looking single test is not evidence of correct logic. Boundary and ordering tests (last value below the threshold, all values below, empty file) are needed.
2. Identify all the logic and robustness errors and rewrite the corrected pseudocode, including handling of blank lines and an empty file.
Read the worked answer
Errors:
- The threshold test is outside the loop, so it uses only the final amount.
- There is no guard against
count = 0: an empty file causes division by zero. - A blank line cannot be converted to a number; depending on the language it becomes 0 (wrongly lowering the average and day count) or crashes the program.
- A trailing blank line can make the loop appear to read a phantom record; EOF must be tested in a way that does not process an empty read as data.
- There is no validation that an amount is numeric and non-negative.
Corrected pseudocode:
count = 0
total = 0
above = 0
OPEN SALES.TXT FOR READ
WHILE NOT EOF(SALES.TXT)
READ line
IF line is not blank THEN
IF line is numeric THEN
amount = VALUE(line)
IF amount >= 0 THEN
total = total + amount
count = count + 1
IF amount > 50000 THEN
above = above + 1
ENDIF
ENDIF
ENDIF
ENDIF
ENDWHILE
CLOSE SALES.TXT
IF count > 0 THEN
average = total / count
PRINT count, average, above
ELSE
PRINT "No valid sales records"
ENDIF
Checks: the IF is now inside the loop, so every record is tested; the count is incremented only for valid records; the empty-file case is handled before division; blank and non-numeric lines are skipped. Note that 'exceeded 50,000' means strictly greater than, so 50000 itself is not counted.
3. The manager now wants the average printed to two decimal places. Explain the change required and the risk if `total` and `count` are declared as integers in a compiled language.
Read the worked answer
Change required: average must be a real (floating-point) variable, and the division must be real division followed by formatting to two decimal places, for example PRINT count, ROUND(average, 2), above or a format string such as %.2f.
The integer-division risk: in many compiled languages, if both operands are integers the / operator performs integer division and discards the remainder. With total = 180001 and count = 3, 180001 / 3 yields 60000, not 60000.33. The decimal part is lost before it ever reaches the print statement, so adding a format to the output does not recover it. The fix is to convert at least one operand to a real, e.g. average = REAL(total) / count, or to declare average as REAL and let the expression be promoted.
Second risk - rounding: printing a real to two places rounds the binary representation, and values exactly halfway (such as 60000.005) may round unexpectedly. For money, some systems keep amounts in whole francs and only format at display time, which avoids accumulating rounding error over 300 days.
Test cases to add: a file whose total is not divisible by the count (e.g. 100, 100, 101), a single-record file, an empty file, and a file with a blank line in the middle. These expose division truncation and the count guard far better than the original three-value test.