我正在使用C语言编码,并希望找出确定数字中有多少个2可以被整除的最有效方法;例如5 = 0,8 = 3。我的问题是,通过这段代码,我利用位运算来加速运行时间,总体而言代码是O(log N)
,是否有任何计算或分析上的优化可以对这段代码进行优化?
int Prime_Factor_Two(int n) {
int k = 0;
while(~(n&1) + 2){
n = n >> 1;
k +=1;
}
return k;
}
我正在使用C语言编码,并希望找出确定数字中有多少个2可以被整除的最有效方法;例如5 = 0,8 = 3。我的问题是,通过这段代码,我利用位运算来加速运行时间,总体而言代码是O(log N)
,是否有任何计算或分析上的优化可以对这段代码进行优化?
int Prime_Factor_Two(int n) {
int k = 0;
while(~(n&1) + 2){
n = n >> 1;
k +=1;
}
return k;
}
那么最有效的方式是什么?使用(几乎)单个汇编指令怎么样?
根据GCC文档(也适用于Clang):
内置函数:
int __builtin_ctz(unsigned int x)
返回从最低位开始计算的尾随0比特数。 如果
x
为零,则结果未定义。
unsigned Prime_Factor_Two(unsigned x) {
return x ? __builtin_ctz(x) : 0;
}
__builtin_ctz(x)
。
__builtin_ctz()
内建函数:
31 - CNTLZ(x & -x)
(假设32位无符号数)。unsigned
更改为int
:unsigned Prime_Factor_Two(int x) {
return x ? __builtin_ctz(x) : 0;
}
x = x & -x
操作来隔离最低有效位;然后,您可以使用log2(x)
函数将其转换为集合位的位置。#include <stdio.h>
#include <math.h>
int main()
{
int num, ans;
do {
printf("Enter a number: ");
if (scanf("%d", &num) != 1 || num == 0) break;
ans = (int)(log2(num & -num) + 0.5);
printf("Answer is: %d\n", ans);
} while (num > 0);
return 0;
}
int main()
{
int num, ans;
do {
printf("Enter a number: ");
if (scanf("%d", &num) != 1) break;
num &= -num;
for (ans = 0; num > 1; ans++) num >>= 1;
printf("Answer is: %d\n", ans);
} while (num > 0);
return 0;
}
编辑: 当然,上述两种方法都是牵强附会且不必要的;一个简单的循环加上一个移位的单比特掩码就可以完成任务——除了值为零,因为它无论如何都可以被2整除(没有余数)无限次:
#include <stdio.h>
int main()
{
int num, ans, bit;
do {
printf("Enter a number: ");
if (scanf("%d", &num) != 1 || num == 0) break;
for (ans = 0, bit = 1; !(num & bit); ans++) bit <<= 1;
printf("Answer is: %d\n", ans);
} while (1);
return 0;
}
/*
* Returns the number of factors of 2 in the prime factorization of the argument, or
* returns -1 if the argument is 0.
*/
int factor_of_two_count(uint64_t in) {
int result = -1;
uint64_t bottom;
bottom = (in & 0xffffffffu);
in = bottom ? bottom : (in >> 32);
result += !bottom * 32;
bottom = (in & 0xffffu);
in = bottom ? bottom : (in >> 16);
result += !bottom * 16;
bottom = (in & 0xffu);
in = bottom ? bottom : (in >> 8);
result += !bottom * 8;
bottom = (in & 0xfu);
in = bottom ? bottom : (in >> 4);
result += !bottom * 4;
bottom = (in & 0x3u);
in = bottom ? bottom : (in >> 2);
result += !bottom * 2;
bottom = (in & 0x1u);
result += !bottom;
return result;
}
unsigned int v; // find the number of trailing zeros in v
int r; // put the result in r
static const int Mod37BitPosition[] = // map a bit value mod 37 to its position
{
32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4,
7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5,
20, 8, 19, 18
};
r = Mod37BitPosition[(-v & v) % 37];
作者解释:
上面的代码找到了在右侧末尾的零的数量,所以二进制0100会产生2。它利用了前32位位置值与37互质的事实,因此对37进行模数除法可以得到从0到36的唯一数字。这些数字可以使用一个小查找表映射到零的数量。它只使用了4个操作,但索引到表中并执行模数除法可能使其不适用于某些情况。
int
类型,只有30个数字是2的正整数次幂,其他42亿个数字都不匹配。你可以很容易地使用switch
在 O(1) 时间内完成这个操作。 - tadmanwhile(~(n&1) + 2)
(三个运算符和一个测试)可以简化为while((n&1) == 0)
(一个运算符和一个测试)。 - Weather Vane(x & ~(x-1))
(这将产生2的幂),然后对结果进行二分查找,在O(log log n)时间内完成。 - n. m.