今天我的一位朋友在网站上向我展示了一个错误。 (Link)
您可以看到百分比的总和为100.1%。
49 + 20.7 + 10.9 + 7 + 5.5 + 7 = 100.1%
我想:如果我编程并且有7个(浮动)数字,我怎么能解决这个问题呢?
假设所有数字都有一个小数位:
49.0 + 20.7 + 10.9 + 7.0 + 5.5 + 7.0 = 100.1%
我认为这是一个四舍五入的问题,我没有看到错误的其他解释。
e.g:
49.5%+ 50.5%= 100%,如果我们整理它们,50%+ 51%= 101%。
但在这种情况下,因为我们有两个数字,所以我们可以使用该数据来均匀。
49.5%+ 50.5%= 100%,如果我们转向均匀,50%+ 50%= 100%。
这些数字已经被污染,因为它们的总和是100.1%,因此,至少有一个数字等于自己减去0.5。
在这7个数字的例子中,舍入到偶数不起作用,因为它不会在以下总和中:
49 + 20.65 + 10.85 + 7 + 5.5 + 7 = 100%
49 + 20.6 + 10.8 + 7 + 5.5 + 7 = 99.9%
有没有简单/快捷的方法来解决这个问题?使用不同的舍入方法?
或者我能解决这个问题的唯一方法是创建一个函数来专门处理我的问题?
注意:我不是在寻找特定的编程语言方法。我希望我能用不同的语言解决这个问题(例如在PHP和C ++中)。
答案 0 :(得分:3)
首先,您对可能原因的分析不是这里的实际原因。 但是,您所谈论的概念在舍入方案中称为 偏见 。它确实存在并且是真实的 - 它不仅仅是您特定问题的原因。
在您声称的示例中导致:49.5%+ 50.5%= 100%,向上舍入,50%+ 51%= 101%。 向上舍入(向正向无穷大舍入)相当于正数向左舍入为零。请参阅1下面的舍入方案列表。 但是,除非你碰巧得到两个相同的数字,这些数字与a.5和b.5相同,而不是a.7 + b.8 + c.5,或者说,这几乎不可能成为原因。任何其他数字组合。 为了证明为什么这不是真正的原因,在这个n个数字的列表中,有(n-1)个相邻的对,如果我们做出合理的假设,即每个最后一个数字是同等可能的,那么得到相邻数字a的机会。 5,b.5仅为(0.1)^ 2 = 0.01
无论如何,这里的真正原因是 由于缺少精度而引入的数值误差(由于数字的截断表示转换为字符串'%2.1f') (无论使用哪种语言,大概是PHP,Javascript或Java)......
通常最简单的解决方案是 只需提高精度 。严格来说,您可能只需要一个(或两个)数字,但IEEE 754 floats give you 23 digits of mantissa for free, so everyone uses that。
然而,如果你真的坚持要设置自己的(人为的)挑战,即在精确度缺失的情况下舍入数字,并且在约束条件下它们必须总和为100.0%(或最大化它们的机会),有几个较少使用的舍入方案。 您可以在教科书中找到这些,但由于显而易见的原因,它们在现实世界中并没有被广泛使用,因为它们会引入随机性和可能的非确定性(尽管您可以设置随机种子,至少确保可重复性)。
所以无论它在这里有什么价值,那些舍入方案(以及其他许多方案,请参阅整篇文章):
[2] http://en.wikipedia.org/wiki/Rounding#Tie-breaking
以下所有结果导致q = .5情况的偏差,并且你说你想要完全避免使用它们(而不是携带额外的精度,这会使问题消失):
以下是您感兴趣的内容:
如果y的小数部分是.5,则在y + 0.5和y - 0.5之间随机选择q,概率相等。 优点:基本上没有整体偏差;但它也是“公平的”在偶数和奇数q值之间。另一方面,它在结果中引入了随机分量;对相同数据执行两次相同的计算可能产生两种不同的结果。此外,如果人类(而不是计算机或偶然机器)是随机的,那么它对无意识的偏见是开放的。决定回合的方向。
如果小数部分为0.5,则向上舍入并向下舍入:对于第一次出现的0.5小数部分,向上舍入;第二次出现,向下舍入;等等。 如果可以有效地编号出现0.5个小数部分,则这抑制了结果的随机分量。但如果出现的总数是奇数,它仍然可以根据分配给第一次出现的舍入方向引入正偏差或负偏差。
如果你想阅读所有这些东西(计算机算术和实现它的硬件电路),一个很好的参考(在硬件方面很重)是
计算机算术算法,以色列Koren的第2版 www.ecs.umass.edu/ece/koren/arith/ 马萨诸塞大学 阿默斯特,2010年
答案 1 :(得分:1)
您不应累积舍入值,而应使用(几乎)精确值。
解决问题的另一种方法可能是:
if (sum > 100){
sum = 100;
}
另一种方法是通过两个和的差来校正值,如注释中所述(算法的实现在JavaScript中,快速和脏的仅用于演示目的,并且仅在舍入到完整整数时有效,否则它需要稍微修改一下):
var numbers = [49, 20.7, 10.8, 7, 5.5, 7];
var roundedNumbers = [49, 21, 11, 7, 6, 7];
var sum = numbers.sum();
var roundedSum = roundedNumbers.sum();
while (roundedSum != sum){
var isRoundeSumLarger = roundedSum > sum;
var maxDifferenceIndex;
var maxDifferenceValue = 0;
for (var n = 0; n < numbers.length; n++){
var difference = Math.abs(roundedNumbers[n] - numbers[n]);
if ((isRoundeSumLarger && roundedNumbers[n] > numbers[n] && maxDifferenceValue < difference)
||(!isRoundeSumLarger && roundedNumbers[n] < numbers[n] && maxDifferenceValue < difference)){
maxDifferenceValue = difference;
maxDifferenceIndex = n;
}
}
var modifyValue = (isRoundeSumLarger ? -1 : 1); // or whatever granularity the rounding algorithm is
roundedNumbers[maxDifferenceIndex] += modifyValue;
roundedSum += modifyValue;
}
console.log(numbers, sum); // [49, 20.7, 10.8, 7, 5.5, 7] 100
console.log(roundedNumbers, roundedSum); // [49, 21, 11, 7, 5, 7] 100
答案 2 :(得分:0)
在计算百分比时会固有地引入“错误”,并通过向下/向上舍入来降低精度。当你总结那些四舍五入的数字时,你将获得不同的总数。
我认为你所建议的是捏造四舍五入的数字,以便他们总结一个“原始”总数,你可以这样编程,例如:
1. sum the percentages = 100.1
2. calculate error: 100 - 100.1 = -0.1
3. select any one of the percentages and negate the error.
4. sum them again, will equal 100.
当然这意味着修改后的百分比不再反映原始数据(即使是通过小数量),也没有正确的方法来做到这一点。
现实是,原始数学没有'问题',只有语义。我倾向于指定数字是四舍五入的,或者显示完整的小数。