【问题标题】:Bit twiddling a lot of bits in C在 C 中摆弄很多位
【发布时间】:2010-10-18 06:01:53
【问题描述】:

我想使用二进制标志来表示 C 中的数学集合,其中“Bit i is set”表示“item i is in the Set”。这很方便,因为像“union”和“intersection”这样的操作很容易实现(“|”和“&”)。但是,我希望我的套装能够包含 32 件以上的物品。此外,我希望我的代码能够在 32 位和 64 位机器上运行。

有没有什么简单的方法可以在 C 语言中操作多于一个字的位?有没有更好的方法来完成这项任务?

【问题讨论】:

    标签: c


    【解决方案1】:

    是的,您只需定义一个 32 位整数数组。然后操作数组的特定元素。

    给定一个从 0 到 255(包括)的位 ID(例如),这将是一个数组:

    unsigned int bits[8];
    

    为了找到对哪个元素进行操作:

    unsigned int index = bitId >> 5; // turns 0..255 into 0..31
    

    获取给定位 ID 的掩码:

    unsigned int masks[] = {
        0x0001, 0x0002, 0x0004, 0x0008,
        0x0001, 0x0020, 0x0040, 0x0080,
        0x0100, 0x0200, 0x0400, 0x0800,
        0x1000, 0x2000, 0x4000, 0x8000
    };
    unsigned int mask = masks[bitId & 0x1f];
    

    如果您的实现中有 uint32_t 类型可用,那可能是最安全的方法。否则,有一些已知的方法可以使用unsigned int 使用CHAR_BITsizeof 在运行时实际计算出masks 数组的大小以及应该使用哪些值来发现数组索引和位掩码索引。

    例如,我的代码库中的这个 sn-p 显示了我如何为基于字符的位掩码做到这一点:

    static unsigned char bitmask[CHAR_BIT];
    void bitsetInit (void) {
            unsigned char mask = 1;
            int i = 0;
            while (i < CHAR_BIT) {
                    bitmask[i++] = mask;
                    mask <<= 1;
            }
    }
    

    并使用:

    bsp->bits[bitnum/CHAR_BIT] &= ~bitmask[bitnum%CHAR_BIT];
    bsp->bits[bitnum/CHAR_BIT] |= bitmask[bitnum%CHAR_BIT];
    

    分别用于清除和设置位。

    如果您想使用unsigned int 而不是unsigned char,您只需计算相应的位数:

    unsigned int UINT_BIT = CHAR_BIT * sizeof (unsigned int);
    

    并在我上面使用过CHAR_BIT 的地方使用它(如果需要,mask 数组可以在运行时动态分配)。

    【讨论】:

    • 我认为uint32_t 在这里使用会更安全。
    • 如果有的话,是的。最初的问题表示 32 位,现在已更改。所以我会更新答案...
    • 您对UINT_BIT 的定义不正确。 unsigned int 可能有填充位。只使用固定宽度的类型,或者使用unsigned charCHAR_BIT,这总是最安全的。 (顺便说一句,唯一缺少固定宽度类型的系统正是那些像unsigned int 这样的类型的行为可能会涉及诸如填充之类的讨厌东西的系统。)实际上8 可能比CHAR_BIT 更好,所以如果@987654344 @ 是 9 之类的假的东西,你不会被 9 除而不是右移卡住。 :-)
    • > 给定一个从 0 到 255 的位 ID(例如),这将是一个数组: > unsigned int bits[8];但这是至少 512 位的空间。
    • 你为什么要把masks/bitmask数组复杂化?只需使用1&lt;&lt;(bitnum%CHAR_BIT)
    【解决方案2】:

    Gnu 多精度库提供整数实现,对任意精度的整数进行了很好的优化,并且还具有最有用的位旋转功能。 (link)

    根据您实际需要执行的具体操作,可能会有一些花哨的数据结构可以更好地完成这项工作。例如,有一个非常聪明的 Disjoint Sets 结构,用于对一组不相交的集合进行建模,它在它支持的 3 种操作上具有真正惊人的渐近性能。

    【讨论】:

    • 人口数甚至汉明距离,哦哦!
    • 人口计数在这里特别有用,因为它相当于设置基数,这有时是一件好事。
    • GMP 可能没问题,但我永远不会将它推荐用于生产工作,因为我发现如果内存用完它就会简单地退出。在我看来,对于一个图书馆来说,这是一个糟糕的设计选择。
    • @pax,默认内存函数在打印到标准错误后退出,但该库允许使用行为不同的custom allocators(例如自定义日志记录),尽管它们仍然无法正常返回.
    • @Matthew,我已经与 GMP 开发人员进行了长时间的讨论,无需在这里重新讨论。我们最终同意不同意 :-) 因为他们不认为这是一个问题。
    【解决方案3】:

    您可以使用来自&lt;stdint.h&gt;uint64_t。除此之外,就&amp;| 而言,恐怕你不走运,应该寻找不同的设计(例如具有适当功能的结构来处理它们,或第三方库。)。

    【讨论】:

      【解决方案4】:

      paxdiablo 似乎给了你正确的方法来解决这个问题,就像你说你想要解决的那样。

      有没有更好的方法来完成这项任务?

      除非您出于特定的性能或硬件原因在位级别上进行工作,否则可能有更好的方法来表示集合。例如,链表或二叉树,其值是集合的成员。这两种结构都可以(有效地)具有无限大小,并且易于迭代。

      仅仅因为一些集合操作很容易用布尔逻辑实现并不意味着所有操作都是。如果您有一个集合类型的接口,而不是一个布尔逻辑(仅)接口,那么依赖于您的集合操作的附加代码可能会更清晰。

      无论您想出什么解决方案,我都建议您将其隐藏在界面后面,以便您将来更改存储解决方案。为此,您可以定义将结构传递给的函数,并且仅通过这些函数对结构进行操作。

      【讨论】:

      • 我不同意你的回答。直到微小的调整,位数组只有一种可能的实现。任何其他数据结构的性能都会差得多,无论是在大 O 还是简单的实际开销方面,并且为了可能能够在更慢的实现中交换而包装你的位数组操作并使它们变慢是没有意义的未来。
      • @R:我说的是数学集合,而不是位数组。如果您只是在谈论位数组,那么您是对的:没有比位数组更快的方法来实现位数组。我没有研究过集合(除了我通过数据库操作接触过的少量数据),所以如果集合上除了布尔代数之外没有其他操作,那么告诉我,我会删除我的答案。不过,我的印象是域比这更大。另外,位域不一定是稀疏分配的最佳存储方案。
      【解决方案5】:

      如果您真的对 32 位和 64 位类型感到满意,那么在现代 C(也称为 C99)中,类型定义 uint_least32_tuint_least64_t 保证存在于 "stdint.h" 中。与精确宽度类型 uint32_tuint64_t(可选)相比,它们可能对应于宽度大于数字所示宽度的基本类型。

      如果速度很重要,您也可以使用uint_fast32_tuint_fast64_t,它们也必须存在。他们以速度换取大小,并且应该使用在目标机器上具有“最快”支持的相应基本类型。不过,数据的激增可能是巨大的。例如。在我的 64 位 ubuntu 上,所有这些“快速”类型都是 64 位类型。

      如果您使用 gcc,您还可以在 64 位计算机上使用 __uint128_t 作为一项额外服务。

      【讨论】:

        猜你喜欢
        • 2017-09-21
        • 1970-01-01
        • 2011-04-05
        • 1970-01-01
        • 1970-01-01
        • 2018-09-09
        • 2019-03-30
        • 2012-02-16
        • 1970-01-01
        相关资源
        最近更新 更多