【问题标题】:how to know the position of the first(left -> right) '1' bit in a char efficiently [duplicate]如何有效地知道字符中第一个(左->右)“1”位的位置[重复]
【发布时间】:2014-04-15 12:17:24
【问题描述】:

正如标题所说,我有一个字符(例如 00101011),我怎么知道第一个 '1' 位在其中的位置? (在这种情况下,它是 6,如果我们认为最右边的位置是 0,则为 5)。
我知道 for 循环移位会有所帮助,但有没有更有效的方法?

【问题讨论】:

  • @MatteoItalia 没有给出位置索引。
  • @self.: 对不起,错误的链接,正确的算法是上面两个位置(“整数的Log2”)。
  • @MatteoItalia 我会检查一下,谢谢。
  • @MartinR 抱歉,我也会检查一下。

标签: c bit


【解决方案1】:

有一些 bithacks(正如 Codor 所提到的)可以做得比仅仅移位更好,但在 O(1) 中没有。想到了二分搜索。

恐怕唯一更有效的方法是专门的处理器指令,例如 x86 上的 bsr 指令。一些编译器为这些指令提供内置函数。例如,gcc 为此提供了__builtin_clz

【讨论】:

  • 我看到有人提到基于2的log方法,但是我不确定方法本身的效率。
  • 大多数日志实现都会先将整数转换为双精度数,这在大多数情况下已经严重影响了性能。
【解决方案2】:

讨论了获得最高1 位的各种方法here,其中还提到了您描述的移位方法。来自那里的详细答案:

使用查找表查找整数的以 2 为底的对数

static const char LogTable256[256] = 
{
#define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n
    -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
    LT(4), LT(5), LT(5), LT(6), LT(6), LT(6), LT(6),
    LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7)
};

unsigned int v; // 32-bit word to find the log of
unsigned r;     // r will be lg(v)
register unsigned int t, tt; // temporaries

if (tt = v >> 16)
{
  r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
}
else 
{
  r = (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
}

查找表方法只需要大约 7 次操作即可找到 32 位值的日志。如果扩展到 64 位数量,大约需要 9 次操作。另一个操作可以通过使用四个表来修剪,每个表都包含可能的添加。使用 int 表格元素可能会更快,具体取决于您的架构。

上面的代码被调整为均匀分布的输出值。如果您的输入均匀分布在所有 32 位值中,请考虑使用以下内容:

if (tt = v >> 24) 
{
  r = 24 + LogTable256[tt];
} 
else if (tt = v >> 16) 
{
  r = 16 + LogTable256[tt];
} 
else if (tt = v >> 8) 
{
  r = 8 + LogTable256[tt];
} 
else 
{
  r = LogTable256[v];
}

最初通过算法生成日志表:

LogTable256[0] = LogTable256[1] = 0;
for (int i = 2; i < 256; i++) 
{
  LogTable256[i] = 1 + LogTable256[i / 2];
}
LogTable256[0] = -1; // if you want log(0) to return -1

Behdad Esfahbod 和我在 2005 年 5 月 18 日(平均而言)削减了一部分手术。2006 年 11 月 14 日,Emanuel Hoogeveen 又取消了另一部分手术。 David A. Butterfield 在 2008 年 9 月 19 日提出了调整到均匀分布输入值的变化。Venkat Reddy 在 2009 年 1 月 5 日告诉我 log(0) 应该返回 -1 以指示错误,所以我更改了表中的第一个条目。

【讨论】:

  • 啊,谢谢,我去看看。
  • 好的,谢谢提示。
【解决方案3】:

首先,字节中的位总是从 0 到 7 枚举,其中位 0 是最低有效位。这种计数方式是通用的。

至于什么是最有效的,定义“高效”。最省速的方法是在编译时生成一个256字节的表,其中每一项都包含第一位的位位置:

#define ALL_ZEROES 0xFF // special case

const uint8_t LOOKUP_TABLE [256] =
{
  ALL_ZEROES, // no 1 found
  0,          // 0000 0001, first 1 at bit 0
  1,          // 0000 0010, first 1 at bit 1
  0,          // 0000 0011, first 1 at bit 0
  2,          // 0000 0100, first 1 at bit 2
  ...
};

然后你通过输入得到位置:

first_one = LOOKUP_TABLE[my_value];

但是,这确实消耗了 256 字节的 ROM 内存,只是为了获得一些额外的 CPU 滴答声。因此,虽然它的速度非常高效,但在内存方面却非常低效。

上面提到的带有for循环和移位的方法已经很快了,而且它没有这种内存开销。因此,在大多数应用程序中,您可能会使用 for 循环,因为它在速度和内存消耗方面最有效。

这完全取决于应用程序的性质。

【讨论】:

    猜你喜欢
    • 2023-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-22
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    相关资源
    最近更新 更多