在C中反转2的幂的最快方法是什么?

时间:2019-05-16 09:49:25

标签: c optimization embedded

在等式中:2^x=a

在C语言中用给定的2的幂(a)求x的最快方法是什么?

编辑

  1. 精确的数学解是: log2(x)
  2. 因为( a )是正整数 2的幂(无有理数,不等于零),所以此问题可以简化为"looking for position of set bit"
  3. 本文主要针对精简嵌入式CPU系统。例如:ARM CORTEX M4。

a到x的结果:

  a | x
 -------
  1 | 0
  2 | 1
  4 | 2
  8 | 3
 16 | 4
 32 | 5
 64 | 6
128 | 7
256 | 8
512 | 9
...

选项1:脏循环

unsigned int get_power_of_two_exponent(unsigned int value)
{
    unsigned int x = 0;

    while( ( 1 << x ) != value)
    {
        x ++;
    }

return x;
}

选项2:奇怪的把戏

#include <stdint.h>

#if defined(__GNUC__)
static int highest_bit_set(uint32_t value)
{
    if (sizeof (unsigned int) == sizeof value)
        return 31 - __builtin_clz(value);
    else
    if (sizeof (unsigned long) == sizeof value)
        return 31 - __builtin_clzl(value);
    else
        exit(127); /* Weird architecture! */
}
#endif

有更快的选择吗?

6 个答案:

答案 0 :(得分:4)

C中的

最快几乎总是查找表,以牺牲内存使用为代价。假定该值始终是2的幂,则可以创建一个查询表,如下所示:

uint8_t get_exponent (uint8_t val)
{
  static const uint8_t byte[256] = 
  {
    [1]   = 0,
    [2]   = 1,
    [4]   = 2,
    [8]   = 3,
    [16]  = 4,
    [32]  = 5,
    [64]  = 6,
    [128] = 7,
  };

  return byte[val & 0xFF];
}

如果您传递的值不是2的幂,它将返回0。

这可以通过循环遍历uint32_t的4个字节并执行4个表查找来进一步扩展。或者通过制作更大的查找表。

在x86上,我得到的内容可以归结为这个小巧的,无分支的机器代码:

get_exponent:
        movzx   edi, dil
        movzx   eax, BYTE PTR byte.2173[rdi]
        ret

(在这种情况下,切换到uint_fast8_t会得到相同的代码。)

答案 1 :(得分:2)

此答案有争议-请参阅评论。

最快的方法,有点讽刺地 1 ,是写

switch (a)
{
    case 1: return 0;
    case 2: return 1;
    case 4: return 2;
    ...

很显然,标签的类型与类型中的位一样多,但是仍然是O(1)。

您甚至可以使用成语aa ^ (a & (a - 1))截断为2的幂,以可移植性为代价,因为仅当a是2的补码类型时,才有效。


1 尽管在C ++中,您可以让编译器使用constexpr和元编程技术来构建表。

答案 2 :(得分:2)

使用以下命令可获得最佳性能(在我的嵌入式ARM CORTEX M4 CPU内核上):

内置CLZ 解决方案(计数前导零)

此外,CLZ解决方案的内存效率远远高于第二名的查找表方法。

通常,LookUp表方法的效率仍然不如Builtin CLZ,因为该表存储在RAM中,例如DDR。因此,访问这种RAM中的数据可能需要花费十几个周期。在此示例中,这是由于启用了指令高速缓存而不启用了数据高速缓存而被放大的。此外,将这个巨大的表存储在缓存中不太合适。

benchmark

答案 3 :(得分:0)

这取决于您要搜索多少值,以及是否定义了最大的输入值。

例如,如果x可以是100,则从步骤(x = 0)开始x++进行搜索,这不是很优雅且没有优化(100检查)。您可以设置步骤x+=5。如果结果低于搜索值,则x+=5。如果更大,则退后x--(最多4次)。您可以根据需要调整步长。

如果存在“上限”,则可以创建一个可能的x数组并实现二进制搜索。

答案 4 :(得分:0)

@Lundin的回答似乎在速度方面是最好的(仅3条汇编说明!),但是对于您的嵌入式系统而言,它可能不是一个好的选择。如果无法使用大型LUT:

我想,怪异的把戏似乎是一个快速的选择(不过,您应该对每个选项进行基准测试并查看实际结果)。您可以在存在的情况下使用它,否则可以回退到通常的移动方式:

                   ..
                   ..
stmt.executeUpdate(
    "IF object_id('tempdb..#temp_table') IS NOT NULL DROP TABLE #temp_table");
stmt.execute("CREATE TABLE #temp_table([context] [varchar](100) NULL)");
stmt.execute("INSERT INTO #temp_table(context) VALUES ('" + context + "')");

如果要确保它是2的幂,可以使用popcnt。当输入不是2的幂时,while循环是一个无限循环,而我的循环只是根据最高位提供解决方案(根据您的需要,这可能是不正确的)。

答案 5 :(得分:0)

2 ^ x = a是等式

假设采用32位架构,并且“ a”和“ x”为整数。

这是我的方法

uint32_t x;
uint8_t *ptr ;
uint8_t ByteNo,BitNo,i;

void My_Function(uint32_t a)
{
    ByteNo = BitNo = 9;//some random number
    ptr = (uint8_t*)&a;//Assuming points to LSB in variable a
    for(i=0;i<4;i++)
    {
        switch(*ptr)
        {
        case 0x01:  BitNo=0;break;
        case 0x02:  BitNo=1;break;
        case 0x04:  BitNo=2;break;
        case 0x08:  BitNo=3;break;
        case 0x10:  BitNo=4;break;
        case 0x20:  BitNo=5;break;
        case 0x40:  BitNo=6;break;
        case 0x80:  BitNo=7;break;
        case 0x00:  BitNo=9;break;
        default :   break;//take care error condition
        }

        if(9 != BitNo)
        {
            break;
        }
        else
        {
            ptr++;
        }
    }//for loop
    ByteNo = i;

    x = (BitNo) + (ByteNo*8);

}//My_Function

另一种方法:

switch(a)
{
case   0x00000001:   x=0;   break;
case   0x00000002:   x=1;   break;
case   0x00000004:   x=2;   break;
case   0x00000008:   x=3;   break;
case   0x00000010:   x=4;   break;
case   0x00000020:   x=5;   break;
case   0x00000040:   x=6;   break;
case   0x00000080:   x=7;   break;
case   0x00000100:   x=8;   break;
case   0x00000200:   x=9;   break;
case   0x00000400:   x=10;   break;
case   0x00000800:   x=11;   break;
case   0x00001000:   x=12;   break;
case   0x00002000:   x=13;   break;
case   0x00004000:   x=14;   break;
case   0x00008000:   x=15;   break;
case   0x00010000:   x=16;   break;
case   0x00020000:   x=17;   break;
case   0x00040000:   x=18;   break;
case   0x00080000:   x=19;   break;
case   0x00100000:   x=20;   break;
case   0x00200000:   x=21;   break;
case   0x00400000:   x=22;   break;
case   0x00800000:   x=23;   break;
case   0x01000000:   x=24;   break;
case   0x02000000:   x=25;   break;
case   0x04000000:   x=26;   break;
case   0x08000000:   x=27;   break;
case   0x10000000:   x=28;   break;
case   0x20000000:   x=29;   break;
case   0x40000000:   x=30;   break;
case   0x80000000:   x=31;   break;
default:    break;//error condition
}