我最近了解到整数溢出是C中未定义的行为(侧面问题 - 它在C ++中也是UB吗?)
通常在C编程中,您需要找到两个值a
和b
的平均值。但是,执行(a+b)/2
会导致溢出和未定义的行为。
所以我的问题是 - 在C中找到两个值a
和b
的平均值的正确方法是什么?
答案 0 :(得分:15)
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;
}
附录
感谢@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;
}
}
答案 1 :(得分:10)
(a >> 1) + (b >> 1) + (((a & 1) + (b & 1)) >> 1)
c int数学中的shift语句(x>&gt; i)等于2除以i的幂。 因此声明(a>&gt; 1)+(b&gt;&gt; 1)与a / 2 + b / 2相同。但是,也需要添加数字截断部分的平均值。该值可以通过掩蔽(a&amp; 1),加((a&amp; 1)+(b&amp; 1))和除(((a&amp; 1)+(b&amp; 1))&gt;得到。 &gt; 1)。平均值变为(a>&gt; 1)+(b>&gt; 1)+(((a&amp; 1)+(b&amp; 1))&gt;&gt; 1)
注意:使用&gt;&gt;的原因和&amp;而不是/和%作为除法和余数运算符是效率之一。
答案 2 :(得分:4)
一个简单的方法是以下
int c = a / 2 + ( b + a % 2 ) / 2;
例如a和b可以表示为
a = 2 * n + r1;
b = 2 * m + r2;
然后
( a + b ) / 2 => ( 2 * n + r1 + 2 * m + r2 ) / 2 => 2 * n / 2 + ( b + r1 ) / 2
最后一个表达式给你
=> a / 2 + ( b + a % 2 ) / 2
更正确的表达式如下
int c = a / 2 + b / 2 + ( a % 2 + b % 2 ) / 2;
例如,如果我们有
int a = INT_MAX;
int b = INT_MAX;
然后c计算为
int c = a / 2 + b / 2 + ( a % 2 + b % 2 ) / 2;
将提供c == INT_MAX
-1 = -1 * 2 + 1
根据公式
a = 2 * n + r1
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;
重要的是要注意,从数学的角度来看,-1
和-2
的平均值应等于-2
,并且公式给出正确的结果。:)
答案 3 :(得分:3)
如果您担心溢出,可以将值转换为更大的类型以执行数学运算,然后执行边界检查。
答案 4 :(得分:2)
这是Calculating the average of two integer numbers rounded towards zero in a single instruction cycle:
(a >> 1) + (b >> 1) + (a & b & 0x1)
你必须考虑:
它的实现定义了右移一个负整数是将零或一个移位到高位。许多CPU通常有两个不同的指令:算术右移(保留符号位)和逻辑右移(不保留符号位)。允许编译器选择(大多数编译器选择算术移位指令)。
ISO / IEC 9899:2011§6.5.7按位移位运算符
¶5E1>&gt;的结果E2是E1右移E2位位置。 [CUT]如果E1具有有符号类型和负值,则结果值是实现定义的。
将表达式更改为:
a / 2 + b / 2 + (a & b & 0x1)
不是解决方案,因为逻辑右移相当于除以2 only for positive or unsigned numbers的幂。
(a & b & 0x1)
也没有明确定义。当a
和b
都是奇数时,此字词应为非零。但是它以一个补码表示失败,ISO C,第6.2.6.2/2节,表明an implementation can choose one of three different representations for integral data types:
(通常两者的补充远远超过其他补充)。
答案 5 :(得分:1)
在整个范围int
内平均两个[INT_MIN...INT_MAX]
的最简单(通常是最快)的方法是求助于更宽的整数类型。 (由@user3100381建议。)让我们称之为int2x
。
int average_int(int a, int b) {
return ((int2x) a + b)/2;
}
当然这要求存在更广泛的类型 - 所以让我们看一下不需要更广泛类型的解决方案。
挑战:
问:当一个int
是奇数而另一个是偶数时,应该采用哪种方式进行舍入?
答:遵循上面的average_int()
并向0舍入。(截断)。
问:代码可以使用%
吗?
答:使用前C99代码时,a % 2
的结果会在a < 0
时产生不同的结果。因此,我们不要使用%
。
问:int
是否需要关于正数和负数的对称范围?
答:由于C99,负数的数量与正数的数量相同(或者更多)。让我们尽量不要求这个。
解决方案:
执行测试以确定是否可能发生溢出。如果没有,请简单使用(a + b) / 2
。否则,将差值的一半(与答案相同)添加到较小的值。
以下给出与average_int()
相同的答案,而不采用更宽的整数类型。它protects against int
overflow并且不要求INT_MIN + INT_MAX
为0或-1。它不依赖于编码是2的补码,1的补码或符号幅度。
int avgC2(int a, int b) {
if (a >= 0) {
if (b > (INT_MAX - a)) {
// (a+b) > INT_MAX
if (a >= b) {
return (a - b) / 2 + b;
} else {
return (b - a) / 2 + a;
}
}
} else {
if (b < (INT_MIN - a)) {
// (a+b) < INT_MIN
if (a <= b) {
return (a - b) / 2 + b;
} else {
return (b - a) / 2 + a;
}
}
}
return (a + b) / 2;
}
最多使用if()
对int
发生3 @app.task(rate_limit="60/s")
def api_call(user):
do_the_api_call()
for i in range(0,100):
api_call("antoine")
api_call("oscar")
次。
答案 6 :(得分:0)
如果您只需处理无符号整数类型(并且可以考虑二进制),则可以将您的添加内容分解为digit
和carry
。我们可以将a+b
(无限精度)写为(a^b) + ((a&b)<<1))
,因此(a+b)/2
只是((a^b)>>1) + (a&b)
。最后一个表达式适用于a
和b
的常见类型,因此您可以在代码中使用它:
unsigned semisum(unsigned a, unsigned b)
{
return ((a^b)>>1) + (a&b);
}
答案 7 :(得分:-1)
如果只有2个元素可以避免溢出,那么最简单的答案是:
(a/2) + (b/2) = average
对于更多元素,您可以使用:
(a/x) + (b/x) + (c/x) + (d/x) ..... = average //x = amount of elements
从数学的角度来看,如果原始值之前没有这样做,永远不会到达溢出,就像你一样不要把它们全部加在一起,而是在将它们加在一起之前将它们分开。因此,在计算过程中执行的任何操作的无结果,包括结果,将比最大的初始元素>更强(到0的任一侧) (假设您只使用Real Numbers
)。
请执行以下操作:
total
。average
。remainder
。total
。average
。remainder
。average
。这将为您提供最多1个答案(十进制数字系统[基数10])。我还不了解C ++,所以我只能用C#给你一个例子。
C#中的伪代码(只是为了提供一个想法):
int[] C = new int[20]; //The array of elements.
int total = C.Length; //The total amount of elements.
int average = 0; //The variable to contain the result.
int remainder = 0; //The variable to contain all the smaller bits.
foreach (int element in C) //Iteration
{
int temp = (element / total); //Divide the current element by the total.
average = average + temp; //Add the result to the average.
temp = (temp % total); //Get the remainder (number that was not divided)
remainder = remainder + temp; //Add remainder to the other remainders.
}
average = average + (remainder / total); // Adds the divided remainders to the average.
压缩的C#示例:
int[] C = new int[20]; //The array of elements.
int total = C.Length; //The total amount of elements.
int average = 0; //The variable to contain the result.
int remainder = 0; //The variable to contain all the smaller bits.
foreach (int element in C) //Iteration
{
average += (element / total); //Add the current element divided by the total to the average.
remainder += ( element % total); //Add the remainders together.
}
average += (remainder / total); //Adds the divided remainders to the total.