考虑下面四个百分比,用浮点数表示:
13.626332%
47.989636%
9.596008%
28.788024%
-----------
100.000000%
我需要用整数表示这些百分比。如果我简单地使用Math.round(),我最终得到的总数是101%。
14 + 48 + 10 + 29 = 101
如果我使用parseInt(),我最终得到了97%。
13 + 47 + 9 + 28 = 97
有什么好的算法可以将任何百分比数表示为整数,同时还保持总数为100%?
编辑:在阅读了一些评论和回答后,显然有很多方法可以解决这个问题。
在我看来,为了保持数字的真实性,“正确”的结果是最小化总体误差的结果,定义为相对于实际值会引入多少误差舍入:
value rounded error decision
----------------------------------------------------
13.626332 14 2.7% round up (14)
47.989636 48 0.0% round up (48)
9.596008 10 4.0% don't round up (9)
28.788024 29 2.7% round up (29)
在平局的情况下(3.33,3.33,3.33)可以做出任意的决定(例如3,4,3)。
我不确定你需要什么程度的精度,但我要做的就是简单地把前n个数字加1,n是小数总和的上界。在这种情况下,它是3,所以我将给前3项加1,然后将其余的取整。当然,这并不是非常准确,有些数字可能会四舍五入或在不应该的时候,但它工作得很好,总是会得到100%。
因此[13.626332,47.989636,9.596008,28.788024]将是[14,48,10,28],因为Math.ceil(.626332+.989636+.596008+.788024) == 3
function evenRound( arr ) {
var decimal = -~arr.map(function( a ){ return a % 1 })
.reduce(function( a,b ){ return a + b }); // Ceil of total sum of decimals
for ( var i = 0; i < decimal; ++i ) {
arr[ i ] = ++arr[ i ]; // compensate error by adding 1 the the first n items
}
return arr.map(function( a ){ return ~~a }); // floor all other numbers
}
var nums = evenRound( [ 13.626332, 47.989636, 9.596008, 28.788024 ] );
var total = nums.reduce(function( a,b ){ return a + b }); //=> 100
你总是可以告诉用户这些数字是四舍五入的,可能不是非常准确……
下面是@varun-vohra答案的一个简单的Python实现:
def apportion_pcts(pcts, total):
proportions = [total * (pct / 100) for pct in pcts]
apportions = [math.floor(p) for p in proportions]
remainder = total - sum(apportions)
remainders = [(i, p - math.floor(p)) for (i, p) in enumerate(proportions)]
remainders.sort(key=operator.itemgetter(1), reverse=True)
for (i, _) in itertools.cycle(remainders):
if remainder == 0:
break
else:
apportions[i] += 1
remainder -= 1
return apportions
你需要math, itertools, operator。
下面是一个实现了最大余数方法的Ruby宝石:
https://github.com/jethroo/lare_round
使用方法:
a = Array.new(3){ BigDecimal('0.3334') }
# => [#<BigDecimal:887b6c8,'0.3334E0',9(18)>, #<BigDecimal:887b600,'0.3334E0',9(18)>, #<BigDecimal:887b4c0,'0.3334E0',9(18)>]
a = LareRound.round(a,2)
# => [#<BigDecimal:8867330,'0.34E0',9(36)>, #<BigDecimal:8867290,'0.33E0',9(36)>, #<BigDecimal:88671f0,'0.33E0',9(36)>]
a.reduce(:+).to_f
# => 1.0
您可以尝试跟踪由于舍入而产生的误差,如果累计误差大于当前数字的小数部分,则再反向舍入。
13.62 -> 14 (+.38)
47.98 -> 48 (+.02 (+.40 total))
9.59 -> 10 (+.41 (+.81 total))
28.78 -> 28 (round down because .81 > .78)
------------
100
不确定这是否适用于一般情况,但如果顺序相反,似乎也会有类似的效果:
28.78 -> 29 (+.22)
9.59 -> 9 (-.37; rounded down because .59 > .22)
47.98 -> 48 (-.35)
13.62 -> 14 (+.03)
------------
100
我相信在某些情况下,这种方法可能会失效,但任何方法都至少在某种程度上是任意的,因为您基本上是在修改输入数据。