2013年1月16日 星期三

關於分錢的程式寫法

在網路上亂逛的時候,在黑暗執行緒blog看到一個分錢的算法,覺得蠻有趣的,
http://blog2.darkthread.net/post-2009-06-11-about-money-distribution.aspx
其原理是將所有人的權重加起來,
再依序從第1個人開始分:
第1個人分得:全部的金額*(第1個人的權重/所有人的權重總合)
第2個人分得:剩下未分的金額*(第2個人的權重/剩下未分配的人權重總合)
第3個人分得:剩下未分的金額*(第3個人的權重/剩下未分配的人權重總合)
........
初看好像是很帥的分配方式,但總感覺似乎在某些情況會怪怪的(例如同條件可能分到不同金額),
但一時之間也無法用完整的理論驗證哪邊怪怪。
於是想了個例子,
例如:1元給三個人,依1.1、1、1權重去分配,
=>分配結果為0、1、0,權重最大的第一個人只分到0,第二個人分得1,第三個人分到0
(以下為修改黑暗執行緒的JS程式,將範例改為極端的情況)
//1元依1.1、1、1權重分
var factor = [ 1.1, 1, 1], factorSum = 0;
var totalAmount = 1, amount = [];
for (var i = 0; i < factor.length; i++) factorSum += factor[i];
var r = "";
for (var i = 0; i < factor.length; i++){
  amount[i] = parseInt(Math.round(totalAmount * factor[i] / factorSum))
  r += "第"+(i+1)+"個人:" + totalAmount + " * " + factor[i] + " / " + factorSum + " = " + amount[i] + "<"+"br"+">";
  totalAmount -= amount[i];
  factorSum -= factor[i];
}
document.write(r);
但這樣分是對是錯,也無定論,還是要依實際需求來判斷。
後來看見原作者也發現該算法在某些情況也會怪怪的,所以又有另一篇說明
http://blog2.darkthread.net/post-2012-02-23-perfect-divide-fixing.aspx

另外在該第一篇文章下,有人回應1~(n-1)個人,先四捨五入算,
第n個人再用全部金額扣除回應1~(n-1)個人拿的金額。
看到這個,我想到一個例子:3元給5個人均分
=>分配結果為1、1、1、1、-1,第1~第4個人分別拿到1元,第5個人變成-1元
//3元5個人均分
var n = 3;
var p1=Math.round(n*(1/5));//0.6=>1
var p2=Math.round(n*(1/5));//0.6=>1
var p3=Math.round(n*(1/5));//0.6=>1
var p4=Math.round(n*(1/5));//0.6=>1
var p5 = n-(p1+p2+p3+p4);//3-4=-1
這樣的分法是對是錯,也是無定論,還是要依實際需求來判斷。
例如當1~4個人是客戶,第5個人是公司,公司不計較這負1塊錢,分配原則是客戶優先,這樣的話便無問題。
(當然也有可能發生公司得到剩餘的錢,例如2元給5個人均分)
跟前一個算法比較的優點是:對1~4人而言,他們永遠知道自己是四捨五入,不會有同樣條件,卻不同結果的情況發生。
不過用此方式,可能要先評估一下,當公司負責吸收這負值時,風險有多大。
假設剩餘無法整除的金額為x元,給n個人均分。
會產生(n-1)個人都進位的情況:0.5 ≦ (x/n) < 1
計算後:0.5n ≦ x < n
所以餘額x最少又可進位的情況是:0.5n 元
最後一個人最大可能要吸收的金額是:(n-1) - 0.5n = 0.5n - 1
所以如果有一千萬人要分,(0.5x一千萬 -1)=>可能一次就吸收快500萬

心得:
  • 分配的方式,還是要依實際需求來決定。
  • 沒辦法完全掌握的分配法,還是盡量不要用,有可能小炸掉沒發現,等到大炸掉才驚覺。
  • 有四捨五入、無條件捨去、無條件進位時,一定要能掌握這些無中生有、無故消失的部分,最後跑那邊,實際情況是否能接受。

沒有留言:

張貼留言