An AI-assisted quote workflow can produce two different totals from the same quantities and prices, even when it uses decimal arithmetic. Rounding each line and rounding only the final sum are different contracts. Pick and test the contract before sending a quote.

This guide builds a small deterministic calculation step for a fictional quote handoff. It accepts a narrow string-based input format, calculates line amounts under an explicit policy and returns fixed two-place strings. It does not ask a model to perform the arithmetic or connect to accounting or payment software.

The one-cent difference is a policy difference

Orbit Desk is a fictional business testing an authored USD sample. It has two lines, each with quantity 0.5 and unit price 0.05. Each exact product is 0.025. Under the demo’s selected policy, each line rounds half up to 0.03, and the total is 0.06. If the two exact products are added first and only the final sum is rounded, the result is 0.05.

Authored calculation Line treatment Final amount
Selected line policy 0.025 becomes 0.03 twice USD 0.06
Final-sum-only comparison 0.025 + 0.025 stays 0.050 until final rounding USD 0.05
Executed results from invented sample data. The comparison illustrates where rounding happens; it does not prescribe the correct policy for a real invoice or contract.

Decimal cannot choose between those business rules. Neither number is evidence of an AI mistake by itself. The question is which policy the receiving system and the business actually expect. This example chooses line rounding solely to make that choice visible and reproducible.

Declare the input and rounding contract

The demo accepts only USD, 1 to 1000 line objects and exactly two fields per line: quantity and unit_price. Both are plain decimal strings, without signs, exponent notation, grouping commas, leading zeros or surrounding whitespace. Quantity must be greater than zero, no more than 1000 and have at most three fractional digits. Unit price may be zero, is capped at 100000 and permits at most two fractional digits. These are authored software limits, not business pricing recommendations.

The Decimal documentation explains string construction, fixed-exponent quantize operations, rounding modes and local contexts. Constructing Decimal from an existing binary float preserves that float’s value rather than recovering an intended source decimal. The example therefore rejects floats at its boundary and rounds with an explicit ROUND_HALF_UP setting.

The versioned CPython wrapper selects the C implementation when available, with a Python fallback. The versioned reference implementation was also inspected. The executed environment used the C backend: Python 3.14.3, decimal 1.70 and libmpdec 4.0.0 on Windows.

Run the complete calculation and tests

Save this as quote_rounding.py in a new disposable directory. It needs only the Python standard library. No packages were installed, no API calls were made and no paid service was used. The script works on embedded authored data and prints a summary; it does not read a business quote, change a file or send anything.

"""Authored USD quote arithmetic; no tax, payment or external integration."""
from decimal import Decimal, Context, localcontext, ROUND_HALF_UP, getcontext
import copy
import json
import re
import unittest

CENT = Decimal('0.01')
QUANTITY = re.compile(r'(?:0|[1-9][0-9]{0,3})(?:\.[0-9]{1,3})?')
PRICE = re.compile(r'(?:0|[1-9][0-9]{0,5})(?:\.[0-9]{1,2})?')


def parse(value, pattern, maximum, positive):
    if not isinstance(value, str) or pattern.fullmatch(value) is None:
        raise ValueError('Expected a plain decimal string in the declared format')
    number = Decimal(value)
    if number > maximum or (positive and number <= 0):
        raise ValueError('Value outside example bounds')
    return number


def quote(lines, currency='USD'):
    if currency != 'USD':
        raise ValueError('This example supports USD only')
    if not isinstance(lines, list) or not 1 <= len(lines) <= 1000:
        raise ValueError('Supply 1 to 1000 line objects')
    with localcontext(Context(prec=28, rounding=ROUND_HALF_UP)):
        amounts = []
        for line in lines:
            if not isinstance(line, dict) or set(line) != {'quantity', 'unit_price'}:
                raise ValueError('Expected exactly quantity and unit_price')
            quantity = parse(line['quantity'], QUANTITY, Decimal('1000'), True)
            price = parse(line['unit_price'], PRICE, Decimal('100000'), False)
            amounts.append((quantity * price).quantize(CENT, rounding=ROUND_HALF_UP))
        total = sum(amounts, Decimal('0.00'))
        return {'currency': currency, 'policy': 'round_each_line_half_up',
                'line_amounts': [format(a, '.2f') for a in amounts],
                'total': format(total, '.2f')}


SAMPLE = [{'quantity': '0.5', 'unit_price': '0.05'},
          {'quantity': '0.5', 'unit_price': '0.05'}]


def demo():
    line_policy = quote(SAMPLE)
    with localcontext(Context(prec=28, rounding=ROUND_HALF_UP)):
        unrounded = sum((Decimal(x['quantity']) * Decimal(x['unit_price'])
                         for x in SAMPLE), Decimal('0'))
        final_only = format(unrounded.quantize(CENT, rounding=ROUND_HALF_UP), '.2f')
    return {'chosen_line_policy': line_policy, 'round_final_sum_only': final_only}


class QuoteTests(unittest.TestCase):
    def test_half_cent_lines(self):
        self.assertEqual(quote(SAMPLE)['line_amounts'], ['0.03', '0.03'])
        self.assertEqual(quote(SAMPLE)['total'], '0.06')

    def test_policy_difference(self):
        self.assertEqual(demo()['round_final_sum_only'], '0.05')

    def test_normal_line(self):
        result = quote([{'quantity': '1.125', 'unit_price': '99.99'}])
        self.assertEqual(result['total'], '112.49')

    def test_float_rejected(self):
        for bad in (0.5, True, 1, None):
            with self.subTest(value=bad), self.assertRaises(ValueError):
                quote([{'quantity': bad, 'unit_price': '0.05'}])

    def test_special_formats_rejected(self):
        for bad in ('NaN', 'Infinity', '1e2', '-1', '+1', '1,00', ' 1', '01', ''):
            with self.subTest(value=bad), self.assertRaises(ValueError):
                quote([{'quantity': bad, 'unit_price': '1.00'}])

    def test_price_scale_rejected(self):
        with self.assertRaises(ValueError):
            quote([{'quantity': '1', 'unit_price': '0.005'}])

    def test_quantity_scale_rejected(self):
        with self.assertRaises(ValueError):
            quote([{'quantity': '0.0001', 'unit_price': '1'}])

    def test_zero_quantity_rejected(self):
        with self.assertRaises(ValueError):
            quote([{'quantity': '0', 'unit_price': '1'}])

    def test_bounds(self):
        self.assertEqual(quote([{'quantity': '1000', 'unit_price': '100000.00'}])['total'],
                         '100000000.00')
        for line in ({'quantity': '1000.001', 'unit_price': '1'},
                     {'quantity': '1', 'unit_price': '100000.01'}):
            with self.subTest(line=line), self.assertRaises(ValueError):
                quote([line])

    def test_currency(self):
        with self.assertRaises(ValueError):
            quote(SAMPLE, currency='EUR')

    def test_schema_and_count(self):
        for lines in ([], SAMPLE * 501, [{'quantity': '1'}],
                      [{'quantity': '1', 'unit_price': '1', 'total': '1'}]):
            with self.subTest(size=len(lines)), self.assertRaises(ValueError):
                quote(lines)

    def test_zero_price_allowed(self):
        self.assertEqual(quote([{'quantity': '1', 'unit_price': '0'}])['total'], '0.00')

    def test_context_and_input_preserved(self):
        original = copy.deepcopy(SAMPLE)
        before = getcontext().prec
        try:
            getcontext().prec = 2
            self.assertEqual(quote(SAMPLE)['total'], '0.06')
            self.assertEqual(getcontext().prec, 2)
            self.assertEqual(SAMPLE, original)
        finally:
            getcontext().prec = before


if __name__ == '__main__':
    print(json.dumps(demo(), indent=2))

Run both commands. The first must report 13 passing test methods. The second prints both calculation policies for the same sample.

python -X utf8 -m unittest -v quote_rounding.py
python -X utf8 quote_rounding.py
{
  "chosen_line_policy": {
    "currency": "USD",
    "policy": "round_each_line_half_up",
    "line_amounts": [
      "0.03",
      "0.03"
    ],
    "total": "0.06"
  },
  "round_final_sum_only": "0.05"
}

Put this step after extraction, before delivery

Use an AI system to propose or extract structured line data, then validate that data against an approved source before calculating. The arithmetic step can recompute amounts, but it cannot know whether the model selected the right item, quantity, rate or currency. Keep the original source and the reviewed input with the result. If a model supplies its own total, compare it separately; this function rejects extra fields rather than trusting that total.

Keep decimal strings through the handoff. Converting to floats before this boundary, or turning the returned strings back into floats for another system, changes the representation contract. Match the receiving system’s documented numeric format and rounding stage. Test that adapter with the same boundary cases rather than assuming that two software products implement the same policy.

The calculation creates a local 28-digit context and quantizes each product to cents before summing. It returns a policy label alongside line amounts and total. The tests show that lowering the caller’s current precision does not change the sample result and that the function preserves both that caller precision and the input objects. This is a bounded example, not a guarantee for every modified decimal-context setting or every larger input domain.

A rejected value needs review. Do not silently strip a currency sign, guess a comma’s meaning, round an over-precision input rate or convert a float just to make validation pass. If your real workflow needs those formats, design a separate parsing policy and add its own examples. For surrounding handoff checks, see structured AI output validation and draft-only agent delivery.

What the execution proves

Thirteen runtime test methods passed. They cover half-cent line rounding, the alternate final-sum result, a normal fractional quantity, rejected floats and booleans, malformed numeric strings, excessive scales, zero quantity, value bounds, unsupported currency, schema/count limits, allowed zero price and preserved input/context state. The final article’s code was extracted and run in a disposable directory. Public code is compared to independently preserved hashes and reproduced after publication.

There is no tax, discount, refund, exchange-rate, credit, invoice-numbering or payment logic here. No real model, accounting platform, payment service, customer quote or production integration was tested. The selected half-up line policy is not presented as a universal accounting or legal rule. Have the business owner establish the actual rule and test the surrounding adapter before treating this example as part of an operational quote workflow.

Jason Ead publishes AlgoThoughts AI News and connects practical engineering with My Biz Heroes: websites, automation, workflows, AI tools and training. Explore Jason’s work. The dragon precision-workshop cover is generated conceptual artwork; the interior table reports the executed sample’s arithmetic.