Stop letting a language model do your arithmetic
Totals a model works out in its head fail in the last decimal, which looks right and reconciles wrong. How calculated fields are computed in code instead, and how to pin money to two decimals.
5 min readAsk a language model to extract an invoice and one of the fields you want is often not printed on the page. The tax base when only the total and the rate are shown. The line amount when the document lists quantity and unit price. A converted figure. A discounted subtotal.
The model will give you a number. It got that number by doing arithmetic in its head, in a system built to predict text.
Why this goes wrong quietly
Language models are competent at arithmetic and not reliable at it, which is the worst combination for a pipeline. The failures do not look like failures:
- The result is off in the last decimal, so it passes a glance and fails a reconciliation.
- Rounding happens at the wrong step, so the total is a cent away from the sum of its parts.
- The same document produces slightly different numbers on different runs.
- A percentage is applied to the wrong base, producing a plausible figure that is wrong by a plausible margin.
None of these trip an alarm. They land in your database, and you find them later in an accounting mismatch, which is expensive to chase precisely because the number looked reasonable.
What Veralens does instead
We do not accept computed numbers from the model. When a field is a calculation, the model's job is to identify the quantities on the page and how they combine. The calculation itself runs afterwards, in our runtime, in code.
The practical consequences:
It is deterministic. The same inputs give the same result, every run. There is no temperature on a multiplication.
Precedence is real. Parentheses, multiplication before addition, unary minus. Not approximately, actually.
Rounding happens once, at the end, on the final value, rather than at whichever intermediate step the model happened to round at.
Getting money onto exactly two decimals
Floating point arithmetic produces 110.60000000000001 from perfectly innocent inputs. That is not a value you want written to a money column.
Declare the step you want with multipleOf and the runtime snaps the result to it:
You get 110.6, not 110.60000000000001. An integer field comes back as a whole number rather than 3.0000000000000004.
Worth knowing: validation keywords like multipleOf, minimum and pattern are not sent to the model, because a model is not a validator and treating it as one produces confident violations of the rule you set. They are applied by the runtime after extraction, where they are enforceable. Put descriptive rules in description when you want the model to read them, and use validation keywords when you want them applied.
Check it against the document
Arithmetic being correct does not mean the right quantities went into it. That is still a reading task, and reading can be wrong.
Which is why the cheapest quality control in any document pipeline is to use the document's own redundancy:
Invoices, receipts, payslips and bank statements almost all carry the same figure twice, once directly and once as a component. That redundancy is free error detection, it needs no model, and it catches the reading mistakes that arithmetic correctness cannot.
The short version
A model should tell you what is on the page. It should not be your calculator, and it definitely should not be your rounding rule. Declare the decimal step you need, let the runtime do the maths, and check the result against the document's own totals.
The quickstart gets you a first extraction in a few minutes, and the invoice walkthrough builds the schema field by field.