Округление вопросов с распределением сумм в долларах по нескольким людям - PullRequest
7 голосов
/ 29 мая 2009

Как лучше всего решить эту проблему в коде?

Проблема в том, что у меня есть 2-долларовые суммы (известные как банк), которые нужно распределить на 3 человека. Каждый человек получает определенную сумму, которая поступает из обоих банков, и ставки должны быть примерно одинаковыми. Я продолжаю сталкиваться с проблемами округления, когда мои ассигнования либо слишком много, либо слишком мало.

Вот конкретный пример:

Горшок № 1 987 654,32
Пот № 2 123 456,78

Человек № 1 получает сумму выделения: 345 678,89
Лицо № 2 получает сумму распределения: 460 599,73
Лицо № 3 получает сумму распределения: 304 832,48

Моя логика следующая (код на 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;
        }
    }
}

Результаты, которые я получаю, таковы:

Банк № 1, Человек № 1 = 307 270,13
Пот № 1, Человек № 2 = 409 421,99
Пот № 1, Человек № 3 = 270 962,21
Пот # 1 Итого = 987 654,33 (1 пенни)

Горшок № 2, Человек № 1 = 38 408,76
Пот № 2, Человек № 2 = 51 177,74
Пот № 2, Человек № 3 = 33 870,27
Пот # 2 Итого = 123 456,77 (1 пенни)

Итоговые суммы банка должны совпадать с исходными.

Я думаю, что я что-то упускаю или, возможно, мне нужно сделать дополнительный шаг. Я думаю, что я на правильном пути.

Любая помощь будет принята с благодарностью.

Ответы [ 5 ]

13 голосов
/ 29 мая 2009

Это часто происходит в финансовых расчетах при округлении до ближайшей копейки. Никакое количество настроек алгоритма округления отдельных операций не будет работать для каждого случая.

У вас должен быть аккумулятор, который отслеживает сумму, выделенную после операции округления и распределения. В конце распределений вы сравниваете накопитель с фактическими результатами (суммируемыми) и распределяете остаток копейки.

В приведенном ниже примере по математике, если вы возьмете 0,133 и округлите его до 0,13 и прибавите 3 раза, вы получите пенни меньше, чем если бы вы сначала добавили 0,133 3 раза, а затем округлили.

 0.13    0.133
 0.13    0.133
+0.13   +0.133
_____   ______
 0.39    0.399 -> 0.40
2 голосов
/ 29 мая 2009

+ 1 для решения Мэтта Спрэдли.

В качестве дополнительного комментария к решению Мэтта, вам, конечно, также необходимо учитывать случай, когда вы в конечном итоге выделяете пенни (или больше) меньше , чем целевая сумма - в этом случае вам нужно вычесть деньги из одной или нескольких выделенных сумм.

Вам также необходимо убедиться, что вы не вычтете пенни из выделенной суммы в 0,00 долл. США (в случае, если вы выделяете очень небольшую сумму среди большого числа получателей).

2 голосов
/ 29 мая 2009

Вы пытались управлять поведением округления с помощью аргумента MidpointRounding?

public static decimal Round( decimal d, MidpointRounding mode )
1 голос
/ 29 мая 2009

Что делать, когда деление денег - вечная проблема. Мартин Фаулер предлагает некоторый комментарий здесь (я думаю, что есть больше подробностей в его реальной PoEAA книге):

Но разделение не является [простым], так как мы должны заботиться о случайных пенни. Мы сделаем это, возвращая массив денег, так что сумма массива будет равна исходной сумме, а первоначальная сумма будет справедливо распределена между элементами массива. Если честно, это означает, что те, кто в начале, получают дополнительные копейки.

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;
    }
0 голосов
/ 29 мая 2009

Определенно Math.Round.

Я бы предложил не округлять результат расчета, но если вам нужно отобразить, то округлить до ближайшей копейки. Или вы можете использовать пенни как наименьший знаменатель, поэтому при отображении делите все на 100.

...