设置了 MSB 的第一个字节的索引
Index of first byte having its MSB set
我有八个 8 位值存储在一个 64 位整数中。每个字节的最高位可以为 1 或 0,其余位均为 0。示例:
MSB 10000000 00000000 10000000 ... 10000000 00000000 00000000 LSB
我现在需要找到设置了位的第一个字节的索引。首先意味着我们从最不重要的方向搜索。在上面的示例中,结果将是 2.
我们可以使用 de Bruijn 扫描第一个设置位并除以 8 以获得其字节索引。
这是我的问题:de Bruijn 是通用的,它适用于任何输入。但在我的用例中,我们仅限于仅设置了 MSB 的字节。是否可以针对这种情况进行优化?
实现是在 C++ 中。我不能使用任何内在函数或内联汇编(_BitScanForward64()、__builtin_clzll 等)。
(编辑)
隔离最低设置位 x &= (-x)
然后查看 How to find the position of the only-set-bit in a 64-bit value using bit manipulation efficiently? 正在检查这个确切的问题(尽管标题)。
下面的答案稍微笼统一些。
通过消除 table 查找,可以在 de Bruijn 位扫描上节省几个延迟周期。
uint64_t ByteIndexOfLowestSetBit(uint64_t val) {
assert(val != 0);
const uint64_t m = UINT64_C(0x0101010101010101);
return ((((val - 1) ^ val) & (m - 1)) * m) >> 56;
}
使用尾随位操作获得覆盖最低设置位及以下的掩码。
将掩码覆盖的每个字节设置为 1
。通过 prefix-summing 横向计算我们有多少 1
字节。我们现在已经将一个从 1 开始的字节索引放入 u64 字的最高有效字节中。将计数移到底部并减去 1
以获得从 0 开始的索引。但是,我们不希望关键路径上的 -1
...所以从 m
中减去 1
,这样我们就永远不会计算总数中的最低有效字节。
找到最高 集合 MS1B 的问题更复杂,因为我们没有任何 bit-manipulation 技巧来隔离想要的位。在这种情况下,
Extract Bits with a Single Multiplication,将它们作为 table 的索引。如果不允许输入值为零,那么最低有效字节的值要么无关紧要,要么是 non-zero。这允许使用具有 7 位索引而不是 8 位索引的查找 table。
根据需要进行调整。
uint64_t ReversedIndexOf_Highest_Byte_With_LSB_Set (uint64_t val) {
static const unsigned char ctz7_tab[128] = {
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
};
assert(val != 0);
assert((val & 0xFEFEFEFEFEFEFEFEULL) == 0);
val = (val * UINT64_C(0x0080402010080402)) >> 57;
return ctz7_tab[val];
}
这里有一个简单的方法。
int LeastSignificantSetBitByteIndex(long value)
{
if((value & 0x80) != 0) return 0;
if((value & 0x8000) != 0) return 1;
if((value & 0x800000) != 0) return 2;
if((value & 0x80000000L) != 0) return 3;
if((value & 0x8000000000L) != 0) return 4;
if((value & 0x800000000000L) != 0) return 5;
if((value & 0x80000000000000L) != 0) return 6;
if((value & 0x8000000000000000L) != 0) return 7;
return -1;
}
int MostSignificantSetBitByteIndex(long value)
{
if((value & 0x8000000000000000L) != 0) return 0;
if((value & 0x80000000000000L) != 0) return 1;
if((value & 0x800000000000L) != 0) return 2;
if((value & 0x8000000000L) != 0) return 3;
if((value & 0x80000000L) != 0) return 4;
if((value & 0x800000) != 0) return 5;
if((value & 0x8000) != 0) return 6;
if((value & 0x80) != 0) return 7;
return -1;
}
我有八个 8 位值存储在一个 64 位整数中。每个字节的最高位可以为 1 或 0,其余位均为 0。示例:
MSB 10000000 00000000 10000000 ... 10000000 00000000 00000000 LSB
我现在需要找到设置了位的第一个字节的索引。首先意味着我们从最不重要的方向搜索。在上面的示例中,结果将是 2.
我们可以使用 de Bruijn 扫描第一个设置位并除以 8 以获得其字节索引。
这是我的问题:de Bruijn 是通用的,它适用于任何输入。但在我的用例中,我们仅限于仅设置了 MSB 的字节。是否可以针对这种情况进行优化?
实现是在 C++ 中。我不能使用任何内在函数或内联汇编(_BitScanForward64()、__builtin_clzll 等)。
(编辑)
隔离最低设置位 x &= (-x)
然后查看 How to find the position of the only-set-bit in a 64-bit value using bit manipulation efficiently? 正在检查这个确切的问题(尽管标题)。
下面的答案稍微笼统一些。
通过消除 table 查找,可以在 de Bruijn 位扫描上节省几个延迟周期。
uint64_t ByteIndexOfLowestSetBit(uint64_t val) {
assert(val != 0);
const uint64_t m = UINT64_C(0x0101010101010101);
return ((((val - 1) ^ val) & (m - 1)) * m) >> 56;
}
使用尾随位操作获得覆盖最低设置位及以下的掩码。
将掩码覆盖的每个字节设置为 1
。通过 prefix-summing 横向计算我们有多少 1
字节。我们现在已经将一个从 1 开始的字节索引放入 u64 字的最高有效字节中。将计数移到底部并减去 1
以获得从 0 开始的索引。但是,我们不希望关键路径上的 -1
...所以从 m
中减去 1
,这样我们就永远不会计算总数中的最低有效字节。
找到最高 集合 MS1B 的问题更复杂,因为我们没有任何 bit-manipulation 技巧来隔离想要的位。在这种情况下, Extract Bits with a Single Multiplication,将它们作为 table 的索引。如果不允许输入值为零,那么最低有效字节的值要么无关紧要,要么是 non-zero。这允许使用具有 7 位索引而不是 8 位索引的查找 table。
根据需要进行调整。
uint64_t ReversedIndexOf_Highest_Byte_With_LSB_Set (uint64_t val) {
static const unsigned char ctz7_tab[128] = {
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
};
assert(val != 0);
assert((val & 0xFEFEFEFEFEFEFEFEULL) == 0);
val = (val * UINT64_C(0x0080402010080402)) >> 57;
return ctz7_tab[val];
}
这里有一个简单的方法。
int LeastSignificantSetBitByteIndex(long value)
{
if((value & 0x80) != 0) return 0;
if((value & 0x8000) != 0) return 1;
if((value & 0x800000) != 0) return 2;
if((value & 0x80000000L) != 0) return 3;
if((value & 0x8000000000L) != 0) return 4;
if((value & 0x800000000000L) != 0) return 5;
if((value & 0x80000000000000L) != 0) return 6;
if((value & 0x8000000000000000L) != 0) return 7;
return -1;
}
int MostSignificantSetBitByteIndex(long value)
{
if((value & 0x8000000000000000L) != 0) return 0;
if((value & 0x80000000000000L) != 0) return 1;
if((value & 0x800000000000L) != 0) return 2;
if((value & 0x8000000000L) != 0) return 3;
if((value & 0x80000000L) != 0) return 4;
if((value & 0x800000) != 0) return 5;
if((value & 0x8000) != 0) return 6;
if((value & 0x80) != 0) return 7;
return -1;
}