在我的平台上,unsigned long long
是64位(8字节)。假设我有两个这样的变量:
unsigned long long partialSize;
unsigned long long totalSize;
//somehow determine partialSize and totalSize
如何可靠地确定有多少百分比(四舍五入到附近的整数)partialSize
为totalSize
? (如果可能的话,如果我不必假设前者小于后者,那将是很好的,但如果我真的必须做出这个假设,那很好。但我们当然可以假设两者都是非阴性。)
例如,以下代码是完全防弹的吗?我担心它会包含某种舍入,转换或转换错误,这些错误可能导致比率在某些条件下失控。
unsigned long long ratioPercentage
= (unsigned long long)( ((double)partialSize)/((double)totalSize) * 100.0 );
答案 0 :(得分:5)
它不是完全防弹的。 double
mantissae只有53位(52 + 1隐式),因此如果您的数字大于2^53
,转换为double
通常会引入舍入错误。但是,与数字本身相关的舍入误差非常小,因此导致整数值的百分比计算将导致比转换更多的不准确性。
一个可能更严重的问题是,这将始终向下,例如,对于totalSize = 1000
和partialSize = 99
,它将返回9
而不是更接近的值10
。通过在转换为0.5
之前添加unsigned long long
,您可以获得更好的舍入效果。
只使用整数运算可以得到精确的结果(如果最终结果没有溢出),如果partialSize
不是太大,那就相当容易了:
if (partialSize <= ULLONG_MAX / 100) {
unsigned long long a = partialSize * 100ULL;
unsigned long long q = a / totalSize, r = a % totalSize;
if (r == 0) return q;
unsigned long long b = totalSize / r;
switch(b) {
case 1: return q+1;
case 2: return totalSize % r ? q : q+1; // round half up
default: return q;
}
}
如果你想要地板,天花板或半圆形甚至是半圆形的话,可以轻松修改。
如果totalSize >= 100
和ULLONG_MAX / 100 >= partialSize % totalSize
,
unsigned long long q0 = partialSize / totalSize;
unsigned long long r = partialSize % totalSize;
return 100*q0 + theAbove(r);
在其他情况下,它变得更加繁琐,我并不热衷于这样做,但如果你需要,我可以被说服。
答案 1 :(得分:3)
请注意,您的公式不正确,因为它省略了圆到最近所需的+0.5
。
所以我将继续假设这个更正的公式:
(unsigned long long)( ((double)partialSize)/((double)totalSize) * 100.0 + 0.5);
正如我在评论中提到的那样,直截了当的方法虽然简单,但并不能保证正确舍入结果。所以你的直觉是正确的,因为它不是防弹的。
在绝大多数情况下,它仍然是正确的,但会有一小部分边缘情况,它们将无法正确舍入。这些问题是否取决于你。但是直接的方法通常足以满足大多数目的。
为什么会失败:
有4个级别的舍入。 (从我在评论中提到的2改正)
每当你有多个舍入源时,你会遇到通常的浮点错误源。
反例:
虽然很少见,但我会列举一些例子,直截了当的公式会给出错误的舍入结果:
850536266682995018 / 3335436339933313800 // Correct: 25% Formula: 26%
3552239702028979196 / 10006309019799941400 // Correct: 35% Formula: 36%
1680850982666015624 / 2384185791015625000 // Correct: 70% Formula: 71%
<强>解决方案:强>
除了使用arbitrary precision arithmetic之外,我想不出一个干净的100%防弹解决方案。
但最后,你真的需要它总是圆润吗?
编辑:
对于较小的数字,这是一个非常简单的解决方案,可以在0.5
:
return (x * 100 + y/2) / y;
只要x * 100 + y/2
没有溢出,这将有效。
@Daniel Fischer的答案对其他舍入行为有更全面的解决方案。虽然修改这个并不是太难以实现圆整甚至。
答案 2 :(得分:2)
对于某些值,单个公式将始终溢出,崩溃或出现大错误 这种组合几乎总是很有效:
if (totalSize > 1000000) {
pct = partialSize / (totalSize / 100);
} else {
pct = (partialSize*100) / totalSize;
}
只有当partialSize大于MAX_U_LONG_LONG / 100且totalSize低于1000000时才会失败。在这种情况下,正确的百分比远大于100%,所以它不是很有趣。