我想用2的幂执行有符号整数按位除法。但是,我遇到了几个问题。我只是想知道是否有人可以帮助我。
首先,我尝试单独使用位移:
int result = number >> n;
但是,当我尝试划分负数时,我遇到了问题。 (它总是用一个更大幅度的数字来围绕。例如:-9 / 4 = -3而不是-2。所以,我在互联网上看这个问题,最终得到了这个解决方案:
int result = (number + (1<<n)-1) >> n;
然而,当我尝试11/4 = 3而不是2
时有什么建议吗?我只能用! 〜&amp; ^ | +&lt;&lt; &GT;&GT; (没有循环或if / switch允许)
答案 0 :(得分:3)
以下方法很糟糕,因为它依赖于:
由于有符号整数溢出,它可能会导致某些股息(例如INT_MIN
)的未定义行为。
因此它不便携,不能保证始终有效。你被警告了。
#include <stdio.h>
#include <limits.h>
int DivByShifting1(int n, unsigned shift)
{
int sgn = n >> ((sizeof(int) * CHAR_BIT) - 1);
return ((((n + sgn) ^ sgn) >> shift) + sgn) ^ sgn;
}
int main(void)
{
int n, s;
for (n = -10; n <= 10; n++)
for (s = 0; s <= 4; s++)
printf("%d / %d = %d\n", n, 1 << s, DivByShifting1(n, s));
return 0;
}
输出(ideone):
-10 / 1 = -10
-10 / 2 = -5
-10 / 4 = -2
-10 / 8 = -1
-10 / 16 = 0
-9 / 1 = -9
-9 / 2 = -4
-9 / 4 = -2
-9 / 8 = -1
-9 / 16 = 0
-8 / 1 = -8
-8 / 2 = -4
-8 / 4 = -2
-8 / 8 = -1
-8 / 16 = 0
-7 / 1 = -7
-7 / 2 = -3
-7 / 4 = -1
-7 / 8 = 0
-7 / 16 = 0
-6 / 1 = -6
-6 / 2 = -3
-6 / 4 = -1
-6 / 8 = 0
-6 / 16 = 0
-5 / 1 = -5
-5 / 2 = -2
-5 / 4 = -1
-5 / 8 = 0
-5 / 16 = 0
-4 / 1 = -4
-4 / 2 = -2
-4 / 4 = -1
-4 / 8 = 0
-4 / 16 = 0
-3 / 1 = -3
-3 / 2 = -1
-3 / 4 = 0
-3 / 8 = 0
-3 / 16 = 0
-2 / 1 = -2
-2 / 2 = -1
-2 / 4 = 0
-2 / 8 = 0
-2 / 16 = 0
-1 / 1 = -1
-1 / 2 = 0
-1 / 4 = 0
-1 / 8 = 0
-1 / 16 = 0
0 / 1 = 0
0 / 2 = 0
0 / 4 = 0
0 / 8 = 0
0 / 16 = 0
1 / 1 = 1
1 / 2 = 0
1 / 4 = 0
1 / 8 = 0
1 / 16 = 0
2 / 1 = 2
2 / 2 = 1
2 / 4 = 0
2 / 8 = 0
2 / 16 = 0
3 / 1 = 3
3 / 2 = 1
3 / 4 = 0
3 / 8 = 0
3 / 16 = 0
4 / 1 = 4
4 / 2 = 2
4 / 4 = 1
4 / 8 = 0
4 / 16 = 0
5 / 1 = 5
5 / 2 = 2
5 / 4 = 1
5 / 8 = 0
5 / 16 = 0
6 / 1 = 6
6 / 2 = 3
6 / 4 = 1
6 / 8 = 0
6 / 16 = 0
7 / 1 = 7
7 / 2 = 3
7 / 4 = 1
7 / 8 = 0
7 / 16 = 0
8 / 1 = 8
8 / 2 = 4
8 / 4 = 2
8 / 8 = 1
8 / 16 = 0
9 / 1 = 9
9 / 2 = 4
9 / 4 = 2
9 / 8 = 1
9 / 16 = 0
10 / 1 = 10
10 / 2 = 5
10 / 4 = 2
10 / 8 = 1
10 / 16 = 0
请注意,((sizeof(int) * CHAR_BIT) - 1)
是一个编译时常量,因此可以允许*
和-
。
另一个版本非常相似,但不要求负整数的右移是算术移位而且没有有符号整数溢出(2的补码和填充位仍然是局限性的,但在今天几乎不存在实践):
#include <stdio.h>
#include <limits.h>
#include <string.h>
int DivByShifting2(int n, unsigned shift)
{
unsigned un = n;
unsigned sgn = 1 + ~(un >> ((sizeof(int) * CHAR_BIT) - 1));
un = ((((un + sgn) ^ sgn) >> shift) + sgn) ^ sgn;
memcpy(&n, &un, sizeof n);
return n;
}
int main(void)
{
int n, s;
for (n = -10; n <= 10; n++)
for (s = 0; s <= 4; s++)
printf("%d / %d = %d\n", n, 1 << s, DivByShifting2(n, s));
return 0;
}
输出(ideone):
-10 / 1 = -10
-10 / 2 = -5
-10 / 4 = -2
-10 / 8 = -1
-10 / 16 = 0
-9 / 1 = -9
-9 / 2 = -4
-9 / 4 = -2
-9 / 8 = -1
-9 / 16 = 0
-8 / 1 = -8
-8 / 2 = -4
-8 / 4 = -2
-8 / 8 = -1
-8 / 16 = 0
-7 / 1 = -7
-7 / 2 = -3
-7 / 4 = -1
-7 / 8 = 0
-7 / 16 = 0
-6 / 1 = -6
-6 / 2 = -3
-6 / 4 = -1
-6 / 8 = 0
-6 / 16 = 0
-5 / 1 = -5
-5 / 2 = -2
-5 / 4 = -1
-5 / 8 = 0
-5 / 16 = 0
-4 / 1 = -4
-4 / 2 = -2
-4 / 4 = -1
-4 / 8 = 0
-4 / 16 = 0
-3 / 1 = -3
-3 / 2 = -1
-3 / 4 = 0
-3 / 8 = 0
-3 / 16 = 0
-2 / 1 = -2
-2 / 2 = -1
-2 / 4 = 0
-2 / 8 = 0
-2 / 16 = 0
-1 / 1 = -1
-1 / 2 = 0
-1 / 4 = 0
-1 / 8 = 0
-1 / 16 = 0
0 / 1 = 0
0 / 2 = 0
0 / 4 = 0
0 / 8 = 0
0 / 16 = 0
1 / 1 = 1
1 / 2 = 0
1 / 4 = 0
1 / 8 = 0
1 / 16 = 0
2 / 1 = 2
2 / 2 = 1
2 / 4 = 0
2 / 8 = 0
2 / 16 = 0
3 / 1 = 3
3 / 2 = 1
3 / 4 = 0
3 / 8 = 0
3 / 16 = 0
4 / 1 = 4
4 / 2 = 2
4 / 4 = 1
4 / 8 = 0
4 / 16 = 0
5 / 1 = 5
5 / 2 = 2
5 / 4 = 1
5 / 8 = 0
5 / 16 = 0
6 / 1 = 6
6 / 2 = 3
6 / 4 = 1
6 / 8 = 0
6 / 16 = 0
7 / 1 = 7
7 / 2 = 3
7 / 4 = 1
7 / 8 = 0
7 / 16 = 0
8 / 1 = 8
8 / 2 = 4
8 / 4 = 2
8 / 8 = 1
8 / 16 = 0
9 / 1 = 9
9 / 2 = 4
9 / 4 = 2
9 / 8 = 1
9 / 16 = 0
10 / 1 = 10
10 / 2 = 5
10 / 4 = 2
10 / 8 = 1
10 / 16 = 0
@R ..正确地提醒可以通过添加0u(无符号0)来完成从signed int
到unsigned int
的转换。
他还提醒un
可以直接返回,而不是memcpy()
到n
。转换应该是实现定义的,但在C的2的补码实现中,逐位复制实际上总是如此。
答案 1 :(得分:1)
只需使用/
运算符:
int result = number / (1 << n);
任何体面的编译器都会将此编译为最佳位移,并使用fixup来“舍入”否定结果。
答案 2 :(得分:0)
也许这应该可以帮到你。
1)如果您使用/
运算符,那么标准会说(ISO/IEC TR3 in 6.5.5 Multiplicative operators
)/
运算符的结果是来自分区的商第二个的第一个操作数。
2)如果您使用>>
标准(ISO/IEC TR3 in 6.5.7
) >>
的结果,其中LHS操作数是有符号类型且为负,则结果值为implementation defined
强>
所以/
会根据需要为您提供结果。
但>>
已签名&amp;&amp;负数取决于您的编译器。
答案 3 :(得分:0)
从int div(int a){return a/4;}
:
leal 3(%rdi), %eax
testl %edi, %edi
cmovns %edi, %eax
sarl $2, %eax
当且仅当操作数为负时,必须通过(1<<n)-1
调整操作数。
无条件的方法是使用sgn(a)*(abs(a)>>n)
;这两者都可以通过无分支bitmagic实现,依赖于实现定义的行为。