公平分配收入的算法

时间:2013-12-19 18:18:54

标签: algorithm finance discrete-mathematics

我遵循一种算法(最好是抽象的或非常清晰的Python或PHP代码),根据以下约束条件,允许在短期和长期内公平分配收入:

  1. 每个传入的金额都是整数。
  2. 每个分布也是一个整数。
  3. 分配中的每个人都将收到以固定分数表示的收入削减。这些分数当然可以归入一个共同的分母(假设分母为100的整数百分比)。所有分子的总和等于分母(在百分比的情况下为100)
  4. 为了在短期内保持公平,i人必须接收floor(revenue*cut[i])ceil(revenue*cut[i])编辑:我要强调ceil(revenue*cut[i])不一定等于1+floor(revenue*cut[i]),这是一些算法失败的地方,包括一个算法(参见我对安德烈答案的评论)。 < / LI>
  5. 为了使其长期合理,随着支付的累积,收到的实际削减必须尽可能接近理论削减,最好总是低于1个单位。特别是,如果总收入是公共分数的分母的倍数,则每个人都应该已经收到了相应的分子倍数。
  6. [编辑]忘记添加每次分发的总金额当然必须加到收到的收入金额。
  7. 这是一个清晰的例子。假设其中有三个人分配收入,一个必须得到31%,另一个得到21%,第三个得到100-31-21 = 48%。

    现在想象一下有80个硬币的收入。第一个人应该收到floor(80*31/100)ceil(80*31/100)个硬币,第二个人应该收到floor(80*21/100)ceil(80*21/100)和第三个人,floor(80*48/100)或{{ 1}}。

    现在想象一下,有120个硬币的新收入。每个人应该再次收到相应削减的楼层或天花板,但由于总收入(200)是分母(100)的倍数,每个人现在应该有他们的确切削减:第一个人应该有62个硬币,第二个人应该有42个硬币,第三个人应该有96个硬币。

    我想我有一个算法可以找出两个人分配收入的算法。它是这样的(35%和65%):

    ceil(80*48/100)

    我认为这个简单的算法符合我的所有约束,但我没有找到将其扩展到两个以上的人而不会违反至少一个的方法。任何人都可以找出多人的延伸(或者证明其不可能)吗?

2 个答案:

答案 0 :(得分:1)

我认为这有效:

  • 跟踪totalReceived,即进入系统的总金额。
  • 将资金添加到系统后,将其添加到totalReceived
  • 设置person[i].newTotal = floor(totalReceived * cut[i])编辑:此处有错误;感谢评论者。这会分配部分资金。跟踪多少,知道剩下多少“新钱”。为了清楚起见,我们正在跟踪剩余的总金额中剩余多少,并且直观地我们在每一步都重新分配全部金额,尽管实际上没有人的资金在一个步骤和下一步。
  • 您需要知道如何分配剩余的整数金额:剩余金额应少于numPeople。对于i范围从0leftOverMoney - 1的{​​{1}},请将person[i].newTotal增加1.实际上,我们会为每个leftOverMoney人提供一美元。
  • 要计算此人在此步骤中“收到”的数量,请计算person[i].newTotal - person[i].total
  • 要清理/完成,请设置person[i].total = person[i].newTotal

答案 1 :(得分:1)

为了适应约束4,必须使用当前付款作为分配的基础。在向投资者分配四舍五入的股息后,剩余的美元可以分别给予任何合格的投资者一个。如果投资者应分得一小部分,即ceil(revenuecut [i])&gt;地板(revenuecut [I])。

约束5可以通过决定合格投资者中谁应该获得剩余资金来解决。通过将剩余的美元奖励给累积了最多(长期)分配错误的合格投资者,将最接近最大长期公平性。

以下内容源自我用于基金经理客户的算法。 (出于法医原因,我总是将Pascal用于财务应用程序。)我已将数据库应用程序中的代码片段转换为基于数组的方法,以提高清晰度。

会计数据结构中包含投资者表。关于每个投资者的信息包括他的份额分数(投资者份额除以基金总股份),迄今为止赚取的金额,以及迄今为止分配的金额(实际支付给投资者)。

Investors : array [0..InvestorCount-1] of record
        // ... lots of information about this investor
        ShareFraction       : float; // >0 and <1
        EarnedToDate        : currency; // rounded to dollars
        DistributedToDate   : currency; // rounded to dollars
    end;
// The sum of all the .ShareFraction in the table is exactly 1.

为这些计算创建了一些临时数据,然后将其释放。

DistErrs : array [0..InvestorCount-1] of record
        TotalError      : float; // distribution rounding error
        CurrentError    : float; // distribution rounding error
        InvestorIndex   : integer; // link to Investors record
    end;
UNDISTRIBUTED_PAYMENTS  : currency;
CurrentFloat            : float;
CurrentInteger          : currency;
CurrentFraction         : float;
TotalFloat              : float;
TotalInteger            : currency;
TotalFraction           : float;
DD, II                  : integer; // array indices

FUND_PREVIOUS_PAYMENTS : currency;是一个参数,源自基金历史。如果它不作为参数提供,则可以计算为所有Investors []的总和.EnnedToDate。

FUND_CURRENT_PAYMENT : currency;是一个参数,源自当前基金价值的增长。

FUND_TOTAL_PAYMENTS : currency;是一个参数,源自当前的基金价值。这是先前支付的基金总额加上基金当前付款。

这三个值形成具有两个自由度的家属系统。可以提供任何两个,并且从其他人计算第三个。

// Calculate how much each investor has earned after the fund current payment.
UNDISTRIBUTED_PAYMENTS := FUND_CURRENT_PAYMENT;
for II := 0 to InvestorCount-1 do begin

  // Calculate theoretical current dividend whole and fraction parts.
  CurrentFloat := FUND_CURRENT_PAYMENT * Investors[II].ShareFraction;
  CurrentInteger := floor(CurrentFloat);
  CurrentFraction := CurrentFloat - CurrentInteger;

  // Calculate theoretical total dividend whole and fraction parts.
  TotalFloat := FUND_TOTAL_PAYMENTS * Investors[II].ShareFraction;
  TotalInteger := floor(TotalFloat);
  TotalFraction := TotalFloat - TotalInteger;

  // Distribute the current whole dollars to the investor.
  Investors[II].EarnedToDate := Investors[II].EarnedToDate + CurrentInteger;
  // Track total whole dollars distributed.
  UNDISTRIBUTED_PAYMENTS := UNDISTRIBUTED_PAYMENTS - CurrentInteger;

  // Record the fractional dollars in the errors table and link to investor.
  DistErrs[II].CurrentError := CurrentFraction;
  DistErrs[II].TotalError := TotalFraction;
  DistErrs[II].InvestorIndex := II;

end;

此时UNDISTRIBUTED_PAYMENTS始终小于InvestorCount

// Sort DistErrs by .TotalError descending.

在现实世界中,数据保存在不在数组中的数据库中,因此通过使用DistErrs作为键在TotalError上创建索引来完成排序。

// Distribute one dollar each to the first UNDISTRIBUTED_PAYMENTS eligible 
// investors (i.e. those whose current share ceilings are higher than their 
// current share floor), in order from greatest to least .TotalError.
for DD := 0 to InvestorCount-1 do begin
if UNDISTRIBUTED_PAYMENTS <= 0 then break;
  if DistErrs[DD].CurrentError <> 0 then begin
    II := DistErrs[DD].InvestorIndex;
    Investors[II].EarnedToDate := Investors[II].EarnedToDate + 1;
    UNDISTRIBUTED_PAYMENTS := UNDISTRIBUTED_PAYMENTS - 1;
  end;
end;

在后续流程中,每个投资者都需要支付他的.EarnedToDate和他的.DistributedToDate之间的差额,并调整他的.DistributedToDate以反映该付款。