将 64 位整数中的每个其他位与 32 位整数进行比较

Comparing every other bit in a 64 bit integer to a 32 bit integer

本文关键字:整数 位与 其他 比较      更新时间:2023-10-16

我正在考虑创建一个小跳棋求解器的想法。首先,我会制作一个非常紧凑的棋盘表示,然后继续构建游戏树等等。

标准棋盘有8 行4 个功能列(跳棋只能对角线移动)。这给了我们 32 个位置!每个职位需要 3 位信息...king位,color位...所以00非国王黑01非国王红10国王黑色11国王红色。这给了我们 64,这是一个很好的数字(长整数的确切大小)。

但是,每个检查器还需要一个额外的位...isOccupied位,因为每个跳棋位置可以是空的,也可以用上述四种状态之一填充。我决定将 64 个状态放入一个长 64 位 int 中,将 32 个占用状态放入一个 32 位整数中。

所以现在我们有一些背景,我有以下问题:我想轻松说"这个棋盘上有多少个红色跳棋? 嗯,这还不错...我们的 64 位整数保存如下数据:

king_color_king_color_king_color如此011001意味着我们有红色,黑色国王,红色。

为了只获取颜色信息,我们可以使用 01010101...01 的位掩码,该掩码以十六进制0x5555555555555555。这将国王位归零,只留下颜色位。因此,使用掩码进行 AND 之后的011001示例,我们010001。如果我们计算位数(popcountbitcount),我们得到红色的数量...

啊,但是等等!这些颜色可能没有"使用中"。我们必须检查我们的 32 位 int 以查看是否正在使用给定的检查器!所以假设我们有 011 用于我们占用的 32 个整数......这意味着第一个检查器,上面的01(红色非国王)...居然没被占用...它只是一个空的广场。如果我们要在那里移动另一个检查器,我们可能需要也可能不需要更新那 2 个特大号位。 所以把它们放在一起

32bit = 011
64bit = 011001

代表 3 个棋盘位置...空棋子之前是红色,其次是黑色国王,然后是红色。一旦我们在 64 位上执行010101掩码操作,我们就会得到:

64bitWithMask = 010001
32bit=011

天真地我们有 2 个红色...但我们实际上只有 1 个活动...我想做的基本上是取 64 位字符串中的奇数位,以及 32 位字符串中的每个位......即

1 AND 0, 0 AND 1, 1 AND 1给了我们 001,它代表红色跳棋的数量。

或者等效地,将64bitWithMask转换为64bitWithMaskOddBits = 101然后简单地 AND 用 32 位得到011 & 101 = 001.

所以形式上,有没有办法取一个长度为 2X 的位字符串,并通过仅取奇数位将其减少到长度 X?我非常努力地避免循环,ifs等,并且只使用逻辑(和,or,xor,否定等)。

或者,当然,如果有另一种策略可以在给定我的 32 位和 64 位字符串的情况下获得正确的红色计数。 谢谢!

编辑:

我提出的问题的解决方案在下面的接受答案中得到了优雅的解决,但对于我的实际应用程序来说,更好的解决方案是将 64 位表示形式分成两个 32。这为我节省了一堆操作来提取我需要的东西。感谢LukeG和Tehtmi的帮助!我很高兴接触到这种新的位操作技术,"并行"。

将一个数字的所有其他位压缩到一个半长的数字有点棘手,因为每个位都需要移动不同的量。但是,有一种聪明的方法可以做到这一点,它需要比单独处理每个位更少的操作。对于 64 位,它看起来像这样(伪代码):

x = x & 0x5555555555555555
// or for the alternate bits: x = (x >> 1) & 0x5555555555555555
x = (x | (x >>  1)) & 0x3333333333333333
x = (x | (x >>  2)) & 0x0f0f0f0f0f0f0f0f
x = (x | (x >>  4)) & 0x00ff00ff00ff00ff
x = (x | (x >>  8)) & 0x0000ffff0000ffff
x = (x | (x >> 16)) & 0x00000000ffffffff

下图说明了 32 位数字(在初始掩码之后)每一步的位发生了什么情况:

0a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p
00ab00cd00ef00gh00ij00kl00mn00op
0000abcd0000efgh0000ijkl0000mnop
00000000abcdefgh00000000ijklmnop
0000000000000000abcdefghijklmnop

例如,位g需要向右移动9,因此请查看9 = 1 + 8的两倍分量。因此,g>> 1步骤和>> 8步骤中发生了偏移。

这种位算法有时被描述为"并行"。您可能有兴趣查看这个著名的列表。(它包括交错,这与这里发生的事情密切相关。

这类代码的标准免责声明是它通常很难使用,所以除非确实存在性能问题,否则它可能不应该在严肃的项目中使用(即使这样,也要确保清楚代码应该做什么,例如带有注释)。如果没有性能问题,并且您仍然希望使用位操作,则可能仍首选循环解决方案,因为它更易于理解和使用。

我不认为任何方法可以在不使用循环的情况下做到这一点。

编辑:我被tehtmi证明是错误的。Wile 我仍然认为本答案末尾提出的"替代解决方案"是解决手头问题的更好方法,tehtmi 提出了一个非常有趣的解决方案,如果您还没有,您应该向上滚动并投赞成票。

我看到两种方法可以解决这个问题。

第一个接近您想要实现的目标,是:

uint32_t occupied;
uint64_t data;
uint32_t occupiedWithRed;
for (auto i = 0; i < 32; ++i) {
occupiedWithRed |= (data >> i) & occupied & (1 << i);
}

红色位置的计数将是占用的 WithRed 中的设置位计数。

更简单(也可能更快)的方法是:

uint32_t occupied;
uint64_t data;
auto count = 0;
for (auto i = 0; i < 32; ++i) {
if ((data >> (2 * i)) & (occupied > i)) ++count;
}

或者,做一些完全不同的事情:正如评论中指出的那样,如果您将数据分成 3 个不同的 32 位无符号整数,您可以轻松生活。一个用于区分红黑,一个用于区分自由和占领,一个用于区分国王和无国王。这样,您的任务将变得更加容易。这将是一个位位和计算汉明权重的问题。

与其收集偶数位或奇数位与 32 个占用位进行比较,我宁愿走另一条路,将它们分布到 64 位整数中,这样它们只在奇数位置上。此外,我会将它们移动到另一个 64 位整数中的偶数位置。

然后,您可以轻松地将奇数位置或偶数位置占用整数与位置信息整数中的偶数位或奇数位进行比较。