2009-05-29 3 views
7

Каков наилучший способ решить эту проблему в коде?Вопросы округления с распределением сумм доллара между несколькими людьми

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

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

Пот # 1 987,654.32
Pot # 2 123,456.78

Лицо № 1 получает Allocation Суммы: 345,678.89
Лица № 2 получает Allocation Суммы: 460,599.73
Person # 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, Person # 1 = 307,270.13
Пот # 1, Person # 2 = 409,421.99
Пот # 1, Person # 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 пенни за вычетом)

Итоговые суммы пота должны соответствовать первоначальным суммам.

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

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

+0

Вы можете увидеть эту статью, которую я написал о том, как справиться с этим в SQL: [Финансовое Округление распределения] (http://www.sqlservercentral.com/articles/Financial+Rounding/88067 /) – 2012-05-02 03:48:23

ответ

12

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

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

В приведенном ниже примере математики, если вы принимаете 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 
+1

Nice иллюстрации. Вот почему в большинстве случаев вы должны откладывать округление как можно дольше. – pseudocoder

1

Определенно математика.

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

1

Я думаю, что это именно та проблема, о которой Эрик Эванс обращается в своей статье "Domain Driven Design", глава 8, стр. 198-203.

+2

Что тогда сказал Эрик Эванс? –

+1

Можете ли вы дать отрывок из книги? – Jon

+0

Я согласен с тем, что Эванс отлично справился с этой проблемой. –

2

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

public static decimal Round(decimal d, MidpointRounding mode) 
2

+1 для решения Matt Spradley.

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

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

1

Что делать, когда деление денег является многолетней проблемой. Мартин Фаулер предлагает некоторые комментарии here (я думаю, что есть более подробно в его реальной 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; 
    }