Payments are experiencing issues due to temporary restrictions in Russia. If your payment does not go through, please submit a support request.Our support team is available 24/7 — we are always here to help with hosting and server issues.We are now accepting requests for dedicated server rental and colocation services in our data center.Reminder: we recommend enabling backups for additional data protection.A new VPS/VDS lineup with NVMe storage and improved performance is now available.Maintenance work on some servers has been completed. All services are operating normally.
Article5 min readViews0

Where to Put the Extra Kopek When Distributing Discounts Across Products

The total discount on an order and the sum of discounts on its line items may diverge after rounding. We examine a precise educational calculation, a stable method for distributing the remainder, and how to preserve amounts for returns.

Comments 0

Denis compares three stacks of coins and the remainder: distributing a discount across order lines
In this article

The store offers a 100-ruble discount, while the three order lines each receive a 33.33-ruble discount. The sum of the lines totals 99.99. One kopeck is lost, even though each result is rounded quite reasonably. If the recipient redistributes the discount using a different method, the system will begin to dispute the saved order.

An explicit rule for distributing the rounding remainder is required here. It must preserve the total and yield the same result when the same order version is processed again. Switching to a more precise numeric type is useful, but it does not by itself solve dividing 100 rubles into 3 whole amounts in kopecks.

First, agree on the unit and the rounding moment

Within the system, the amount may be stored as an integer of the smallest units or as a decimal number with a specified precision. At the API boundary, it may be transmitted as a string with a decimal point. Type, currency, and scale are part of the contract. The number 100 without these conditions is insufficient: it could mean one hundred rubles or one hundred kopecks.

Do not assume that every currency always has two fractional digits. Even for rubles, an intermediate percentage calculation may contain more digits than the final payable amount. Therefore, the calculation precision, the allowed transmission format, and the stage at which the result is rounded to kopecks must be defined separately.

Binary floating-point numbers have an additional limitation: some decimal fractions cannot be represented exactly. Python documentation illustrates this difference and the possibility of controlled decimal rounding. However, decimal arithmetic also relies on specified precision and rules. The key is not the library name, but a consistent agreement among all exchange participants.

One kopek across three lines

Consider a training order: three different line items, each containing one product priced at 200 rubles. The total amount before the discount is 600 rubles. The agreed discount on the products is 100 rubles; there are no adjustments for delivery, tax calculations, or other factors in this model. The discount is distributed proportionally to the original cost, so the exact shares are equal.

Now let us move to kopecks. The total amount to distribute is 10,000. The integer part of each share is 3,333 kopecks. After assigning three shares, 9,999 kopecks have been distributed, leaving one remaining. This last kopeck can be assigned to the line item selected in advance according to the described rule. For example, among line items with equal fractional parts, the one with the smallest stable line item ID wins.

Then the discounts will be 33.34, 33.33, and 33.33 rubles. The final line amounts will be 166.66, 166.67, and 166.67 rubles. Their total is exactly 500 rubles, and the discount is exactly 100. Another pre-agreed method may assign the remainder to a different line; what matters is preserving the total sum and ensuring reproducibility.

For unequal shares, one possible algorithm first takes whole kopecks, then distributes the remaining fractional parts in descending order. Equality allows for a stable key. This is a proposal for a calculation model, not a universal requirement for payment and accounting systems. The specific process must also account for allowable discounts, excluded items, and the prohibition of a negative line total.

A 100-ruble discount is distributed as 33.34, 33.33, and 33.33; the final sums of the three lines total exactly 500 rubles
Sample order: three lines at 200 rubles each. The single remaining kopek is assigned according to a pre-agreed rule.

Reordering items must not change the previous result

The rule of 'adding the difference to the last line' depends on which item ends up last. The website sorts lines by addition, the accounting system by SKU, and delivery by shipping unit. On each side, the same kopeck may be assigned to a different product. The overall total will converge, but line-by-line reconciliation will not.

If order is used in the algorithm, it must be part of an agreed-upon contract. A more stable option for exchange is to transmit already approved amounts along with line identifiers and the order version. The recipient verifies the total based on this data; they do not re-run their current promotion solely to achieve a similar result.

A line identifier cannot be replaced by its position number in the array. An order may contain two lines for the same product with different conditions. It is crucial to preserve the link to the specific line, its quantity, the assigned discount, and the final total. Simple grouping by product can lose this history.

Refunds verify calculations more strictly than payments

For a test order, the customer returns a line with a total of 166.66 rubles. If the process requires returning its saved paid value, recalculating based on the current three equal shares yields a different result. The original amounts are needed precisely because the order state and promotion rules may have changed.

When partially returning multiple units of a single line, a new level of distribution arises: the line total must be consistently divided between the returned and remaining units. In our example, there is one unit per line, so this issue is intentionally left unresolved. Delivery, tax, and cash register documents are also agreed upon separately; the arithmetic example does not establish their rules.

When reordering rows A, B, and C, the approved sums of 166.66, 166.67, and 166.67 rubles are preserved
Linking to a persistent identifier preserves the calculation history. Applying a refund to a stored amount is valid if the process allows it.

For acceptance testing, it is sufficient to start with specific invariants: the sum of distributed discounts equals the approved discount; the sum of line totals matches the agreed product total; repeating the same calculation does not change the distribution; reordering input lines does not change the result if order is not a contractual condition.

Check these properties for a zero discount, one kopeck, equal and unequal prices, recalculation, and cancellation of a single line. The presented sums were recalculated separately, but the specific store module was not tested here. A good result for integration is the ability to explain any kopeck based on saved original data and the distribution rule. Manually correcting the difference after each exchange does not provide this capability.

Discussion 0

Share your experience and ask questions. Comments without links appear after editorial review.

No comments yet. Start the discussion.