我希望提供一种不依赖于任何机器/编译器/工具链特殊工作方式的解决方案。
我想到的最好的方法是:
(a / 2) + (b / 2) + !!(a % 2) * !!(b %2)
是否有更准确、更快速、更简单的方法呢?如果我们事先知道其中一个数比另一个数大怎么办?
谢谢。
D
(a / 2) + (b / 2) + !!(a % 2) * !!(b %2)
是否有更准确、更快速、更简单的方法呢?接受答案后(4年)
我希望函数int average_int(int a, int b)
能够做到以下两点:
1. 对于所有的a
和b
组合,能够在整个[INT_MIN..INT_MAX]
范围内工作。
2. 与使用更宽的数学方法得出的(a+b)/2
结果相同。
当int2x存在时,@Santiago Alessandri的方法效果很好。
int avgSS(int a, int b) {
return (int) ( ((int2x) a + b) / 2);
}
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|解决方案,但没有%
当没有溢出发生时,所有下面的解决方案都可以在1个单位内与(a+b)/2
匹配,但我希望找到一个能够匹配所有int
的解决方案。
@Santiago Alessandri的解决方案只适用于int
的范围小于long long
的范围的情况 - 这通常是成立的。
((long long)a + (long long)b) / 2
@AProgrammer被接受的答案,有大约四分之一的概率无法匹配(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..,解决方案有约四分之一的概率无法匹配(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);
}
(a^b)<=0
,你可以放心地使用 (a+b)/2
而不用担心溢出。(a-(a|b)+b)/2+(a|b)/2
。-(a|b)
的绝对值至少与 a
和 b
中的较大者相等,并且符号相反,因此可以避免溢出。a|b
的相对大小取决于表示方法...a+(b-a)/2
。请注意,这会偏向于 a
。您可以将其翻转并偏向于 b
。而我的解决方案则偏向于零,如果我没记错的话。(a&b)+(a^b)/2
。在二进制补码中,它可以适用于任何符号,但我认为如果 a
和 b
具有相同的符号,则它也适用于反码或原码。您可以检查一下吗?a
和b
为负数时,在二进制补码中,a|b
的幅度较小而不是较大。因此,舍入方向可能取决于有符号表示。 - R.. GitHub STOP HELPING ICE编辑:@chux - Reinstate Monica修复了版本问题:
if ((a < 0) == (b < 0)) { // a,b same sign
return a/2 + b/2 + (a%2 + b%2)/2;
} else {
return (a+b)/2;
}
原始回答(如果未被接受,我会将其删除)。
a/2 + b/2 + (a%2 + b%2)/2
看起来这是最简单的一个,不对实现特性做任何假设(它依赖于C99,将/的结果指定为“向0截断”,而对于C90,则取决于实现)。
它的优点是没有测试(因此没有昂贵的跳转),所有除法/余数都是2,因此编译器可以使用位操作技术。
-Os
时,它对所有内容都使用idiv
,而使用-O2
或更高版本时,则切换到位运算,但使用了大量的位运算...如果您有更大的类型可用,我认为您应该在更大的类型中进行平均值计算... - R.. GitHub STOP HELPING ICE对于无符号整数,平均值是 (x+y)/2 的下取整。但对于有符号整数,这个公式不适用。对于两个和为奇数负数的整数,这个公式会失败,因为它们的下取整比平均值少1。
您可以在《Hacker's Delight》第2.5节中了解更多。
计算两个有符号整数的平均值而不溢出的代码如下:
int t = (a & b) + ((a ^ b) >> 1)
unsigned t_u = (unsigned)t
int avg = t + ( (t_u >> 31 ) & (a ^ b) )
我已经使用Z3 SMT求解器检查了它的正确性。
以下是一些可能有用的观察:
“最准确”的并不一定在整数中是唯一的。例如,对于1和4,2和3同样是“最准确”的答案。从数学上讲(而非C整数):
(a+b)/2 = a+(b-a)/2 = b+(a-b)/2
让我们尝试分解这个问题:
您到底想要优化什么?不同的处理器架构可能有不同的最佳解决方案。例如,在您的代码中,将乘法替换为 AND 可能会提高性能。此外,在二进制补码架构中,您可以简单地使用 (a & b & 1)。
我只是随便丢一些代码,没有太仔细看,但也许有人可以使用并改进:
int sgeq = ((a<0)==(b<0));
int avg = ((!sgeq)*(a+b)+sgeq*(b-a))/2 + sgeq*a
a^b
是否小于零来测试a
和b
是否具有相反的符号(因为如果两个数值都是负数,那么它们的符号位将在位表示中互相抵消)。 - R.. GitHub STOP HELPING ICE((long long)a + (long long)b) / 2
如果你想要小数部分,将其存储为double类型。
需要注意的是,结果将适合于32位整数。
如果你正在使用最高级别的整数,则可以使用:
((double)a + (double)b) / 2
a
和 b
已经是最高级别的有符号整数类型,该怎么办? - R.. GitHub STOP HELPING ICE>>
的结果是由实现定义的。换句话说,取决于编译器和平台,它可能实际上会或不会进行符号扩展,并且甚至可能拒绝编译或崩溃程序(只要实现定义了此行为)。 - Anomie这个答案适用于任何数量的整数:
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;
}
这个测试期望平均值为5.0
(a / 2) + (b / 2) + ((a % 2) + (b % 2)) / 2
它的好处是它完全补充了模运算的定义,因此在数学意义上是完美准确的...但在C语言中是否准确呢? - McCormicka/b
是可表示的,则表达式(a/b)*b + a%b
应等于a
。”,所以它们至少是一致的,这意味着如果你使用基于数学的方法而没有任何!!技巧,它应该是正确的(但如果涉及到负数,它可能会向上或向下舍入)。 - Random832