Linq 在集合中找到要向下舍入的最高数字,然后将其向上舍入
正如标题所描述的,我有一组对象——称之为分配——包含一个描述和一个数字。集合中的所有数字加起来都是100%,但为了便于显示,我有时会四舍五入到整数。在某些边缘情况下,将数字四舍五入后,我最终得到99% 例如: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% |
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