Question

What is the best way to solve this problem in code?

The problem is that I have 2 dollar amounts (known as a pot), that need to be allocated to 3 people. Each person gets a specific amount that comes from both pots and the rates must be approximately the same. I keep coming across rounding issues where my allocations either add up to too much or too little.

Here is a specific example:

Pot #1 987,654.32
Pot #2 123,456.78

Person #1 gets Allocation Amount: 345,678.89
Person #2 gets Allocation Amount: 460,599.73
Person #3 gets Allocation Amount: 304,832.48

My logic is as follows (Code is in c#):

foreach (Person person in People)
{
    decimal percentage = person.AllocationAmount / totalOfAllPots;

    decimal personAmountRunningTotal = person.AllocationAmount;

    foreach (Pot pot in pots)
    {
        decimal potAllocationAmount = Math.Round(percentage * pot.Amount, 2);
        personAmountRunningTotal -= potAllocationAmount;

        PersonPotAssignment ppa = new PersonPotAssignment();
        ppa.Amount = potAllocationAmount;

        person.PendingPotAssignments.Add(ppa);
    }

    foreach (PersonPotAssignment ppa in person.PendingPotAssignments)
    {
        if (personAmountRunningTotal > 0) //Under Allocated
        {
            ppa.Amount += .01M;
            personAmountRunningTotal += .01M;
        }
        else if (personAmountRunningTotal < 0) //Over Allocated
        {
            ppa.Amount -= .01M;
            personAmountRunningTotal -= .01M;
        }
    }
}

The results I get are as follows:

Pot #1, Person #1 = 307,270.13
Pot #1, Person #2 = 409,421.99
Pot #1, Person #3 = 270,962.21
Pot #1 Total = 987,654.33 (1 penny off)

Pot #2, Person #1 = 38,408.76
Pot #2, Person #2 = 51,177.74
Pot #2, Person #3 = 33,870.27
Pot #2 Total = 123,456.77 (1 penny off)

The Pot Totals should match the original totals.

I think I may be missing something or there may be an extra step that I need to take. I think I am on the right track.

Any help would be greatly appreciated.

Was it helpful?

Solution

This happens in financial calculations a lot when rounding to the nearest penny. No amount of tweaking the individual operations rounding algorithm will work for every case.

You have to have an accumulator that tracks the amount allocated after the rounding and distribution operation. At the end of the allocations, you check the accumulator against the actual results (summed together) and distribute the leftover penny.

In the math example below, if you take 0.133 and round it to 0.13 and add 3 times you get a penny less than if you add 0.133 3 times first and then round.

 0.13    0.133
 0.13    0.133
+0.13   +0.133
_____   ______
 0.39    0.399 -> 0.40

OTHER TIPS

Have you tried conntrolling the rounding behavior with the MidpointRounding argument?

public static decimal Round( decimal d, MidpointRounding mode )

+1 for Matt Spradley's solution.

As an additional comment to Matt's solution, you of course also need to account for the case where you end up allocating penny (or more) less than the target amount -- in that case, you need to subtract money from one or more of the allocated amounts.

You also need to ensure that you don't end up subtracting a penny from an allocated amount of $0.00 (in the event that you are allocating a very small amount among a large number of recipients).

What to do when dividing money is a perennial problem. Martin Fowler offers some commentary here (I think there is more detail in his actual PoEAA book):

But division is not [straightforward], as we have to take care of errant pennies. We'll do that by returning an array of monies, such that the sum of the array is equal to the original amount, and the original amount is distributed fairly between the elements of the array. Fairly in this sense means those at the beginning get the extra pennies.

class Money... 
    public Money[] divide(int denominator) {
        BigInteger bigDenominator = BigInteger.valueOf(denominator);
        Money[] result = new Money[denominator];
        BigInteger simpleResult = amount.divide(bigDenominator);
        for (int i = 0; i < denominator ; i++) {
            result[i] = new Money(simpleResult, currency, true);
        }
        int remainder = amount.subtract(simpleResult.multiply(bigDenominator)).intValue();
        for (int i=0; i < remainder; i++) {
            result[i] = result[i].add(new Money(BigInteger.valueOf(1), currency, true));
        }
        return result;
    }

Definitely the Math.Round.

I would suggest not rounding the calculation result, but if you need to display, then round to nearest penny. Or you can use pennies as smallest denominator, thus when displaying, divide everything by 100.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top