以下方法是不好的,因为它依赖于以下内容:
- 负整数的右移是算术右移(可能不是这种情况)
- 有符号整数采用二进制补码表示法(极少情况下可能不是这种情况)
- 整数没有填充位(现代CPU上这些天你不会找到填充位,尽管标准允许它们存在)
并且它可能会导致一些被除数的未定义行为(例如
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's complement-ness和填充位仍然存在局限性,但在今天的实践中几乎不存在):
#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.. 正确地指出,将 signed int
转换为 unsigned int
可以通过添加 0u(unsigned 0)来完成。
他还提醒说,可以直接返回 un
而不是对 n
执行 memcpy()
。转换应该是实现定义的,在 C 的 2's complement 实现中,几乎总是以位对位复制的方式进行的。
/
来获得结果 还是 使用>>
来执行等价的除法? - Koushik Shetty/
。现在我明白了。 - Koushik Shetty