考虑下面四个百分比,用浮点数表示:

    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)。


当前回答

检查如果这是有效的或不就我的测试用例,我能够得到这个工作。

假设number是k;

按降序排序百分比。 从降序遍历每个百分比。 计算k的百分比第一个百分比采取数学。输出的天花板。 下一个k = k-1 遍历直到所有百分比被消耗。

其他回答

我曾经写过一个un舍入工具,来找到一组数字的最小扰动来匹配一个目标。这是一个不同的问题,但理论上可以在这里使用类似的想法。在这种情况下,我们有一系列的选择。

因此,对于第一个元素,我们可以四舍五入到14,也可以四舍五入到13。这样做的代价(在二进制整数编程的意义上)对于向上舍入比向下舍入要小,因为向下舍入需要我们将该值移动更大的距离。同样,我们可以把每个数字四舍五入,所以我们总共有16个选择。

  13.626332
  47.989636
   9.596008
+ 28.788024
-----------
 100.000000

我通常会在MATLAB中使用bintprog(一种二进制整数编程工具)解决一般问题,但这里只有几个选项需要测试,所以用简单的循环就可以很容易地测试出16个选项中的每一个。例如,假设我们将这个集合四舍五入为:

 Original      Rounded   Absolute error
   13.626           13          0.62633
    47.99           48          0.01036
    9.596           10          0.40399
 + 28.788           29          0.21198
---------------------------------------
  100.000          100          1.25266

总绝对误差为1.25266。它可以通过以下替代舍入来略微减少:

 Original      Rounded   Absolute error
   13.626           14          0.37367
    47.99           48          0.01036
    9.596            9          0.59601
 + 28.788           29          0.21198
---------------------------------------
  100.000          100          1.19202

事实上,这就是绝对误差的最优解。当然,如果有20项,搜索空间的大小将是2^20 = 1048576。对于30或40个术语,这个空间将是相当大的。在这种情况下,您将需要使用能够有效搜索空间的工具,可能使用分支和绑定方案。

因为这里没有一个答案似乎能正确解决这个问题,下面是我使用下划线的半模糊版本:

function foo(l, target) {
    var off = target - _.reduce(l, function(acc, x) { return acc + Math.round(x) }, 0);
    return _.chain(l).
            sortBy(function(x) { return Math.round(x) - x }).
            map(function(x, i) { return Math.round(x) + (off > i) - (i >= (l.length + off)) }).
            value();
}

foo([13.626332, 47.989636, 9.596008, 28.788024], 100) // => [48, 29, 14, 9]
foo([16.666, 16.666, 16.666, 16.666, 16.666, 16.666], 100) // => [17, 17, 17, 17, 16, 16]
foo([33.333, 33.333, 33.333], 100) // => [34, 33, 33]
foo([33.3, 33.3, 33.3, 0.1], 100) // => [34, 33, 33, 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

我相信在某些情况下,这种方法可能会失效,但任何方法都至少在某种程度上是任意的,因为您基本上是在修改输入数据。

舍入的目标是产生最少的错误。当您对单个值进行舍入时,这个过程简单而直接,大多数人都很容易理解。当你同时四舍五入多个数字时,这个过程变得更加棘手——你必须定义如何组合错误,即必须最小化的错误。

Varun Vohra的答案将绝对误差的总和最小化,而且实现起来非常简单。然而,有一些边缘情况它不能处理-舍入24.25,23.25,27.25,25.25的结果应该是什么?其中一个需要被围捕,而不是减少。你可能会任意选择列表中的第一个或最后一个。

也许用相对误差比绝对误差更好。将23.25四舍五入到24会使它变化3.2%,而将27.25四舍五入到28只会使它变化2.8%。现在有一个明显的赢家。

我们还可以做进一步的调整。一种常见的技术是对每个错误进行平方运算,这样大错误的计数就不成比例地多于小错误。我还会使用非线性除数来得到相对误差——1%的误差比99%的误差重要99倍,这似乎是不对的。在下面的代码中,我使用了平方根。

完整算法如下:

将这些百分比四舍五入后相加,再减去100。这将告诉您这些百分比中有多少必须四舍五入。 为每个百分比生成两个错误分数,一个是四舍五入,另一个是四舍五入。取两者之差。 对上面产生的误差差异进行排序。 对于需要四舍五入的百分比数,从已排序的列表中选取一项,并将四舍五入后的百分比增加1。

您仍然可能有多个具有相同错误和的组合,例如33.3333333,33.3333333,33.3333333。这是不可避免的,结果完全是任意的。下面给出的代码倾向于四舍五入左边的值。

在Python中把它们放在一起是这样的。

from math import isclose, sqrt

def error_gen(actual, rounded):
    divisor = sqrt(1.0 if actual < 1.0 else actual)
    return abs(rounded - actual) ** 2 / divisor

def round_to_100(percents):
    if not isclose(sum(percents), 100):
        raise ValueError
    n = len(percents)
    rounded = [int(x) for x in percents]
    up_count = 100 - sum(rounded)
    errors = [(error_gen(percents[i], rounded[i] + 1) - error_gen(percents[i], rounded[i]), i) for i in range(n)]
    rank = sorted(errors)
    for i in range(up_count):
        rounded[rank[i][1]] += 1
    return rounded

>>> round_to_100([13.626332, 47.989636, 9.596008, 28.788024])
[14, 48, 9, 29]
>>> round_to_100([33.3333333, 33.3333333, 33.3333333])
[34, 33, 33]
>>> round_to_100([24.25, 23.25, 27.25, 25.25])
[24, 23, 28, 25]
>>> round_to_100([1.25, 2.25, 3.25, 4.25, 89.0])
[1, 2, 3, 4, 90]

正如您在最后一个示例中看到的,该算法仍然能够提供非直观的结果。尽管89.0不需要四舍五入,但是列表中的一个值需要四舍五入;相对误差最小的结果是将较大的值舍入,而不是较小的可选值。

这个答案最初主张遍历所有可能的向上舍入/向下舍入组合,但正如评论中指出的那样,更简单的方法效果更好。算法和代码反映了这种简化。

下面是一个实现了最大余数方法的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