Skip to main content
ThunderLang
← All articles
ai-engineering

How to Test AI-Generated Discount Code for Rounding Errors

7 min read · 2026-10-12 · Allen Codewell

Blank clay tags follow separate and shared channels along a terracotta-and-sage forked track.

AI-generated illustration. Choose where rounding happens before implementation: rounding each line’s discount and rounding the combined discount can produce different totals.

To test AI-generated discount code for rounding errors, choose a rounding rule, then write acceptance tests with independently calculated expected values before asking the coding agent to implement it.

Two five-cent lines with a 10% discount expose the choice: per-line half-up rounding produces an eight-cent payable total; rounding the combined discount produces nine cents.

Neither result is inherently correct. The business rule decides.

Choose the rounding boundary before implementation

“Apply a 10% discount” leaves several questions unanswered:

  • Does the discount apply to each unit, each line subtotal, or the order subtotal?
  • What happens when the calculated discount is exactly half a cent?
  • Do you round the discount before subtracting it, or round the discounted price?

These choices are not interchangeable. Specify them in the requirement rather than inherit whatever the generated code happens to do.

For this worked example, use the following contract:

Given line subtotals in integer cents and one discount rate, calculate each line’s discount, round that discount to the nearest cent with exact halves rounded up, then subtract the sum of the rounded discounts from the sum of the line subtotals.

The assumptions are deliberately narrow:

  • Each input line is an already-calculated subtotal, not a unit price. Quantity has already been applied.
  • Line subtotals are nonnegative integer cents in one currency.
  • The rate is an integer number of basis points: 1,000 basis points means 10%, and 10,000 means 100%.
  • The rate is between 0% and 100%, inclusive.
  • Taxes, refunds, currency conversion, and multiple or stacked discounts are outside this contract.

If the intended policy is per-order rounding, change the contract and expected results first.

Use a one-cent counterexample

Consider this hypothetical order:

Calculation Line A Line B
Line subtotal 5 cents 5 cents
Exact discount at 10% 0.5 cents 0.5 cents
Discount rounded half up 1 cent 1 cent
Subtotal minus rounded discount 4 cents 4 cents

Under the chosen contract:

Subtotal = 5 + 5 = 10 cents
Rounded discounts = 1 + 1 = 2 cents
Payable total = 10 - 2 = 8 cents

Combining the lines before calculating and rounding the discount gives a different result:

Subtotal = 10 cents
Combined discount = 10 × 10% = 1 cent
Payable total = 10 - 1 = 9 cents

The second calculation violates the per-line contract even though its arithmetic is internally consistent. The error is the location of rounding, not necessarily an inaccurate multiplication.

Rounding each discounted line price creates another mismatch. Five cents minus a half-cent discount is 4.5 cents. Rounding that price half up gives five cents, not the four cents required by rounding the discount first.

Name both the rounding boundary and the value being rounded.

Turn the requirement into a shareable test card

Before sending the task to a coding agent, attach this test card to the change request:

Contract field Agreed behavior
Inputs Nonnegative integer line subtotals in cents; integer rate in basis points
Discount base Each complete line subtotal
Exact line discount line_cents × rate_bps / 10000 cents
Rounding Nearest integer cent; exact halves round up
Order of operations Round each discount, sum rounded discounts, subtract from subtotal
Distinguishing fixture Lines [5, 5], rate 1000
Expected intermediate values Exact discounts [0.5, 0.5] cents; rounded discounts [1, 1] cents
Expected result Subtotal 10, discount total 2, payable total 8 cents
Exclusions Taxes, refunds, currency conversion, stacked discounts, and unspecified input handling

Derive expected values from the requirement and the worked calculation. Do not run the implementation and copy its output into a test: that can preserve the mistake you want to detect.

Two cloth mats each hold four counters, with one additional counter set aside beside each mat.

AI-generated illustration. In the hypothetical two-line example, each five-cent line receives a 10% discount. Each half-cent discount rounds up to one cent, leaving four cents per line and eight cents payable. Rounding the combined discount instead leaves nine cents.

Write acceptance tests around the boundary

The following is illustrative pseudocode, not a tested library API. It assumes the calculation routine exposes per-line discounts and totals. If the actual interface returns only a payable total, assert that result at the public boundary. Test intermediate values separately where the implementation exposes them.

test "round each line discount before summing":
    result = calculate_discount(
        line_cents = [5, 5],
        rate_bps = 1000
    )

    assert result.subtotal_cents == 10
    assert result.line_discount_cents == [1, 1]
    assert result.discount_total_cents == 2
    assert result.payable_cents == 8

Add fixtures below, at, and above the half-cent boundary, plus checks for zero amounts and the rate endpoints:

Line subtotals, cents Rate Exact discounts, cents Rounded discounts, cents Payable, cents
[4] 10% [0.4] [0] 4
[5] 10% [0.5] [1] 4
[6] 10% [0.6] [1] 5
[5, 5] 10% [0.5, 0.5] [1, 1] 8
[4, 4] 10% [0.4, 0.4] [0, 0] 8
[0] 10% [0] [0] 0
[5, 6] 0% [0, 0] [0, 0] 11
[5, 6] 100% [5, 6] [5, 6] 0

The [4, 4] fixture catches aggregation from another direction: per-line rounding gives no discount, whereas rounding the combined 0.8-cent discount gives one cent.

A table-driven test can encode these fixed expectations:

for each fixture:
    result = calculate_discount(fixture.lines, fixture.rate_bps)

    assert result.line_discount_cents == fixture.rounded_discounts
    assert result.payable_cents == fixture.expected_payable

Keep expected values independent of the production rounding helper. Using the same helper for actual and expected results would weaken the check.

Make the arithmetic match the contract

Integer inputs alone do not guarantee exact arithmetic if the implementation converts them to floating point midway through the calculation. For this contract, quotient-and-remainder arithmetic makes the half-cent decision explicit.

For each line, let numerator = line_cents × rate_bps. Divide it by 10000 to obtain an integer quotient and remainder:

  • Start with the quotient as the discount in whole cents.
  • If the remainder is at least 5000, add one cent.
  • Otherwise, leave the quotient unchanged.

For a five-cent line at 10%, the numerator is 5000. The quotient is zero and the remainder is 5000, so the rounded discount is one cent.

This describes arithmetic for nonnegative inputs, not a complete production implementation. Fixed-width integer multiplication can overflow. Define a supported numerical range or choose an arithmetic type that accommodates it; the small fixtures above do not test that limit.

Use the tests to detect specification drift

Ask the coding agent to implement the contract without changing its expected values. Then review the diff for changes in meaning, not just whether the suite passes:

  • Did rounding move outside the per-line calculation?
  • Did the code round final prices instead of discounts?
  • Did “half up” become a different tie-breaking rule?
  • Did a test expectation change from eight cents to nine without a requirement change?

As a practical exercise, temporarily replace per-line rounding with aggregate rounding in an isolated local test copy. The [5, 5] acceptance test should fail. This deliberate mutation checks whether the test detects that specific change. It does not establish that the rest of the suite is adequate.

Passing these fixtures establishes agreement with their expected results, not correctness for every input. Nor can tests resolve an unstated business rule. Taxes, refunds, currency conversion, malformed inputs, empty orders, integration behavior, and numerical ranges beyond these fixtures need separate decisions and checks if they enter scope.

Keep the chain intact as the agent revises the code: requirement → distinguishing example → acceptance test → implementation review.

For a next step, consult ThunderLang’s getting-started guide as you prepare your AI-assisted change.