找到两个值的平均值的正确方法是什么?

bod*_*ydo 17 c integer-overflow undefined-behavior

我最近了解到整数溢出是C中未定义的行为(侧面问题 - 它是否也是C++中的UB?)

经常在C语言编程,你需要找到两个值的平均值ab.但是,这样做(a+b)/2会导致溢出和未定义的行为.

所以我的问题是-什么是找到两个值的平均值的正确方法a,并b用C?

Dou*_*rie 15

Secure Coding的帮助下

if (((si_b > 0) && (si_a > (INT_MAX - si_b))) ||
    ((si_b < 0) && (si_a < (INT_MIN - si_b))))
{
  /* will overflow, so use difference method */
  return si_b + (si_a - si_b) / 2;
} 
else
{
 /* the addition will not overflow */
  return (si_a + si_b) / 2;
}
Run Code Online (Sandbox Code Playgroud)

附录

感谢@chux指出舍入问题.这是一个经过正确舍入测试的版本......

int avgnoov (int si_a, int si_b)
{
    if ((si_b > 0) && (si_a > (INT_MAX - si_b)))
    {
      /* will overflow, so use difference method */
      /* both si_a and si_b > 0; 
          we want difference also > 0
          so rounding works correctly */
      if (si_a >= si_b)
        return si_b + (si_a - si_b) / 2;
      else
        return si_a + (si_b - si_a) / 2;
    } 
    else if ((si_b < 0) && (si_a < (INT_MIN - si_b)))
    {
      /* will overflow, so use difference method */
      /* both si_a and si_b < 0; 
          we want difference also < 0
          so rounding works correctly */
      if (si_a <= si_b)
        return si_b + (si_a - si_b) / 2;
      else
        return si_a + (si_b - si_a) / 2;
    }
    else
    {
     /* the addition will not overflow */
      return (si_a + si_b) / 2;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • +1,这是一个很好的解决方案,没有整数溢出,它还具有不使用`%`运算符的优点.`%`是c99中的余数运算符,但在c90中它是余数或模运算符(余数和模数相对于负值不同). (3认同)

use*_*841 10

(a >> 1) + (b >> 1) + (((a & 1) + (b & 1)) >> 1)
Run Code Online (Sandbox Code Playgroud)

c int数学中的shift语句(x >> i)等于2除以i的幂.所以声明(a >> 1)+(b >> 1)与a/2 + b/2相同.但是,也需要添加数字截断部分的平均值.该值可以通过掩蔽(a&1),添加((a&1)+(b&1))和除(((a&1)+(b&1))>> 1)来获得.平均值变为(a >> 1)+(b >> 1)+(((a&1)+(b&1))>> 1)

注意:使用>>和&而不是/和%作为除法和余数运算符的原因之一是效率.

  • “注意:使用 &gt;&gt; 和 &amp; 而不是 / 和 % 作为除法和余数运算符的原因之一是效率。” 你的编译器比那更聪明。请参阅[在 C 中使用移位运算符的乘法和除法实际上更快吗?](http://stackoverflow.com/questions/6357038/is-multiplication-and-division-using-shift-operators-in-c-actually-faster)因此,为了代码的可读性,请在您想要时使用 / 和 % 。 (3认同)
  • 右移负值具有实现定义的结果。你最好使用除法,因为这肯定会产生负结果或 0。我也不确定“a &amp; 1”是否保证等于“abs(a % 2)”。 (3认同)
  • 使用`a = -2`和`b = 1`,该解决方案给出`-1`作为结果.但在C(c99/c11)中,`( - 2 + 1)/ 2`为'0`.这个解决方案与Vlad的解决方案有同样的问题; c99/c11除法截断为"0"而不是"-inf". (2认同)

Vla*_*cow 4

一个简单的方法如下

int c = a / 2 + ( b + a % 2 ) / 2;
Run Code Online (Sandbox Code Playgroud)

例如a和b可以表示为

a = 2 * n + r1;
b = 2 * m + r2;
Run Code Online (Sandbox Code Playgroud)

然后

( a + b ) / 2 => ( 2 * n + r1 + 2 * m + r2 ) / 2 => 2 * n / 2 + ( b + r1 ) / 2
Run Code Online (Sandbox Code Playgroud)

最后一个表达式给你

=> a / 2 + ( b + a % 2 ) / 2
Run Code Online (Sandbox Code Playgroud)

更正确的表达方式如下

int c = a / 2 + b / 2 + ( a % 2 + b % 2 ) / 2;
Run Code Online (Sandbox Code Playgroud)

例如,如果我们有

int a = INT_MAX;
int b = INT_MAX;
Run Code Online (Sandbox Code Playgroud)

然后 c 计算为

int c = a / 2 + b / 2 + ( a % 2 + b % 2 ) / 2;
Run Code Online (Sandbox Code Playgroud)

会给c == INT_MAX

编辑:计算机运算符的效果和数学运算符的效果之间发现了有趣的差异。例如根据数学-1可以表示为

-1 = -1 * 2 + 1 
Run Code Online (Sandbox Code Playgroud)

即根据公式

a = 2 * n + r1
Run Code Online (Sandbox Code Playgroud)

2 * n应为小于或等于 tp 的整数a

所以小于-1的数是-2。:)

我认为我所展示的一般公式是可行的,要求对于奇数负数,将考虑小于奇数负数的偶数负数。

看来正确的公式如下

int c = ( a < 0 ? a & ~1 : a ) / 2 + 
        ( b < 0 ? b & ~1 : b ) / 2 + 
        ( ( a & 1 ) + ( b & 1 ) ) / 2;
Run Code Online (Sandbox Code Playgroud)

需要注意的是,从数学角度来看,-1和的平均值-2应等于-2,并且该公式给出了正确的结果。:)

  • 那么,如果“a == b == INT_MAX”怎么办?`INT_MAX % 2` 通常不为零。“简单的方法”仍然会产生溢出。 (2认同)
  • 对于“a = 2”和“b = -1”,您的方法给出“1”,但“(2 - 1) / 2”是“0”。 (2认同)