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

Linq 在集合中找到要向下舍入的最高数字,然后将其向上舍入,linq,c#-3.0,ienumerable,rounding,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% |

正如标题所描述的,我有一组对象——称之为分配——包含一个描述和一个数字。集合中的所有数字加起来都是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%Edit:我所说的“要向下舍入的最高的一个”是指被舍入得最远的一个。因此,1.42%被四舍五入0.42,而65.23只被四舍五入0.23

现在代码,我有一个类

public class Allocation
{
  public string Description {get;set;}
  public doubel Percentage {get;set;}
}
这些数据保存在
IEnumerable
中。因此,可能使用LINQ,我如何确定哪一个是要取整的。或者更具体地说,我如何生成一个新的带有四舍五入数字的
IEnumerable


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

关于获得100%,为什么不先运行一次原始计算,看看您获得了多少百分比,然后通过查看它与100%之间的差异,知道需要向上和向下取整多少百分比

因此,如果你最终得到97%,则将3个数字向上取整,而不是向下取整。
或者,如果最后的结果是102%,则将两个数字的最小小数(超过0.5)向下舍入,而不是向上舍入

正如ho1所指出的,将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)
                                  };
考虑以下情况:

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();
公共类分配RoundingWrapper
{
公共分配原始{get;set;}
公共双舍入{get;set;}
公共双导入错误()
{
返回四舍五入-原始。百分比;
}
}
//将分配投影到包装器中以进行舍入。
列表源=GetAllocations();
列表roundingWrappers=源
.Select(a=>newAllocationRoundingWrapper()
{
原件=a,
四舍五入=数学四舍五入(a.百分比)
}).ToList();
int error=(int)roundingWrappers.Sum(x=>x.IntroductError());
//将舍入误差分布到各个区域
//绝对误差最大的项目。
List orderedItems=错误>0?
roundingWrappers.OrderByDescending(x=>x.IntroductError()).ToList():
roundingWrappers.OrderBy(x=>x.IntroductError()).ToList();
IEnumerator枚举器=orderedItems.GetEnumerator();
而(错误>0)
{
枚举数。MoveNext();
枚举数.Current.Rounded+=1.0;
误差-=1;
}
而(误差<0)
{
枚举数。MoveNext();
枚举数.Current.Rounded-=1.0;
误差+=1;
}
//将项目返回到分配中以获得结果
列表结果=roundingWrappers
.Select(x=>newallocation()
{
Description=x.Original.Description,
百分比=x四舍五入
}).ToList();

注:根据引入的错误进行订购可能会导致打结。考虑3个项目,只有一个项目将得到+1…您可能希望该项目的选择是一致的。如果多次运行的结果一致,则应打破联系

我建议总是四舍五入,然后如果结果是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)。

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

    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;
    }
static List Round2(列出实际分配)
{
列表实际值=新列表();
实际添加范围(实际分配);
列表四舍五入=新列表();
foreach(实际的var a)
四舍五入。添加(数学四舍五入(a));
如果(四舍五入的.Sum()==100)
{
返回四舍五入;
}
其他的
{
bool roundUp=rounded.Sum()<100;
对于(int i=0;i
只是澄清一下,它只能是99%或100%——确定我有99%的情况并不是一个问题——它知道集合中的哪个项目应该向上取整,而不是向下取整。@Jamiec:很公平,但只是出于好奇,你真的确定它只能是99%或100%?您是否有其他检查以确保值不可能是:65.47、25.51、7.51和1