如何使四舍五入的百分比加起来等于100%


192

考虑以下四个百分比,以float数字表示:

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


21
假设您有3.33、3.33和3.33。您会选哪一个4?
RobG 2012年

3
究竟。这个问题体现了术语上的矛盾。
罗恩侯爵

4
这是报表中非常常见的情况-如何显示不总是与显示值之和匹配的十进制值的“总计”。
D Stanley

1
您的示例案例中的“正确” 结果是什么?这可以解决关于“最佳”解决方案的分歧。
D Stanley

Answers:


35

由于这里的答案似乎都无法正确解决,因此这是我使用underscorejs的半混淆版本:

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]

6
如果我错了,请纠正我,但这不是我的答案提出的算法的实现吗?(未在underscorejs上清除)
vvohra87

@VarunVohra对不起,直到现在我还没有注意到这一点,是的,看起来您的算法是相同的:)不知道为什么我的帖子是可接受的答案,混淆的代码仅用于哈哈兹……
yonilevy

@yonilevy删除了我的评论;我只是不知道它应该返回一个排序列表。我道歉!
Zack Burt

2
当最后一个元素为0且先前的元素加为100时,此功能存在问题。例如[52.6813880126183、5.941146616193481、24.55310199789695、8.78203135436383、8.04416403785489、0]。最后一个逻辑上返回-1。我很快想到了以下解决方案,但可能还有更好的方法:jsfiddle.net/0o75bw43/1
Cruclax

1
@Cruclax在输入数组中所有条目均为零时显示全1
tony.0919 2016年

158

只要您不担心依赖原始十进制数据,有很多方法可以做到这一点。

第一种,也许是最受欢迎的方法是最大剩余法

基本上是:

  1. 四舍五入
  2. 求和与100之差
  3. 通过将项目按小数部分的降序加1来分配差异

在您的情况下,它将如下所示:

13.626332%
47.989636%
 9.596008%
28.788024%

如果取整数部分,则得到

13
47
 9
28

总计为97,而您想再添加三个。现在,您看一下小数部分,它们是

.626332%
.989636%
.596008%
.788024%

并选择最大的,直到总数达到100。这样您将获得:

14
48
 9
29

另外,您可以选择显示小数点后一位而不是整数。因此,数字将是48.3和23.9等。这将使方差从100下降很多。


5
美国数学学会网站上的“功能专栏” – 分配II:分配系统 –描述了几种类似的“分配”方法。
肯尼·埃维特

1
这几乎就像我的答案的副本并粘贴在此处stackoverflow.com/questions/5227215/…
泽瓦

请注意,与您对@DStanley答案的评论相反,您答案中的9.596008%被四舍五入为9%,这相差0.5%以上。仍然是一个很好的答案。
Rolazaro Azeveires

32

做到这一点的“最佳”方法(之所以称为“最佳”,是因为它是一个主观术语),可能是使您一直(非整体)地了解您的位置,并取整值。

然后将其与历史记录一起使用,以确定应该使用的值。例如,使用您提供的值:

Value      CumulValue  CumulRounded  PrevBaseline  Need
---------  ----------  ------------  ------------  ----
                                  0
13.626332   13.626332            14             0    14 ( 14 -  0)
47.989636   61.615968            62            14    48 ( 62 - 14)
 9.596008   71.211976            71            62     9 ( 71 - 62)
28.788024  100.000000           100            71    29 (100 - 71)
                                                    ---
                                                    100

在每个阶段,您都不会对数字本身进行四舍五入。取而代之的是,您对累计值进行四舍五入,并计算出从上一个基线达到该值的最佳整数-该基线是前一行的累积值(四舍五入)。

之所以可行,是因为您不会在每个阶段都丢失信息,而是更智能地使用信息。最后一列是“正确”的四舍五入值,您可以看到它们的总和为100。

您可以在上面的第三个值中看到此值与盲目舍入每个值之间的差异。尽管9.596008通常会四舍五入到10,但是累积71.211976正确地四舍五入到71-这意味着只9需要添加到的先前基准62


这也适用于“有问题的”序列,例如三个概略值,其中一个应四舍五入:1/3

Value      CumulValue  CumulRounded  PrevBaseline  Need
---------  ----------  ------------  ------------  ----
                                  0
33.333333   33.333333            33             0    33 ( 33 -  0)
33.333333   66.666666            67            33    34 ( 67 - 33)
33.333333   99.999999           100            67    33 (100 - 67)
                                                    ---
                                                    100

1
第二种方法解决了这两个问题。第一个给26, 25, 26, 23,第二个1, 0, 1, 0, 1, 0, ...
paxdiablo 2015年

这种方法也适用于舍入小数,因为它可以防止输出出现负数
Jonty5817

18

舍入的目的是产生最少的错误。当您舍入单个值时,该过程非常简单明了,大多数人都容易理解。当您同时四舍五入多个数字时,该过程将变得更加棘手-您必须定义错误的合并方式,即必须将错误最小化。

通过Varun的沃赫拉良好的投答案最小的绝对误差的总和,这是很容易实现。但是,在某些情况下,它无法处理-四舍五入的结果是24.25, 23.25, 27.25, 25.25什么?其中之一需要四舍五入而不是向下舍入。您可能会随意选择列表中的第一个或最后一个。

也许最好使用相对误差而不是绝对误差。将23.25向上舍入到24会使它改变3.2%,而将27.25向上舍入到28只会使它改变2.8%。现在有一个明显的赢家。

有可能进一步调整。一种常见的技术是对每个错误求平方,以使大错误比小错误占更多比例。我还将使用非线性除数来获得相对误差-1%的误差比99%的误差重要99倍似乎并不正确。在下面的代码中,我使用了平方根。

完整的算法如下:

  1. 将所有百分比四舍五入后求和,然后减去100。这将告诉您必须舍入这些百分比中的多少。
  2. 为每个百分比生成两个错误分数,一个向下取整,一个向上取整。取两者之间的差异。
  3. 排序上面产生的错误差异。
  4. 对于需要四舍五入的百分比数,请从排序列表中取出一项,然后将四舍五入的百分比增加1。

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

用Python将它们放在一起看起来像这样。

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不需要四舍五入,该列表中的值之一也需要四舍五入。相对误差最小的原因是取整较大的值而不是取整很小的选择。

这个答案最初提倡进行向上/向下取整的所有可能组合,但是正如注释中指出的那样,一种更简单的方法效果更好。算法和代码反映了这种简化。


1
我认为您不需要考虑所有组合:以减少加权误差从无到有,从到无穷大的顺序进行处理(相当多的只是在Verun Vohrasyonilevy(“相同”)的答案中引入了权)。
灰胡子

@greybeard你是对的,我对此太想了。我不能仅仅对错误进行排序,因为每个值都有两个错误,但是采取差异解决了这个问题。我已经更新了答案。
Mark Ransom

当实际数字为0%时,我希望总有0%。所以加入if actual == 0: return 0error_gen伟大的作品。
Nikolay Baluk '16

1
isclose开头的方法是 round_to_100什么?
toto_tico


7

不要对四舍五入的数字求和。您将得到不正确的结果。根据项数和小数部分的分布,总数可能会显着减少。

显示四舍五入的数字,但将实际值相加。根据您显示数字的方式,实际的方式会有所不同。这样你得到

 14
 48
 10
 29
 __
100

无论采取哪种方式,都会出现差异。在您的示例中,没有办法显示总计为100的数字,而不会以错误的方式“舍入”一个值(最小误差将9.596更改为9)

编辑

您需要选择以下选项之一:

  1. 物品的准确性
  2. 总和的准确性(如果要对舍入后的值求和)
  3. 四舍五入项与四舍五入之和之间的一致性)

在大多数情况下,处理#3百分比是最好的选择,因为当总数等于101%时,比单个项目的总和不等于100时,它会更加明显,并且您保持单个项目的准确性。我认为将9.596调整为9并不正确。

为了解释这一点,我有时会添加一个脚注,以解释各个值是四舍五入的,可能未必总和为100%-任何了解四舍五入的人都应该能够理解该解释。


6
这不是很有帮助,因为打印的值总计不超过100。问题的目的是防止用户认为值不正确,在这种情况下,大多数人在查看并与总数进行比较时会这样做。
vvohra87 2012年

@VarunVohra阅读了我的编辑,您无法显示您的数字,以至于它们加起来等于100,而不能“舍入”大于0.5的数字。
D Stanley

1
实际上,@ DStanley可以,除非所有数字都小于0.5的集合除外。检查我的答案-LRM就是这么做的。
vvohra87

3
@VarunVohra在原来的例子LRM将产生14,48,9,和29将“圆” 9.596至9.如果我们分配基于整数LRM将是最准确的,但它仍然被更多改变一个结果超过半单位。
D Stanley

7

我写了一个C#版本的舍入助手,该算法与Varun Vohra的答案相同,希望对您有所帮助。

public static List<decimal> GetPerfectRounding(List<decimal> original,
    decimal forceSum, int decimals)
{
    var rounded = original.Select(x => Math.Round(x, decimals)).ToList();
    Debug.Assert(Math.Round(forceSum, decimals) == forceSum);
    var delta = forceSum - rounded.Sum();
    if (delta == 0) return rounded;
    var deltaUnit = Convert.ToDecimal(Math.Pow(0.1, decimals)) * Math.Sign(delta);

    List<int> applyDeltaSequence; 
    if (delta < 0)
    {
        applyDeltaSequence = original
            .Zip(Enumerable.Range(0, int.MaxValue), (x, index) => new { x, index })
            .OrderBy(a => original[a.index] - rounded[a.index])
            .ThenByDescending(a => a.index)
            .Select(a => a.index).ToList();
    }
    else
    {
        applyDeltaSequence = original
            .Zip(Enumerable.Range(0, int.MaxValue), (x, index) => new { x, index })
            .OrderByDescending(a => original[a.index] - rounded[a.index])
            .Select(a => a.index).ToList();
    }

    Enumerable.Repeat(applyDeltaSequence, int.MaxValue)
        .SelectMany(x => x)
        .Take(Convert.ToInt32(delta/deltaUnit))
        .ForEach(index => rounded[index] += deltaUnit);

    return rounded;
}

它通过了以下单元测试:

[TestMethod]
public void TestPerfectRounding()
{
    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> {3.333m, 3.334m, 3.333m}, 10, 2),
        new List<decimal> {3.33m, 3.34m, 3.33m});

    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> {3.33m, 3.34m, 3.33m}, 10, 1),
        new List<decimal> {3.3m, 3.4m, 3.3m});

    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> {3.333m, 3.334m, 3.333m}, 10, 1),
        new List<decimal> {3.3m, 3.4m, 3.3m});


    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> { 13.626332m, 47.989636m, 9.596008m, 28.788024m }, 100, 0),
        new List<decimal> {14, 48, 9, 29});
    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> { 16.666m, 16.666m, 16.666m, 16.666m, 16.666m, 16.666m }, 100, 0),
        new List<decimal> { 17, 17, 17, 17, 16, 16 });
    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> { 33.333m, 33.333m, 33.333m }, 100, 0),
        new List<decimal> { 34, 33, 33 });
    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> { 33.3m, 33.3m, 33.3m, 0.1m }, 100, 0),
        new List<decimal> { 34, 33, 33, 0 });
}

真好!给了我一个基础。.尽管我相信,Enumerable没有ForEach
Jack0fshad0ws

4

您可以尝试跟踪由于四舍五入而导致的错误,如果累积的错误大于当前数字的小数部分,则可以对纹理进行四舍五入。

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

我确定在某些情况下这可能会崩溃,但是由于您基本上是在修改输入数据,因此任何方法都至少在某种程度上是任意的。


2
会计师和银行家使用类似技术已有数百年历史了。从另一行“携带其余部分”。从“进位”的一分钱的1/2开始。将“进位”添加到第一个值,然后截断。现在,您因截断而损失的金额被放入“进位额”中。一直向下进行,四舍五入后的数字每次都会精确地合计为所需的总数。
杰夫·格里格

Carolyn Kay在Access VB 2007中建议此实现:<code>'使用“剩余”方法ref圆退款美元ref1 = rsQry![退款已支付的$$$] * rsQry![属性值] / propValTot ref2 = ref1 + ref5 '将剩余的零相加,以开始ref3 = ref2 * 100'将100乘以一个整数ref4 = ref3 / 100'将100除以一个十进制数rsTbl![退款已支付的$$$] = ref4'将“表中的余数”四舍五入的数字ref5 = ref2-ref4'进行新的余数</ code>
Jeff Grigg

2

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

因此,对于第一个元素,我们可以将其舍入为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个字词,搜索空间的大小将很大。在这种情况下,您可能需要使用可以有效地搜索空间的工具,也许使用分支定界方案。


仅供以后参考:“最大余数”算法必须根据您的度量标准将总绝对误差最小化(请参阅@varunvohra的答案)。证明很简单:假设它不会使错误最小化。然后必须有一些要四舍五入的值,应该四舍五入,反之亦然(两组的大小相同)。但是,它四舍五入的每个值都比下一个整数(和vv)离下一个整数更远,因此新的错误量必须更大。QED。但是,它不适用于所有错误指标;需要其他算法。
rici 2012年

2

我认为以下将实现您的追求

function func( orig, target ) {

    var i = orig.length, j = 0, total = 0, change, newVals = [], next, factor1, factor2, len = orig.length, marginOfErrors = [];

    // map original values to new array
    while( i-- ) {
        total += newVals[i] = Math.round( orig[i] );
    }

    change = total < target ? 1 : -1;

    while( total !== target ) {

        // Iterate through values and select the one that once changed will introduce
        // the least margin of error in terms of itself. e.g. Incrementing 10 by 1
        // would mean an error of 10% in relation to the value itself.
        for( i = 0; i < len; i++ ) {

            next = i === len - 1 ? 0 : i + 1;

            factor2 = errorFactor( orig[next], newVals[next] + change );
            factor1 = errorFactor( orig[i], newVals[i] + change );

            if(  factor1 > factor2 ) {
                j = next; 
            }
        }

        newVals[j] += change;
        total += change;
    }


    for( i = 0; i < len; i++ ) { marginOfErrors[i] = newVals[i] && Math.abs( orig[i] - newVals[i] ) / orig[i]; }

    // Math.round() causes some problems as it is difficult to know at the beginning
    // whether numbers should have been rounded up or down to reduce total margin of error. 
    // This section of code increments and decrements values by 1 to find the number
    // combination with least margin of error.
    for( i = 0; i < len; i++ ) {
        for( j = 0; j < len; j++ ) {
            if( j === i ) continue;

            var roundUpFactor = errorFactor( orig[i], newVals[i] + 1)  + errorFactor( orig[j], newVals[j] - 1 );
            var roundDownFactor = errorFactor( orig[i], newVals[i] - 1) + errorFactor( orig[j], newVals[j] + 1 );
            var sumMargin = marginOfErrors[i] + marginOfErrors[j];

            if( roundUpFactor < sumMargin) { 
                newVals[i] = newVals[i] + 1;
                newVals[j] = newVals[j] - 1;
                marginOfErrors[i] = newVals[i] && Math.abs( orig[i] - newVals[i] ) / orig[i];
                marginOfErrors[j] = newVals[j] && Math.abs( orig[j] - newVals[j] ) / orig[j];
            }

            if( roundDownFactor < sumMargin ) { 
                newVals[i] = newVals[i] - 1;
                newVals[j] = newVals[j] + 1;
                marginOfErrors[i] = newVals[i] && Math.abs( orig[i] - newVals[i] ) / orig[i];
                marginOfErrors[j] = newVals[j] && Math.abs( orig[j] - newVals[j] ) / orig[j];
            }

        }
    }

    function errorFactor( oldNum, newNum ) {
        return Math.abs( oldNum - newNum ) / oldNum;
    }

    return newVals;
}


func([16.666, 16.666, 16.666, 16.666, 16.666, 16.666], 100); // => [16, 16, 17, 17, 17, 17]
func([33.333, 33.333, 33.333], 100); // => [34, 33, 33]
func([33.3, 33.3, 33.3, 0.1], 100); // => [34, 33, 33, 0] 
func([13.25, 47.25, 11.25, 28.25], 100 ); // => [13, 48, 11, 28]
func( [25.5, 25.5, 25.5, 23.5], 100 ); // => [25, 25, 26, 24]

最后一件事,我使用问题中最初给出的数字来运行该函数以与所需的输出进行比较

func([13.626332, 47.989636, 9.596008, 28.788024], 100); // => [48, 29, 13, 10]

这与问题想要的=> [48,29,14,9]不同。直到我看了总误差幅度,我才明白这一点

-------------------------------------------------
| original  | question | % diff | mine | % diff |
-------------------------------------------------
| 13.626332 | 14       | 2.74%  | 13   | 4.5%   |
| 47.989636 | 48       | 0.02%  | 48   | 0.02%  |
| 9.596008  | 9        | 6.2%   | 10   | 4.2%   |
| 28.788024 | 29       | 0.7%   | 29   | 0.7%   |
-------------------------------------------------
| Totals    | 100      | 9.66%  | 100  | 9.43%  |
-------------------------------------------------

本质上,我函数的结果实际上引入了最少的错误。

在这里摆弄


这几乎就是我的想法,区别在于应该相对于值来测量误差(将9.8舍入为10的误差比将19.8舍入为20的误差更大)。不过,可以通过将其反映在sort回调中来轻松完成此操作。
poezn 2012年

对于[33.33,33.33,33.33,0.1],这是错误的,它返回[
1、33、33、33

@yonilevy谢谢你。立即修复。
布鲁诺

尚未,对于[16.666、16.666、16.666、16.666、16.666、16.666],它返回[15、17、17、17、17、17、17],而不是[16、16、17、17、17、17、17]-参见我的答案
yonilevy

2

我不确定您需要什么精度,但是我要做的只是将第一个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

您可以随时通知用户数字是四舍五入的,可能不是非常准确...


1

如果要四舍五入,在任何情况下都无法获得完全相同的好方法。

您可以使用N百分比的小数部分(在示例中为4)。

添加小数部分。在您的示例中,小数部分的总数= 3。

找出分数最高的3个数字,其余的取小数。

(对不起,修改)


1
虽然这可能提供了增加100个号码,你可能最终转向3.9到3和25.1到26
RobG

没有。3.9将是4,而25.1将是25。我说将3个数字最高的分数而不是最高的数值作为上限。
arunlalam 2012年

2
如果有太多以.9结尾的分数,则说9个值为9.9%,一个值为10.9,那么一个值将最终为9%,8为10%和1个为11%。
arunlalam 2012年

1

如果您真的必须对它们进行四舍五入,这里已经有很好的建议(最大的余量,相对的误差最小,等等)。

已经有一个很好的理由不进行四舍五入(您将获得至少一个“看起来更好”但“错误”的数字),以及如何解决该问题(警告读者),这就是我要做的。

让我补充“错误”的数字部分。

假设您有三个事件/实体/ ...,其中一些百分比近似为:

DAY 1
who |  real | app
----|-------|------
  A | 33.34 |  34
  B | 33.33 |  33
  C | 33.33 |  33

稍后值会稍微改变,以

DAY 2
who |  real | app
----|-------|------
  A | 33.35 |  33
  B | 33.36 |  34
  C | 33.29 |  33

第一个表具有已经提到的“错误”数字问题:33.34比33更接近33。

但是现在您有一个更大的错误。将第2天与第1天进行比较,A的实际百分比值增加了0.01%,但近似值显示减少了1%。

那是一个定性误差,可能比最初的定量误差还差。

可以为整个集合设计一个近似值,但是,您可能必须在第一天发布数据,因此您不会知道第二天。因此,除非您真的必须真的近似,否则最好不要这么做。


任何知道如何制作更好桌子的人,请编辑或告诉我如何/在哪里
Rolazaro Azeveires

0

就我的测试用例而言,检查它是否有效,我可以使它正常工作。

假设数字是k;

  1. 按降序排序百分比。
  2. 从降序开始迭代每个百分比。
  3. 为第一个百分比计算k的百分比,取输出的Math.Ceil。
  4. 下一个k = k-1
  5. 迭代直到所有百分比消耗完。

0

我已经从Varun Vohra的答案中实现了列表和字典的方法。

import math
import numbers
import operator
import itertools


def round_list_percentages(number_list):
    """
    Takes a list where all values are numbers that add up to 100,
    and rounds them off to integers while still retaining a sum of 100.

    A total value sum that rounds to 100.00 with two decimals is acceptable.
    This ensures that all input where the values are calculated with [fraction]/[total]
    and the sum of all fractions equal the total, should pass.
    """
    # Check input
    if not all(isinstance(i, numbers.Number) for i in number_list):
        raise ValueError('All values of the list must be a number')

    # Generate a key for each value
    key_generator = itertools.count()
    value_dict = {next(key_generator): value for value in number_list}
    return round_dictionary_percentages(value_dict).values()


def round_dictionary_percentages(dictionary):
    """
    Takes a dictionary where all values are numbers that add up to 100,
    and rounds them off to integers while still retaining a sum of 100.

    A total value sum that rounds to 100.00 with two decimals is acceptable.
    This ensures that all input where the values are calculated with [fraction]/[total]
    and the sum of all fractions equal the total, should pass.
    """
    # Check input
    # Only allow numbers
    if not all(isinstance(i, numbers.Number) for i in dictionary.values()):
        raise ValueError('All values of the dictionary must be a number')
    # Make sure the sum is close enough to 100
    # Round value_sum to 2 decimals to avoid floating point representation errors
    value_sum = round(sum(dictionary.values()), 2)
    if not value_sum == 100:
        raise ValueError('The sum of the values must be 100')

    # Initial floored results
    # Does not add up to 100, so we need to add something
    result = {key: int(math.floor(value)) for key, value in dictionary.items()}

    # Remainders for each key
    result_remainders = {key: value % 1 for key, value in dictionary.items()}
    # Keys sorted by remainder (biggest first)
    sorted_keys = [key for key, value in sorted(result_remainders.items(), key=operator.itemgetter(1), reverse=True)]

    # Otherwise add missing values up to 100
    # One cycle is enough, since flooring removes a max value of < 1 per item,
    # i.e. this loop should always break before going through the whole list
    for key in sorted_keys:
        if sum(result.values()) == 100:
            break
        result[key] += 1

    # Return
    return result

0

这是@ 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

你需要mathitertoolsoperator


0

对于那些在熊猫系列中具有百分比的用户,这是我对最大余数方法的实现(如Varun Vohra的答案所示),您甚至可以选择要舍入的小数。

import numpy as np

def largestRemainderMethod(pd_series, decimals=1):

    floor_series = ((10**decimals * pd_series).astype(np.int)).apply(np.floor)
    diff = 100 * (10**decimals) - floor_series.sum().astype(np.int)
    series_decimals = pd_series - floor_series / (10**decimals)
    series_sorted_by_decimals = series_decimals.sort_values(ascending=False)

    for i in range(0, len(series_sorted_by_decimals)):
        if i < diff:
            series_sorted_by_decimals.iloc[[i]] = 1
        else:
            series_sorted_by_decimals.iloc[[i]] = 0

    out_series = ((floor_series + series_sorted_by_decimals) / (10**decimals)).sort_values(ascending=False)

    return out_series

-1

这是银行家四舍五入的情况,也就是“四舍五入”。BigDecimal支持它。其目的是确保四舍五入平衡,即既不利于银行也不利于客户。


5
它不能确保四舍五入的平衡-它只是通过在偶数和奇数之间分配半舍入来减少错误的数量。在某些情况下,银行家四舍五入会产生不正确的结果。
D Stanley

@DStanley同意。我没有说其他。我说了它的目的。很小心。
罗恩侯爵

2
足够公平-我曲解了你想说的话。无论哪种情况,我都不认为这可以解决问题,因为使用银行家取整不会改变示例中的结果。
D Stanley
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.