正如标题所描述的,我有一组对象 - 称之为分配 - 其中包含描述&一个号码。集合中的所有数字加起来都是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%甚至会更好!
答案 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;
}