我怎样才能安全地平均两个无符号整数在C + +?

单独使用整数math,我想“安全地”平均两个C ++的无符号整数。

我的意思是“安全地”避免溢出(以及其他任何可以想到的)。

例如,平均值2005000很容易:

unsigned int a = 200; unsigned int b = 5000; unsigned int average = (a + b) / 2; // Equals: 2600 as intended 

但在42949672955000的情况下,则:

 unsigned int a = 4294967295; unsigned int b = 5000; unsigned int average = (a + b) / 2; // Equals: 2499 instead of 2147486147 

我想到的最好的是:

 unsigned int a = 4294967295; unsigned int b = 5000; unsigned int average = (a / 2) + (b / 2); // Equals: 2147486147 as expected 

有更好的方法吗?

你最后的做法似乎很有希望 您可以通过手动考虑a和b的最低位来改善:

 unsigned int average = (a / 2) + (b / 2) + (a & b & 1); 

如果a和b都是奇数,则给出正确的结果。

 unsigned int average = low + ((high - low) / 2); 

编辑

以下是相关文章: http : //googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html

你的方法是不正确的,如果两个数字是奇数例如5和7,平均值是6,但你的方法#3返回5。

尝试这个:

 average = (a>>1) + (b>>1) + (a & b & 1) 

只有math运算符:

 average = a/2 + b/2 + (a%2) * (b%2) 

如果你不介意一个x86内联汇编(GNU C语法),你可以利用超级猫的build议在一个add之后使用循环进位,将完整的33位结果的高32位放入一个寄存器。

当然,你通常应该介意使用inline-asm,因为它违背了一些优化( https://gcc.gnu.org/wiki/DontUseInlineAsm )。 但是我们无论如何去了:

 // works for 64-bit long as well on x86-64, and doesn't depend on calling convention unsigned average(unsigned x, unsigned y) { unsigned result; asm("add %[x], %[res]\n\t" "rcr %[res]" : [res] "=r" (result) // output : [y] "%0"(y), // input: in the same reg as results output. Commutative with next operand [x] "rme"(x) // input: reg, mem, or immediate : // no clobbers. ("cc" is implicit on x86) ); return result; } 

告诉编译器参数是可交换的%修饰符实际上并没有帮助在我尝试的情况下使用更好的asm,使用y作为常量或pointer-deref(内存操作数)来调用函数。 可能对输出操作数使用匹配约束会导致错误,因为不能将其与读写操作数一起使用。

正如你可以在Godbolt编译器资源pipe理器中看到的那样 ,它编译正确,我们用相同的inline asm将操作数改为unsigned long的版本也是如此。 然而,clang3.9弄乱了它,并决定使用"m"选项作为"rme"约束,所以它存储到内存并使用内存操作数。


RCR-by-one不是太慢,但在Skylake上仍然是3 uops,有2个周期的延迟。 在AMD CPU上,RCR具有单周期延迟。 (来源: Agner Fog的指令表 ,另请参阅x86标签wiki以获得x86性能链接)。 它比@ sellibitze的版本更好,但比@ Sheldon的顺序依赖版本更糟糕。 (见Godbolt上的代码)

但是请记住,内联asm不会像常量传播那样优化,所以在这种情况下任何纯C ++版本都会更好。

而正确的答案是…

 (A&B)+((A^B)>>1) 

你有什么好的,有一点小细节,它会声称3和3的平均值是2.我猜你不希望那样; 幸运的是,有一个简单的解决方法:

 unsigned int average = a/2 + b/2 + (a & b & 1); 

这在两个部门都被截断的情况下,只会使平均数值上升。

如果代码是embedded式微代码,并且速度很关键,汇编语言可能会有所帮助。 在许多微控制器上,加法的结果自然会进入进位标志,并且存在将其转换回寄存器的指令。 在ARM上,平均操作(寄存器中的源和目标)可以用两条指令完成; 任何C语言相当于可能会产生至less5,并可能比这更多一点点。

顺便说一句,在字宽较短的机器上,差异可能更大。 在一个8位PIC-18系列上,平均两个32位数字将需要十二个指令。 做转变,增加和改正,每个转变需要5条指令,加8个,修正8个,所以26(不是2.5倍的差异,但绝对值可能更重要)。

使用一个64位的unsigned int作为总和的占位符,除以2后再转换为int。可疑是否“更好”,但是你一定要尽量避免溢出问题。

  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这个testing

(((a&b << 1) + (a^b)) >> 1)也是一个不错的方法。

礼貌: http ://www.ragestorm.net/blogs/?p= 29