代码之家  ›  专栏  ›  技术社区  ›  Jamiec

找到要向下舍入的集合中的最高数字,然后将其向上舍入

  •  5
  • Jamiec  · 技术社区  · 14 年前

    分配 -包含描述和数字。集合中的所有数字加起来都是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%被四舍五入为0.42,而65.23%仅被四舍五入为0.23

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

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

    这些都放在一个 IEnumerable<Allocation> . 所以,可能使用LINQ,我如何确定哪一个是要取整的。或者更具体地说,如何生成新的 IEnumerable<分配>

    如果有人有任何其他建议总是使四舍五入的百分比总是等于100%,那就更好了!

    5 回复  |  直到 14 年前
        1
  •  3
  •   Amy B    14 年前

    正如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。。。您可能希望该项目始终被选中。如果预期多次运行的结果一致,则应打破联系。

        2
  •  3
  •   supercat    14 年前

        3
  •  2
  •   Hans Olsson    14 年前

    关于获得100%,为什么不先运行一次原始计算,看看你得到了多少百分比,然后你就知道你需要向上和向下取整多少,看它与100%有多少个百分点不同。

    所以如果你的分数是97%,那就把3个数字向上舍入,而不是向下舍入。 或者如果你的分数是102%,那就用最小的小数(0.5以上)向下舍入两个数字,而不是向上舍入。

        4
  •  1
  •   James Curran    14 年前
     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)
                                      };
    
        5
  •  0
  •   Steven Evers    14 年前

        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;
        }