从两个(64位)整数获得可靠的整数百分比

时间:2011-12-31 18:54:06

标签: c 64-bit int computer-science

在我的平台上,unsigned long long是64位(8字节)。假设我有两个这样的变量:

unsigned long long partialSize;
unsigned long long totalSize;
//somehow determine partialSize and totalSize

如何可靠地确定有多少百分比(四舍五入到附近的整数)partialSizetotalSize? (如果可能的话,如果我不必假设前者小于后者,那将是很好的,但如果我真的必须做出这个假设,那很好。但我们当然可以假设两者都是非阴性。)

例如,以下代码是完全防弹的吗?我担心它会包含某种舍入,转换或转换错误,这些错误可能导致比率在某些条件下失控。

unsigned long long ratioPercentage
    = (unsigned long long)( ((double)partialSize)/((double)totalSize) * 100.0 );

3 个答案:

答案 0 :(得分:5)

它不是完全防弹的。 double mantissae只有53位(52 + 1隐式),因此如果您的数字大于2^53,转换为double通常会引入舍入错误。但是,与数字本身相关的舍入误差非常小,因此导致整数值的百分比计算将导致比转换更多的不准确性。

一个可能更严重的问题是,这将始终向下,例如,对于totalSize = 1000partialSize = 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 >= 100ULLONG_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改正)

  1. 演员表64位 - &gt; 53位
  2. 分部
  3. 乘以100。
  4. 最终演员。
  5. 每当你有多个舍入源时,你会遇到通常的浮点错误源。

    反例:

    虽然很少见,但我会列举一些例子,直截了当的公式会给出错误的舍入结果:

     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%,所以它不是很有趣。