2011-12-31 72 views
4

在我的平台上,unsigned long long是64位(8字节)。假设我有两个这样的变量:从两个(64位)整数中获取可靠的整数百分比

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); 
+1

你是正确的,在非常极端的情况下,直截了当的方法可以“走出去”。这是因为有两个级别的舍入以及64位 - > 53位的精度损失。 – Mysticial 2011-12-31 18:57:41

+1

@Mysticial。结果只需要最多7位的精度。我认为64→53根本不是问题。 – kennytm 2011-12-31 19:05:58

+0

您可能感兴趣的两个链接:1)http://kanooth.com/numbers/ 2)http://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic – paulsm4 2011-12-31 19:08:06

回答

4

这不完全是防弹的。 double mantissae只有53位(52 + 1隐含),所以如果你的数字大于2^53,转换为double一般会引入舍入误差。但是,舍入误差与数字本身相比非常小,因此导致整数值的百分比计算会导致比转换更不准确。

可能更严重的问题是,它总是向下,例如,对于totalSize = 1000partialSize = 99,它将返回9而不是更接近的值10。在投射到unsigned long long之前,您可以通过添加0.5来获得更好的四舍五入效果。

你可以得到只使用整数运算(如果最终结果不会溢出),这是相当容易,如果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); 

它得到在其他情况下更繁琐,我并不热衷于这样做,但如果你需要它,我可以被说服。

+0

+1。虽然我没有测试过,但它看起来可行并且解决了不同的舍入行为。 – Mysticial 2011-12-31 22:56:13

+0

但是,对于没有溢出风险的半数和数字,你更简单快捷。 – 2011-12-31 22:59:35

2

单个公式总是会溢出,崩溃或给某些值带来大的错误。
这种组合效果很好,几乎总是:

if (totalSize > 1000000) { 
    pct = partialSize/(totalSize/100); 
} else { 
    pct = (partialSize*100)/totalSize; 
} 

时partialSize比MAX_U_LONG_LONG/100大它只会失败,总计TOTALSIZE低于100万。在这种情况下,正确的比例超过100%大得多,所以这不是很有趣。

+0

伟大的建议。但是你错过了这种偏见:这将会缩减为零而不是四舍五入。 – 2011-12-31 22:20:27

+0

确实,这段代码并没有完全正确地截断。更糟糕的是,partialSize /(totalSize/100)逻辑失去了一些精确性,所以它可能会漏掉一些。但问题是“四舍五入到一个整数”,而不是最近的。 – ugoren 2012-01-01 08:57:25

3

请注意,您的公式不正确,因为它省略了需要轮到最近的+0.5

所以,我会进行下去假设这修正公式:

(unsigned long long)(((double)partialSize)/((double)totalSize) * 100.0 + 0.5); 

正如我在评论中提到过,直进的方法虽然简单,但不能保证正确的舍入结果。所以你的直觉是正确的,因为它不是防弹的。

在绝大多数情况下,它仍然是正确的,但是会出现一小组边界情况,在这些情况下它不会被正确舍入。这些问题是否由你决定。但是对于大多数目的来说,直接的方法通常是足够的。

它为什么可能失败:

有4个级别的舍入。 (从我在评论中提到的2校正)

  1. 石膏64位 - 由100
  2. 最终铸造> 53位
  3. 划分
  4. 乘法。

无论何时您有多个舍入源,都会遇到浮点错误的常见来源。

反例:

虽然罕见,但我会列出几个例子,其中直进式会给出不正确的舍入结果:

850536266682995018/3335436339933313800 // Correct: 25% Formula: 26% 
3552239702028979196/10006309019799941400 // Correct: 35% Formula: 36% 
1680850982666015624/2384185791015625000 // Correct: 70% Formula: 71% 

解决方案:

我想不出一个干净的100%防弹解决方案,而不是使用arbitrary precision arithmetic

但最后,你是否真的需要它永远是完美的圆形?


编辑:

对于更小的数字,这里的一对0.5,除此之外,该方案非常简单:

return (x * 100 + y/2)/y; 

这只要x * 100 + y/2不会溢出工作。

@Daniel Fischer答案为其他舍入行为提供了更全面的解决方案。尽管修改这个以达到全面的平衡并不难。

+0

如果我知道我的所有数字都小于2^53,该怎么办?我会得到准确的结果吗? (我正在处理文件大小,在我的情况下,文件不会那么大) – pf85 2011-12-31 21:17:27

+1

如果你的数字小于2^53,那就消除了第1点。我相当确信你仍然可以找到一些浮点四舍五入将结果推到0.5(或整数)边界的情况,但是对于'xx.5±epsilon'百分比的错误舍入结果是一个真正的问题?如果是这样,我添加到我的答案的整数方法将保证正确的四舍五入结果,因为溢出不是一个问题在这里。 – 2011-12-31 22:13:50

+0

+0.5看起来像一个黑客。那么这个呢? - >'static unsigned int d(unsigned long long p,unsigned long long t){return round((double)p * 100/t); }' – 2011-12-31 22:48:21