4. Algorithm Versions

July 18, 2022


Over time, we have made modifications to the calculations. Each of these modifications has created a new "version".

In each version, both the "NetPrice" and "PriceWithTax" methods are implemented.

Currently there are four versions in use (numbered 2, 3, 4, and 5), and the primary differences between them are as follows:

Version 2

This was the first well-defined algorithm, and is also the current default version on all accounts.

To avoid precision ambiguity, this algorithm uses quite agressive rounding. After applying discount to a unit price, the unit price is rounded to 2 decimal places. At small prices, this may lead to a precision loss (eg., 15% off of $0.05 will be $0.04).

Version 3

Version 3 was created to address precision loss on discounts, and does not round post-discount unit prices or row totals.

However, when adding up net total and tax total to get invoice total, this creates a rounding bias. An example:

  • Product price with VAT: 0.45
  • Tax rate: 20%
  • Net total =  0.375 €
  • Tax = 0.075 €
  • Total should be 0.45 €.

The algorithm, however, rounds invoice net total and total tax to 2 decimal places:

  • Net total =  0.38
  • Tax = 0.08
  • Total = 0.46

Invoice total is "wrong" by one cent, due to a bias in rounding (both net and tax are rounded up). Therefore, it is no longer recommended to use version 3 on new accounts.

This issue has been resolved in version 5 with the Largest Remainder algorithm.

Version 4

Version 4 is a special solution, rather than a generic improvement to the standard algorithm. Version 4 only affects the "PriceWithTax" method and addresses the needs of companies who sell items with sub-cent prices.

This version allows prices with VAT have more than 2 decimal places. An example:

  • Unit net price 0.0027 €
  • + VAT 20%
  • = 0.00324 € (5 decimal places)

Without version 4, this item would be sold at 0 €.

This version is NOT compatible with other customers' needs. Erply's database stores only net prices (regardless of the method in use), with finite precision, and this creates an ambiguity of how to interpret the stored values.

If net price is stored in database as 0.8333 € and tax is 20% —

  • Is the price with VAT supposed to be exactly 1 €, and the stored net price is just the closest approximation of that?
  • Or is this an exact value, and correct price with VAT would be 0.99996 €? This is what version 4 assumes.

This discrepancy becomes more evident if net prices have originally been imported with insufficient precision. Assuming 20% VAT, a product might have been imported with net price 2.08 €. With any other version, this results in a price with VAT 2.50 €. In version 4, price with VAT would be 2.496 €, and this will show up as a discrepancy on the receipt if the sold quantity is larger than 1.

If needed, we have worked around that by increasing the precision of the price column in database (in product table and invoice rows table) — changed it from 4 to 6 decimals. Additionally, the configuration parameter overwrite_price_decimals = 6 must be applied.

Version 4 is only supported in Classic back office (not in Berlin).

Version 5

Version 5 fixes the rounding bias of version 3, using the "Largest Remainder" algorithm. It does that by individually "adjusting" row net totals (and tax totals). If needed, some row net totals are selectively adjusted one cent up or down (and corresponding tax totals, vice versa), to ensure that the rows not only add up correctly, but also that tax per transaction is calculated with maximum accuracy.

Version 5 introduces an additional complexity: it is no longer possible to calculate the total of one invoice row, without knowing all the other rows on the same document. This has implications for reports which need to process sales data line-by-line, instead of document-by-document. Back office reports already handle it correctly; the complexity would only be an issue when re-implementing the calculation methods in an integration, outside of Erply.

Version 5 is only supported in Classic back office (not Berlin).

Since version 5 does not round post-discount unit prices, it solves the following problems:

Promotion “Get 3 for $50”.

  • Version 2 results in a receipt total of $16.67 * 3 = $50.01; version 5 returns a total of exactly $50.

The problem where a pack of 12 must cost exactly $1.99.

  • Version 2 results in a receipt total of $0.17 * 12 = $2.04; version 5 returns a total of $1.99.
  • On US accounts, configuration parameter overwrite_price_decimals = 4 is needed as well, to be able to enter unit price with more precision (0.1658).

Summary of Differences

In the "NetPrice" method, the primary differences are as follows:

Key calculation step

 

Version 2

 

Version 3

 

Version 4

 

Version 5

 

Round post-discount unit net price?2 decimals10 decimals10 decimals10 decimals
Round row net total?2 decimals8 decimals8 decimals2 decimals
Round row total tax?No roundingNo roundingNo rounding2 decimals, adjusted using Largest Remainder

In the "PriceWithTax" method, the primary differences are as follows:

Key calculation step

 

Version 2

 

Version 3

 

Version 4

 

Version 5

 

Round pre-discount unit price with VAT?2 decimals2 decimals10 decimals2 decimals
Round post-discount unit price with VAT?2 decimals10 decimals10 decimals10 decimals
Round row net total?2 decimals8 decimals8 decimals2 decimals, adjusted using Largest Remainder