8

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

问题是我有 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 美分折扣)

底池总数应与原始总数相匹配。

我想我可能遗漏了一些东西,或者可能需要采取额外的步骤。我认为我在正确的轨道上。

任何帮助将不胜感激。

4

5 回答 5

14

当四舍五入到最接近的美分时,这在财务计算中经常发生。对单个操作舍入算法进行任何调整都不会适用于每种情况。

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

在下面的数学示例中,如果您取 0.133 并将其四舍五入为 0.13 并添加 3 次,则您将比先添加 3 次然后四舍五入的 0.133 少一分钱。

 0.13    0.133
 0.13    0.133
+0.13   +0.133
_____   ______
 0.39    0.399 -> 0.40
于 2009-05-29T02:21:46.150 回答
3

+1 为 Matt Spradley 的解决方案。

作为对马特解决方案的附加评论,您当然还需要考虑最终分配的一美分(或更多)少于目标金额的情况——在这种情况下,您需要从一个或多个分配的金额。

您还需要确保最终不会从 0.00 美元的分配金额中减去一分钱(如果您在大量收件人中分配的金额很小)。

于 2009-05-29T02:52:09.347 回答
2

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

public static decimal Round( decimal d, MidpointRounding mode )
于 2009-05-29T01:42:42.640 回答
1

分钱怎么办是一个长期存在的问题。Martin Fowler在这里提供了一些评论(我认为在他的实际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;
    }
于 2009-05-29T10:43:18.867 回答
0

绝对是 Math.Round。

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

于 2009-05-29T01:37:41.557 回答