围绕多个人分配美元金额的问题

时间:2009-05-29 01:33:49

标签: c# .net puzzle rounding

在代码中解决此问题的最佳方法是什么?

问题是我有2美元的金额(称为底池),需要分配给3个人。每个人获得来自两个盆的特定数量,并且费率必须大致相同。我不断遇到问题,我的分配加起来太多或太少。

这是一个具体的例子:

Pot#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;
        }
    }
}

我得到的结果如下:

Pot#1,Person#1 = 307,270.13
锅#1,人#2 = 409,421.99
锅#1,人#3 = 270,962.21
锅#1总计= 987,654.33(1便士)

Pot#2,Person#1 = 38,408.76
锅#2,人#2 = 51,177.74
锅#2,人#3 = 33,870.27
锅#2总计= 123,456.77(1便士)

Pot Totals应与原始总数匹配。

我想我可能会遗漏某些东西,或者我可能需要采取额外措施。我想我走在正确的轨道上。

非常感谢任何帮助。

5 个答案:

答案 0 :(得分:13)

当四舍五入到最近的便士时,这在财务计算中会发生很多。对于每种情况,不需要调整单个操作舍入算法的数量。

您必须有一个累加器,用于跟踪舍入和分配操作后分配的金额。在分配结束时,根据实际结果检查累加器(总结在一起)并分配剩余的便士。

在下面的数学示例中,如果你取0.133并将其四舍五入为0.13并且加3次,你会比先加上0.133次然后再加一次0.133便宜一分钱。

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

答案 1 :(得分:2)

您是否尝试使用MidpointRounding参数控制舍入行为?

public static decimal Round( decimal d, MidpointRounding mode )

答案 2 :(得分:2)

+1 Matt Spradley的解决方案。

作为对Matt解决方案的补充评论,您当然还需要考虑最终分配一分钱(或更多)而非目标金额的情况 - 在这种情况下,您需要从一个或多个分配金额中扣除资金。

您还需要确保不会从分配的0.00美元金额中扣除一分钱(如果您在大量收件人中分配的金额非常小)。

答案 3 :(得分:1)

分钱时该怎么做是一个长期存在的问题。 Martin Fowler提供了一些评论here(我认为他的实际PoEAA书中有更详细的内容):

  

但是分工并不[直截了当],因为我们必须照顾错误的便士。我们将通过返回一个monies数组来做到这一点,这样数组的总和等于原始数量,并且原始数量在数组的元素之间公平分配。从这个意义上来说,相当意味着那些开始时会获得额外的便士。

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;
    }

答案 4 :(得分:0)

绝对是Math.Round。

我建议不要舍入计算结果,但如果需要显示,则舍入到最近的便士。或者你可以使用便士作为最小分母,因此在显示时,将所有内容除以100。