取C中两个有符号数的平均值

时间:2011-04-18 00:40:04

标签: c optimization average numerics

让我们说我们有x和y,两者都是C中的有符号整数,我们如何才能找到两者之间最准确的平均值?

我更喜欢不利用任何机器/编译器/工具链特定工作的解决方案。

我提出的最好的是:(a / 2) + (b / 2) + !!(a % 2) * !!(b %2)是否有更准确的解决方案?快点?简单?

如果我们知道一个是否比另一个先验大,该怎么办?

感谢。

d


编者注:请注意,当输入值接近C int类型的最大绝对边界时,OP需要不受整数溢出影响的答案。这在原始问题中没有说明,但在给出答案时很重要。

7 个答案:

答案 0 :(得分:6)

接受回答后(4年)

我希望函数int average_int(int a, int b)能够:
  1.针对[INT_MIN..INT_MAX]a的组合,在b的整个范围内工作   2.与(a+b)/2具有相同的结果,就像使用更宽的数学一样。

int2x存在时,@Santiago Alessandri方法效果很好。

int avgSS(int a, int b) {
  return (int) ( ((int2x) a + b) / 2);
}

其他@AProgrammer的变体:

int avgC(int a, int b) {
  if ((a < 0) == (b < 0)) {  // a,b same sign
    return a/2 + b/2 + (a%2 + b%2)/2;
  }
  return (a+b)/2;
}

solution包含更多测试但没有%

所有以下解决方案&#34;工作&#34;当溢出没有发生时,在(a+b)/2的1之内,但我希望找到一个与所有(a+b)/2匹配int的。{/ p>

@Santiago Alessandri只要int的范围小于long long的范围(通常),解决方案就会有效。

((long long)a + (long long)b) / 2
接受答案的

@AProgrammer在1/4的时间内无法匹配(a+b)/2。示例输入,例如a == 1, b == -2

a/2 + b/2 + (a%2 + b%2)/2

@Guy Sirton,解决方案大约1/8的时间无法匹配(a+b)/2。示例输入,例如a == 1, b == 0

int sgeq = ((a<0)==(b<0));
int avg = ((!sgeq)*(a+b)+sgeq*(b-a))/2 + sgeq*a;

@R..,解决方案在1/4的时间内无法匹配(a+b)/2。示例输入,例如a == 1, b == 1

return (a-(a|b)+b)/2+(a|b)/2;

@MatthewD,现在已删除的解决方案在约5/6的时间内无法匹配(a+b)/2。示例输入,例如a == 1, b == -2

unsigned diff;
signed mean;
if (a > b) {
    diff = a - b;
    mean = b + (diff >> 1);
} else {
    diff = b - a;
    mean = a + (diff >> 1);
}

答案 1 :(得分:3)

如果(a^b)<=0您可以使用(a+b)/2而不必担心溢出。

否则,请尝试(a-(a|b)+b)/2+(a|b)/2-(a|b)的幅度至少与ab的幅度相同,并且符号相反,因此可以避免溢出。

我从头顶快速做到这一点,所以可能会有一些愚蠢的错误。请注意,此处有没有机器特定的黑客所有行为完全由C标准确定,并且它需要有符号值的二进制补码,一补码或符号幅度表示,并指定按位运算符在逐位表示中工作。 不,a|b的相对大小取决于表示......

修改:如果符号相同,您也可以使用a+(b-a)/2。请注意,这会偏向a。您可以撤消它并偏向b。另一方面,如果我没有弄错的话,我上面的解决方案会偏向零。

另一种尝试:一种标准方法是(a&b)+(a^b)/2。在两个补码中,无论符号如何,它都有效,但我认为如果ab具有相同的符号,它也可以在补码或符号量级中起作用。小心检查一下?

答案 2 :(得分:3)

a/2 + b/2 + (a%2 + b%2)/2

似乎最简单的一个适用于实现特性的假设(它依赖于C99,它指定/的结果为“截断为0”,而它是依赖于C90的实现)。

它的优点是没有测试(因此没有昂贵的跳转),并且所有的分区/余数都是2,所以编译器可以使用位错误技术。

答案 3 :(得分:1)

只是一些可能有帮助的观察结果:

“最准确”并不一定是整数唯一的。例如。对于1和4,2和3是同样“最准确”的答案。数学上(不是C整数):

(a+b)/2 = a+(b-a)/2 = b+(a-b)/2

让我们试着打破这个:

  • 如果符号(a)!=符号(b)则a + b将不会溢出。这种情况可以通过比较二进制补码表示中的最高有效位来确定。
  • 如果符号(a)==符号(b),那么如果a大于b,则(a-b)不会溢出。否则(b-a)不会溢出。编辑:实际上两者都不会溢出。

您准备优化什么?不同的处理器架构可能有不同的最佳解决方案例如,在您的代码中,用AND替换乘法可以提高性能。同样在二进制补码架构中,您可以简单地(a&amp; b&amp; 1)。

我只是要抛出一些代码,不要看得太快,但也许有人可以使用和改进:

int sgeq = ((a<0)==(b<0));
int avg = ((!sgeq)*(a+b)+sgeq*(b-a))/2 + sgeq*a

答案 4 :(得分:1)

对于无符号整数,平均值是(x + y)/ 2的最低值。但是签名整数也是如此。对于总和为奇数的整数,该公式失败,因为它的最低值比其平均值小1。

您可以在第2.5节

中的Hacker's Delight阅读更多内容

计算没有溢出的2个有符号整数的平均值的代码是

int t = (a & b) + ((a ^ b) >> 1)
unsigned t_u = (unsigned)t
int avg = t + ( (t_u >> 31 ) & (a ^ b) )

我已使用Z3 SMT solver

检查了它的正确性

答案 5 :(得分:-1)

我会这样做,将两者转换为long long(64位有符号整数)加起来,这不会溢出然后将结果除以2:

((long long)a + (long long)b) / 2

如果您想要小数部分,请将其存储为double。

重要的是要注意结果将适合32位整数。

如果您使用的是最高等级的整数,则可以使用:

((double)a + (double)b) / 2

答案 6 :(得分:-2)

这个答案适合任意数量的整数:

    int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    decimal avg = 0;
    for (int i = 0; i < array.Length; i++){
        avg = (array[i] - avg) / (i+1) + avg;
    }

此测试需要avg == 5.0