Take three lines. 33.33 lei each, net, VAT at 21%.
Round the VAT on each line and add them up: 7.00 + 7.00 + 7.00 = 21.00.
Add the net first and take VAT once on the total: 99.99 × 0.21 = 20.9979, which rounds to 21.00.
Same answer. This is why the bug survives testing.
Now make the lines 33.34, 33.33, 33.33. Per line: 7.00 + 7.00 + 7.00 = 21.00. Per total: 100.00 × 0.21 = 21.00. Still fine.
Keep going and eventually you find the pair that does not agree, and it is always one ban. One hundredth of a leu, on a document that is otherwise perfect, rejected by a validator that will not tell you which of the two numbers it thinks is wrong.
Both methods are legitimate, which is the whole problem
This is not a case of one right answer and one bug. Rounding per line and rounding per document are both defensible accounting positions, both are used in the wild, and different systems in the same company will confidently do different things.
Your ERP rounds one way. The accounting package it exports to rounds the other. The e-Factura XML has slots for both the line amounts and the document totals, and it validates that they agree. So the mismatch does not appear where it is created. It appears at the border, months later, as a rejection with a message about a total.
The rule that resolves it is boring and worth writing on the wall: the document is the sum of its lines. Compute each line to its final rounded value first, then add. Never compute a total independently and hope it matches. The moment two paths can produce the total, one of them will eventually be the one that is wrong.
Where the ban actually comes from
Floating point gets blamed for this and floating point is usually innocent. The real source is that rounding is not distributive. Round-then-sum and sum-then-round are different operations, and they differ by up to half a unit per line. With enough lines the difference is not one ban; on a 200 line delivery note it can be several.
Which means the size of your discrepancy scales with document length, and your test suite, built on three-line invoices, will never see it.
Two things follow.
Test with a long document. Generate fifty lines with awkward thirds and check the invariant. This is a five line property test and it is the single highest-value test in a billing system.
Never store a computed total you can recompute. If the total is a column, it can disagree with the lines. If it is a function of the lines, it cannot. Where a column is unavoidable for query performance, recompute and compare on write, and treat a mismatch as an error rather than something to reconcile later.
The discount trap
Line discounts are where this gets genuinely nasty, because a discount introduces a second rounding opportunity on the same line.
Take a 10% discount on 33.33. The discounted net is 29.997. Round to 29.99 or 30.00 and the VAT differs. Apply the discount to the line, or as a document-level allowance, and the VAT base differs again. UBL has separate structures for both, and they are not interchangeable: a document-level allowance has to be apportioned across VAT rates, and if your invoice mixes 21% and 11% lines, that apportionment is itself a rounding decision.
We have seen an integration where discounts were applied at line level in the UI, stored at document level in the database, and re-derived at line level for the XML. Three representations of one discount. It reconciled correctly for eighteen months, because every test invoice had a single VAT rate.
What to do about the number that is already wrong
If you have historical documents with mismatched totals, do not fix them by adjusting a line. The line was right. Adjusting it changes what you charged somebody.
The correct instrument is a rounding adjustment: an explicit, visible line item, of at most a ban or two, that makes the document internally consistent and says so. It is honest, it is auditable, and it survives being asked about three years later. A quietly amended unit price does none of those things.
And put the adjustment in one place in the code. The temptation is to nudge the number at the point of export, because that is where the rejection appeared. Then the invoice in your system and the invoice you sent the state disagree, which is a strictly worse problem than the one you started with.
Why this is the article we point people at
Rounding sounds like the least interesting thing in an invoicing system. It is consistently the thing that produces the most confusing failures, because the symptoms appear far from the cause, the arithmetic is defensible on both sides, and the difference is too small to be believed as a real problem until somebody explains that the validator does not care about the size.
If you are scoping a billing or e-Factura integration, assume the rounding conversation will happen. Better to have it in week one, at a whiteboard, than in month four, at a rejection.
