CAPE Information Technology Unit 1 study materials
CAPE Information Technology Unit 1 study resources

The Utility Billing Loop That Skips a Customer

CAPE Information Technology Unit 1 · Put your knowledge to work in context.

All case studies

The Utility Billing Loop That Skips a Customer

A student is writing the billing module for a small water utility. The specification says: for each of the customers on the route, read the previous and current meter readings, compute consumption, apply a standing charge of $12.00, and print a bill; apply a rate of $0.45 per unit for the first 100 units, $0.60 for units 101 to 200, and $0.85 above 200; at the end print the average bill. The student's pseudocode declares rate as an integer, writes WHILE count <= numberOfCustomers with count starting at 0, accumulates total, and finishes with average = total / count. There is no check that the current reading is greater than the previous one, and no rounding before printing. A test run with three customers printed four bills, the fourth containing nonsense values, and the average was wrong. A test route with zero customers crashed.

1. Identify and explain each defect in the student's algorithm, including the ones the test run did not expose.

Read the worked answer

Defects visible in the test run

  1. Off-by-one loop bound. count starts at 0 and the condition is count <= numberOfCustomers, so the body runs n+1 times. With three customers it reads a fourth, non-existent record - the nonsense bill - and then exits.
  2. Average divides by the wrong count. After the loop count equals n+1, and total contains the garbage bill, so the average is wrong twice over.
  3. Division by zero. With zero customers the loop still executes once and then divides by a zero count; the design needs an explicit guard for an empty route.

Defects the test run did not expose

  1. Integer rate. Declaring rate as an integer truncates 0.45, 0.60 and 0.85 to 0, so every bill is just the standing charge. Consumption and money should be real (or integer cents), never integer units.
  2. No validation of readings. If the current reading is lower than the previous one - a replaced meter, a transposed digit, an estimated reading - consumption is negative and the bill becomes a credit. The program must detect and flag rather than compute.
  3. No rounding policy. Repeated floating-point addition produces values like 47.300000000000004. Round each bill to two decimal places at the point of printing, and state whether totals use rounded or unrounded values.
  4. Ambiguous tier semantics. The spec does not say whether the rate applies to all units or progressively. The student's code applies the rate to the whole consumption; if the intention is marginal tiers, three separate calculations are needed.
  5. No handling of non-numeric or missing input, and no error path for a customer who should be skipped.

Diagnosis discipline: a test that shows one symptom usually hides others; the boundary values in the next task are what surface them.

2. Rewrite the algorithm in pseudocode so that it is correct and defensively handles bad readings and an empty route.

Read the worked answer
SET total ← 0
SET billed ← 0
READ numberOfCustomers

IF numberOfCustomers <= 0 THEN
    OUTPUT "No customers on this route"
ELSE
    FOR i ← 1 TO numberOfCustomers
        READ customerName, previous, current

        IF current < previous THEN
            OUTPUT customerName, "Invalid reading: current below previous, bill withheld"
        ELSE IF current or previous is not numeric THEN
            OUTPUT customerName, "Invalid input, bill withheld"
        ELSE
            SET used ← current - previous

            IF used <= 100 THEN
                SET rate ← 0.45
            ELSE IF used <= 200 THEN
                SET rate ← 0.60
            ELSE
                SET rate ← 0.85
            ENDIF

            SET bill ← ROUND(used * rate + 12.00, 2)
            OUTPUT customerName, used, bill
            SET total ← total + bill
            SET billed ← billed + 1
        ENDIF
    NEXT i

    IF billed = 0 THEN
        OUTPUT "No bills produced"
    ELSE
        OUTPUT "Average bill: ", ROUND(total / billed, 2)
    ENDIF
ENDIF

Reasoning for the changes

  • A counted FOR i ← 1 TO n loop removes the off-by-one error by construction; the bound is stated once.
  • billed counts only accepted bills, so invalid records do not distort the average, and the empty-route case is handled before any division.
  • Validation precedes calculation: a meter reading that goes backwards is a data problem, and producing a negative bill silently hides it from the office.
  • Rates are real numbers; rounding happens once, at output, so printed bills and the printed average agree.
  • The tier logic still applies one rate to all units. If the utility intends marginal tiers, replace the single calculation with three: charge the first 100 units at 0.45, units 101–200 at 0.60, and the remainder at 0.85, summing the parts. That is a requirements decision to confirm with the client, not something the programmer should assume.

3. Design a set of test data for the corrected algorithm, and justify why each case is included.

Read the worked answer

Boundary and typical cases (single customer route unless stated)

Previous Current Units Purpose Expected result
1000 1050 50 Ordinary case inside the lowest band 50 × 0.45 + 12.00 = 34.50
1000 1099 99 Just below the first boundary 56.55
1000 1100 100 Exactly on the first boundary 57.00
1000 1101 101 Just above the first boundary 72.60
1000 1200 200 Exactly on the second boundary 132.00
1000 1201 201 Just above the second boundary 182.85
1000 1000 0 Zero consumption; standing charge only 12.00
1500 1400 negative Current below previous (meter replacement or typo) Bill withheld, message printed
- - non-numeric Text in a reading field Bill withheld, message printed
route with 0 customers Empty input "No customers on this route", no division
route with 3 valid + 1 invalid Average must ignore the rejected record Average of the three valid bills
1000.5 1100.25 fractional Real readings and rounding check Bill rounded to 2 dp

Justification

  • Boundary values (99, 100, 101, 200, 201) are where conditional logic changes branch. Errors cluster at the comparison operator - <= versus < - and a test at 150 would never reveal it.
  • Zero units proves the standing charge is applied independently of consumption.
  • Invalid readings test the error path, which is the part most often written but never executed.
  • Empty route tests the division-by-zero guard.
  • Mixed valid and invalid records checks that billed rather than numberOfCustomers drives the average - the defect the original test run hid.
  • Fractional readings check rounding. When reporting, state the expected output for each row before running, so the test is a comparison rather than an interpretation of whatever appears.