然后算法和公平地分摊账单:)

gum*_*muz 3 python algorithm math floating-accuracy

我正试图解决你可能遇到的以下现实问题:

你和一些朋友共进晚餐,你们都同意平均分摊账单.除了账单终于到来之外,你发现不是每个人都有足够的现金(如果有的话,便宜的混蛋).

所以,你们中的一些人比其他人付出更多......之后你们回家并试着决定"谁欠谁的数额?".

这个,我正在尝试以算法和公平的方式解决:)

一开始看起来这么容易,但是我已经陷入了四舍五入的困境,我觉得这完全是一个失败者;)

关于如何解决这个问题的任何想法?

编辑:一些python代码,以显示我的困惑

>>> amounts_paid = [100, 25, 30]
>>> total = sum(amounts_paid)
>>> correct_amount = total / float(len(amounts_paid))
>>> correct_amount
51.666666666666664
>>> diffs = [amnt-correct_amount for amnt in amounts_paid]
>>> diffs
[48.333333333333336, -26.666666666666664, -21.666666666666664]
>>> sum(diffs)
7.1054273576010019e-015
Run Code Online (Sandbox Code Playgroud)

理论上,差异的总和应为零,对吧?

另一个例子它工作:)

>>> amounts_paid = [100, 50, 150]
>>> total = sum(amounts_paid)
>>> correct_amount = total / float(len(amounts_paid))
>>> correct_amount
100.0
>>> diffs = [amnt-correct_amount for amnt in amounts_paid]
>>> diffs
[0.0, -50.0, 50.0]
>>> sum(diffs)
0.0
Run Code Online (Sandbox Code Playgroud)

S.L*_*ott 9

http://www.billmonk.com/

在其他人中.问题已经解决了.很多次.


"理论上,差异的总和应该为零,对吗?"

是.float但是,自从您使用以来,当人数不是2的幂时,就会出现代表问题.

决不.使用.float对于.金融.

决不

总是.使用.decimal对于.金融.

总是

  • 这不是一个有趣的问题.它是加减法. (2认同)
  • @kaloyan:但是@gumuz**确实**完全解决了它.并且使用了`float`并且被舍入错误搞糊涂了.@gumuz不需要"解决方案".@gumuz需要停止使用`float`. (2认同)
  • @ S.Lott:BillMonk链接+1(相当酷).-1是一个脾气暴躁的人.对于他来说,问题很有趣,因为他需要花时间来写这个问题.它不需要被视为普遍无趣的.我打赌他(和其他人)从答案中学到了一些东西. (2认同)

e.J*_*mes 5

诀窍是将每个人视为一个单独的帐户.

您可以轻松确定(从原始账单中)每个人支付多少钱.将此设置为每个人的负数.接下来,通过将支付的金额添加到其帐户中来记录每个人已支付的金额.在这一点上,多付(贷款人)的人将有正余额,而少付(借款人)的人将有负余额.

除非在只有一个贷方的明显情况下,借款人欠每个贷方的钱没有一个正确的答案.借款人支付的金额可以转给任何贷方.只需将金额加到借款人的总金额中,然后从收到付款的贷方中扣除金额.

当所有账户都达到零时,每个人都已经付清了.

编辑(回复评论):

我认为我的问题在于这个数量并不总是可以整除的事实,所以提出一个优雅地处理这个问题的算法似乎让我一次又一次地绊倒.

在处理美元和美分时,没有100%干净的方法来处理舍入.有些人会比其他人多支付1美分.公平的唯一方法是随机分配额外的0.01美元(根据需要).当通过分割账单来计算"欠款"时,这只会进行一次.它有时有助于将货币价值存储为美分,而不是美元(例如12.34美元将存储为1234美元).这允许您使用整数而不是浮点数.

为了分配额外的美分,我会做以下事情:

total_cents = 100 * total;
base_amount = Floor(total_cents / num_people);
cents_short = total_cents - base_amount * num_people;
while (cents_short > 0)
{
    // add one cent to a random person
    cents_short--;
}
Run Code Online (Sandbox Code Playgroud)

注意: "随机"分配便士的最简单方法是将第一个额外分配给第一个人,第二个分配给第二个,等等.如果您总是以相同的顺序输入相同的人,这只会成为一个问题.