找到要舍入的集合中的最大数字,然后将其四舍五入

时间:2010-08-31 16:17:51

标签: linq c#-3.0 ienumerable rounding

正如标题所描述的,我有一组对象 - 称之为分配 - 其中包含描述&一个号码。集合中的所有数字加起来都是100%,但出于显示目的,我有时会达到整数百分比。在某些边缘情况下,对数字进行舍入后,最终得出99%。

示例:

Description  | Actual | Rounded
===============================
Allocation A | 65.23% | 65% 
Allocation B | 25.40% | 25%
Allocation C | 7.95%  | 8%
Allocation D | 1.42%  | 1%
===============================
Total        | 100%   | 99% (Bad!)

所要求的解决方案,即不完美但将要做的,是找到要舍入的最高解,然后将其四舍五入。在上面的示例中,四舍五入时1.42%将变为2%。 编辑:通过“最高的一个向下舍入”我的意思是最圆的一个。因此1.42%下调0.42而65.23仅下调0.23

所以现在代码,我有一个类

public class Allocation
{
  public string Description {get;set;}
  public doubel Percentage {get;set;}
}

这些是IEnumerable<Allocation>。因此,可能使用LINQ,我如何确定哪一个是圆形的。或者更具体地说,如何生成带有舍入数字的新IEnumerable<Allocation>

如果有人有任何其他建议,总是将圆整百分比总是等于100%甚至会更好!

5 个答案:

答案 0 :(得分:3)

正如ho1所指出的,将1添加到特定行的解决方案并不能解决实际问题。

考虑以下情况:

3 items evenly divided, 100/3 = 33 ; 33 * 3 = 99 ; Error = -1
7 items evenly divided, 100/7 = 14 ; 14 * 7 = 98 ; Error = -2
66 items evenly divided, 100/66 = 2 ; 2 * 66 = 132 ; Error = 32

这是一些未经测试的代码,可能会让您接近您需要去的地方。这里可能有一个标志错误,所以要小心。

public class AllocationRoundingWrapper
{
  public Allocation Original {get;set;}
  public double Rounded {get;set;}
  public double IntroducedError()
  {
    return  Rounded - Original.Percentage;
  }
}

  //project the Allocations into Wrappers for rounding efforts.

List<Allocation> source = GetAllocations();

List<AllocationRoundingWrapper> roundingWrappers = source
  .Select(a => new AllocationRoundingWrapper()
  {
    Original = a,
    Rounded = Math.Round(a.Percentage)
  }).ToList();

int error = (int) roundingWrappers.Sum(x => x.IntroducedError());

  //distribute the rounding errors across the
  // items with the absolute largest error.

List<RoundingWrapper> orderedItems = error > 0 ?
  roundingWrappers.OrderByDescending(x => x.IntroducedError()).ToList() :
  roundingWrappers.OrderBy(x => x.IntroducedError()).ToList();

IEnumerator<RoundingWrapper> enumerator = orderedItems.GetEnumerator();

while(error > 0)
{
  enumerator.MoveNext();
  enumerator.Current.Rounded += 1.0;
  error -= 1;
}
while(error < 0)
{
  enumerator.MoveNext();
  enumerator.Current.Rounded -= 1.0;
  error += 1;
}

  //project back into Allocations for the result
List<Allocation> result = roundingWrappers
  .Select(x => new Allocation()
  {
    Description = x.Original.Description,
    Percentage = x.Rounded
  }).ToList();

注意:按引入的错误排序可能会导致联系。考虑3项情况,只有一项会得+1 ......你可能希望一致地选择该项目。如果预期多次运行会得到一致的结果,那么应该打破联系。

答案 1 :(得分:3)

我建议总是向下舍入,然后如果结果是100-n,用'n'最大残差对数字进行四舍五入。这适用于任何数据。绕到最近然后尝试调整结果的方法往往更复杂。我不认为这样的分配(四舍五入到0.01%)加起来达到100.00%,这说明当它们四舍五入到最接近的0.1%或1%时会发生什么。

另一种方法是使用舍入到最近的方式进行初始计算,然后如果结果不能100%将所有数字除以总百分比,则再试一次。因此,如果最终百分比为101%,则将所有(未接地)数字除以1.01,并重复循环和总计序列。这将给出稍微不同的结果,人们可能会发现或多或少需要。假设数字是1.3 1.3 1.3 96.1。四舍五入时,总数为99.将1.3中的一个舍入到2将使总数达到100,但舍入会使值扭曲53%而不是23%;相比之下,将96.1舍入到97将表示其值的失真约为0.95%(97对96.1)。

答案 2 :(得分:2)

关于获得100%,为什么不首先运行原始计算,看看你获得了多少百分比,然后通过查看它与100%相差多少个百分点来了解你需要多少与之相比。

因此,如果你最终得到97%,那么第3轮数字而不是下降。 或者如果最终得到102%,则将两个数字用最小的小数(超过0.5)向下舍入而不是向上。

答案 3 :(得分:1)

 var HighestDown = 
       allocation.Where(a=>Math.Round(a.Percentage) == Math.Floor(a.Percentage)
                 .Max(a=>a.Percentage - Math.Floor(a.Percentage));

  HighestDown.Percentage = Math.Ceiling(HighestDown.Percentage);

  var roundedAllocations = for a in allocation
                           select new Allocation
                                  {
                                       Description = a.Description,
                                       Percentage = Math.Round(a.Percentage)
                                  };

答案 4 :(得分:0)

我认为这就是你要找的东西。它可能会被清理和优化,但在决定以另一种方式对数字进行舍入时,它需要最大的舍入距离。

    static List<double> Round2(List<double> actualAllocations)
    {
        List<double> actual = new List<double>();
        actual.AddRange(actualAllocations);

        List<double> rounded = new List<double>();
        foreach (var a in actual)
            rounded.Add(Math.Round(a));

        if (rounded.Sum() == 100)
        {
            return rounded;
        }
        else
        {
            bool roundUp = rounded.Sum() < 100;

            for (int i = 0; i < Math.Abs(100 - rounded.Sum()); i++)
            {
                var index = actual.IndexOf(
                    (from a in actual
                    orderby Math.Abs(Math.Round(a) - a) descending
                    select a).First());

                if (roundUp)
                    actual[index]++;
                else
                    actual[index]--;
            }
        }

        rounded.Clear();
        foreach (var a in actual)
            rounded.Add(Math.Round(a));

        return rounded;
    }