Syllabus Lesson 83 of 141 · Bugs that lie
Bugs that lie

Totals that drift by a cent

Why it matters A penny out on one invoice in a thousand looks like a rounding quirk. To an accountant reconciling a year of payments it looks like you cannot add up.

Invoices add VAT to each line and round each line to the penny, halves away from zero (so 0.945 becomes 0.95, and a refund of -0.105 becomes -0.11). Then the lines are added. A customer reports:

invoice_total([("0.30", 3)], "5")
# expected "0.95"   (0.90 + 5% = 0.945)
# got      "0.94"

There are two separate reasons this goes wrong. Python's round() sends exact halves to the even neighbour, and a float cannot hold 0.945 exactly anyway: it holds something a tiny bit above or below it.

Binary float: a number stored as a fraction with a power of two underneath. Most decimal amounts like 0.30 have no exact form, so they are stored as the nearest value that fits.

Your turn

Fix invoice_total(lines, vat_percent) and return the total as a string like "12.34". Prices are strings with exactly two decimals; quantities are whole numbers and are negative for refund lines; vat_percent is a string such as "5" or "17.5". Each line is price x quantity plus VAT, rounded to the penny with halves away from zero, then the lines are added. The hidden tests try every price from 0.01 to 20.00 at several VAT rates, refund lines, and long mixed invoices.

Spotted a problem in this lesson? Report it · Ask about this lesson

This exercise opens soon

Finish the lesson before it and this one unlocks. The whole course is free, no account needed, and you can read this lesson right now.