检测C++中的匹配位
Detecting matching bits in C++
>例如,我正在尝试获取两个bitset
对象
a = 10010111
b = 01110010
并从两个变量中删除位,如果它们在同一位置/索引中匹配。所以我们只剩下
a = 100xx1x1 = 10011
b = 011xx0x0 = 01100
有什么办法可以做到这一点吗?
其他答案显示了很好的、惯用的C++方法。不幸的是,它们会相当慢。即使是 AndyG 聪明的基于模板的解决方案,尽管它在编译时确实做了尽可能多的工作,但仍然会导致编译器生成大量必须在运行时执行的代码。
如果您关心速度并且针对支持 BMI2 指令集的处理器(可能是英特尔 Haswell 及更高版本,或 AMD Excavator 及更高版本),那么您可以使用执行并行位提取的PEXT
指令。这使您可以在大约两条机器指令中解决整个问题。
由于您不是在汇编中编写,因此您将使用相应的内在函数作为PEXT
指令,即_pext_u32
。就其基本形式而言,代码简单、可读且非常高效:
#include <stdint.h> // for uint32_t
#include <x86intrin.h> // for _pext_u32() [on MSVC, drop the 'x86']
void RemoveMatchingBits(uint32_t& a, uint32_t& b)
{
const uint32_t mask = (a ^ b);
a = _pext_u32(a, mask);
b = _pext_u32(b, mask);
}
首先,按位或两个值(a
和b
一起)。这将生成一个掩码,如果相应的位设置为a
或b
,则设置掩码中的每个位,否则不设置该位。然后,该掩码用作_pext_u32
执行的位提取的基础。两个位提取操作使用相同的掩码,因此只需要一条XOR
指令。每个_pext_u32
内部函数都将编译为PEXT
指令。因此,除了一些MOV
的指令来随机排列值(这将取决于用于生成代码的编译器以及此代码是否内联)之外,只需要三个机器代码指令。以下是GCC和Clang的现代版本如何编译上述函数(MSVC和ICC发出的代码非常相似):
RemoveMatchingBits(unsigned int&, unsigned int&):
mov eax, DWORD PTR [rdi] // rdi contains a pointer to 'a'
mov edx, DWORD PTR [rsi] // rsi contains a pointer to 'b'
xor edx, eax
pext eax, eax, edx
mov DWORD PTR [rdi], eax
mov eax, DWORD PTR [rsi]
pext eax, eax, edx
mov DWORD PTR [rsi], eax
ret
如您所见,这里的大多数额外指令都是MOV
的,由我们编写函数的方式强制要求接受其参数引用并就地修改这些值。调整函数的编写方式,和/或让优化器在调用站点内联它,将产生更有效的实现。
如果你想使用一个std::bitset
,只需稍微修改代码。to_ulong()
成员函数允许您访问原始位进行操作。像这样:
void RemoveMatchingBits(std::bitset<8>& a, std::bitset<8>& b)
{
const std::bitset<8> mask = (a ^ b);
a = _pext_u32(static_cast<uint32_t>(a.to_ulong()), static_cast<uint32_t>(mask.to_ulong()));
b = _pext_u32(static_cast<uint32_t>(b.to_ulong()), static_cast<uint32_t>(mask.to_ulong()));
}
请注意,鉴于需要处理std::bitset
对象,这进一步降低了生成的代码的效率。特别是,to_ulong()
成员函数必须在溢出的情况下检测并引发异常,并且 MSVC 似乎无法优化该签出,即使std::bitset<8>
不可能溢出 32 位整数类型也是如此。哦,好吧——代码会足够快,没有人说抽象是完全免费的。
如果假设支持 BMI2 无法编译,则可以使用CPUID
指令在运行时进行检查(几乎所有 x86 编译器都为此提供了内在函数)。
如果它不可用,则您不以 x86 为目标,或者如果您只是不想担心运行时委派的复杂性,则可以回退到替代的位抖动实现。具体来说,您想要的是"压缩"操作。对此的讨论和代码在小亨利·S·沃伦(Henry S. Warren, Jr.)的经典著作《黑客的喜悦》(Hacker's Delight)的第7-4节中给出。
下面是一个简单的、基于循环的"压缩"实现,改编自Hacker's Delight中的图 7-9:
uint32_t compress(uint32_t value, uint32_t mask)
{
uint32_t result = 0;
uint32_t shift = 0;
uint32_t maskBit;
do
{
maskBit = (mask & 1);
result |= ((value & maskBit) << shift);
shift += maskBit;
value >>= 1;
mask >>= 1;
} while (mask != 0);
return result;
}
这充分模拟了PEXT
指令,但速度并不快。以下代码实现了相同的算法,但使用基于黑客喜悦中的图 7–10 的更快的"并行后缀"方法:
uint32_t fallback_pext_u32(uint32_t value, uint32_t mask)
{
const int log2BitSize = 5; // log_2 of the bit size (here, 32 bits)
value &= mask; // clear irrelevant bits
uint32_t mk = (~mask << 1); // we will count 0's to the right
uint32_t mp;
uint32_t mv;
uint32_t t;
for (int i = 0; i < log2BitSize; ++i)
{
mp = mk ^ (mk << 1); // parallel suffix
mp = mp ^ (mp << 2);
mp = mp ^ (mp << 4);
mp = mp ^ (mp << 8);
mp = mp ^ (mp << 16);
mv = (mp & mask); // bits to move
mask = ((mask ^ mv) | (mv >> (1 << i))); // compress mask
t = (value & mv);
value = ((value ^ t) | (t >> (1 << i))); // compress value
mk &= ~mp;
}
return value;
}
此回退实现比单个PEXT
指令慢,但它是完全无分支的,因此在处理随机输入时不会对错误预测的分支进行任何隐藏的惩罚。您应该在这里从 CPU 获得最大可能的吞吐量,但无论哪种方式,它肯定比具有一系列条件分支的for
循环快得多,正如其他答案所建议的那样。
您可以使用boost::dynamic_bitset<>
作为结果,然后使用push_back
可以动态创建位集。
#include <iostream>
#include <boost/dynamic_bitset.hpp>
#include <bitset>
int main()
{
const int N = 8;
boost::dynamic_bitset<> a_out(0);
boost::dynamic_bitset<> b_out(0);
std::bitset<N>a(0x97); //10010111
std::bitset<N>b(0x72); //01110010
for (int i = 0; i < N; i++)
{
if (a[i] != b[i])
{
a_out.push_back(bool(a[i]));
b_out.push_back(bool(b[i]));
}
}
std::cout << a_out << "n";
std::cout << b_out << "n";
return 0;
}
试试这里!
输出:
10011
01100
[已编辑] 如果你想优化,你可以在for
循环之前添加它(但你必须有 boost 1.62 或更高版本才能使用reserve()
)
//@5gon12eder Optimization
const auto xorified = a ^ b;
const auto n = xorified.count();
a_out.reserve(n);
b_out.reserve(n);
在for
循环中,将位比较为:
if (xorified[i]) { ... }
编译时计算的所有内容
演示(需要 C++17)
这里的其他答案很棒,在一般情况下你应该更喜欢什么,因为你可能不知道最初的两个位集是什么。
但是,这并不好玩。对于您的特定示例,我们确实有足够的信息在编译时解决所有问题,并且通过使用constexpr if,可变参数模板,变量模板和整数序列*,我们可以在编译时执行所有计算和转换为字符串文字(用于初始化位集)。
A. 方法
- 将位集表示为整数序列
std::integer_sequence<int,1,0,0,1,0,1,1,1>
和std::integer_sequence<int,0,1,1,1,0,0,1,0>
- 根据您的逻辑过滤序列(删除相同位置的相同位)
- 请参阅我的另一个答案,了解如何执行此操作
- 将integer_sequences转换为字符序列
- 我的意思是
std::integer_sequence<char, ...>
- 我的意思是
- 使用变量模板将 char 序列转换为可用于构造
std::bitset
的以 null 结尾的字符串文本- 要创建的位集的大小可以通过
size()
成员函数从生成的std::integer_sequence<int, ...>
中获取:
- 要创建的位集的大小可以通过
完整代码:
#include <iostream>
#include <utility>
#include <bitset>
// sequence concatenation
template <typename INT, INT ...s, INT ...t>
constexpr auto
concat_sequence(std::integer_sequence<INT,s...>,std::integer_sequence<INT,t...>){
return std::integer_sequence<INT,s...,t...>{};
}
// base case; empty sequence
template<class INT, INT a, INT b>
constexpr auto Filter(std::integer_sequence<INT, a>, std::integer_sequence<INT, b>)
{
if constexpr (a == b)
return std::integer_sequence<INT>{};
else
return std::integer_sequence<INT,a>{};
}
template<class INT>
constexpr auto Filter(std::integer_sequence<INT>, std::integer_sequence<INT>)
{
return std::integer_sequence<INT>{};
}
// recursive case
template<class INT, INT a, INT... b, INT c, INT... d>
constexpr auto Filter(std::integer_sequence<INT, a, b...>, std::integer_sequence<INT, c, d...> )
{
static_assert(sizeof...(b) == sizeof...(d), "Sequences should initially be the same length");
return concat_sequence(Filter(std::integer_sequence<INT, a>{}, std::integer_sequence<INT, c>{}),
Filter(std::integer_sequence<INT, b...>{}, std::integer_sequence<INT, d...>{}));
}
// for constructing bitset/printing
template <char... s>
using char_sequence=std::integer_sequence<char,s...>;
template <char ...s>
constexpr static char const make_char_string[]={s... , ' '};
template <char ...s>
constexpr auto const & make_char_string_from_sequence(char_sequence<s...>){
return make_char_string<s...>;
}
template<class INT, INT digit>
constexpr auto make_binary_charseq()
{
static_assert(digit < 2, "binary digits are 0 and 1 only");
return char_sequence<digit == 1? '1' : '0'>{};
}
template <class INT, INT... elts>
struct convert_binary_to_charseq_impl;
template <class INT, INT n, INT ...rest>
constexpr auto convert_binary_to_charseq(std::integer_sequence<INT, n, rest...>){
return concat_sequence(make_binary_charseq<INT, n>(),
convert_binary_to_charseq_impl<INT, rest...>{}());
}
template <class INT, INT... elts>
struct convert_binary_to_charseq_impl{
constexpr auto operator()()const {
return convert_binary_to_charseq<INT, elts...>(std::integer_sequence<INT, elts...>{});
}
};
template <class INT>
struct convert_binary_to_charseq_impl<INT>{
constexpr auto operator()()const{
return char_sequence<>{};
}
};
和我们的测试:
int main()
{
using left_result = decltype(Filter(std::integer_sequence<int,1,0,0,1,0,1,1,1>{}, std::integer_sequence<int,0,1,1,1,0,0,1,0>{}));
using right_result = decltype(Filter(std::integer_sequence<int,0,1,1,1,0,0,1,0>{}, std::integer_sequence<int,1,0,0,1,0,1,1,1>{}));
static_assert(std::is_same_v<left_result, std::integer_sequence<int, 1,0,0,1,1>>, "Filtering did not work");
static_assert(std::is_same_v<right_result, std::integer_sequence<int, 0,1,1,0,0>>, "Filtering did not work");
std::bitset<left_result::size()> a(make_char_string_from_sequence(convert_binary_to_charseq(left_result{})));
std::bitset<right_result::size()> b(make_char_string_from_sequence(convert_binary_to_charseq(right_result{})));
std::cout << a << std::endl;
std::cout << b << std::endl;
}
输出:
10011
01100
这里的缺点是我有效地进行了两次计算,但我相信它可以重新设计(这一切都在编译时,所以我们不在乎,对吧!?
*功劳归功劳:Peter Sommerlad 的 CppCon2015 演讲对于将序列转换为字符串非常宝贵。幻灯片
您将需要编写自己的算法。 像这样的东西可能会起作用:
std::bitset<size> mask = a^b; //A zero will be put in place where a and b do match
int offset = 0;
std::bitset<size> fin(0); //This will hold the answer for the a bitset
for (int x = 0; x < size; x++)
{
if (!mask[x]) //If the bit is zero we are keeping the bit
{
if (a[x])
{
fin.set(offset);
}
offset++;
}
}
如果您使用的是 std::bitset,则可以先使用 XOR 运算符。 这将为您提供新的位集,在值相同的索引上填充 0,否则填充 1。 之后,您只需删除新位集具有 0 的索引。
您无法从std::bitset
中删除位,因此您的结果将具有额外的零。我的意思是结果而不是10011
将是00010011
constexpr int num = 8;
std::bitset<num> a("10010111");
std::bitset<num> b("01110010");
std::bitset<num> a_result;
std::bitset<num> b_result;
unsigned int last_index = 0;
for(auto index = 0; index < num; ++index)
{
if(a.test(index) ^ b.test(index))
{
a_result.set(last_index, a.test(index));
b_result.set(last_index, b.test(index));
++last_index;
}
}
或者您可以使用std::vector<bool>
作为结果,这是内部使用位集的bool
的专用std::vector
(实际上是定义的实现)。所有可能的解决方案都取决于您要实现的目标。
constexpr int num = 8;
std::bitset<num> a("10010111");
std::bitset<num> b("01110010");
std::vector<bool> a_result;
std::vector<bool> b_result;
for(auto index = 0; index < num; ++index)
{
if(a.test(index) ^ b.test(index))
{
a_result.push_back(a.test(index));
b_result.push_back(b.test(index));
}
}
您尝试使用此算法
void Procedure(void)
{
unsigned char NumA, NumB;
unsigned char ResA = 0, ResB = 0;
int Count1 = 0;
int Count2 = 8;
NumA = 0x97; // 10010111
NumB = 0x72; // 01110010
while( Count1 < 8 )
{
if( (NumA & 0x80) != (NumB & 0x80) )
{
ResA = ResA << 1;
if( (NumA & 0x80) == 0x80)
ResA = ResA | 0x01;
ResB = ResB << 1;
if( (NumB & 0x80) == 0x80)
ResB = ResB | 0x01;
--Count2;
}
NumA = NumA << 1;
NumB = NumB << 1;
++Count1;
}
ResA = ResA << Count2;
ResB = ResB << Count2;
}
结果存储在 ResA 和 ResB 变量中
这是我C++解决方案:
#include <iostream>
#include <bits/stdc++.h>
pair<int, int> extractMatchingBits(int a, int b) {
int cleanA = 0;
int cleanB = 0;
int matches = a^b;
for (int i = 0; matches != 0; i++) {
const int bitIdx = log2(matches & -matches);
cleanA |= ((a >> bitIdx) & 1) << i;
cleanB |= ((b >> bitIdx) & 1) << i;
matches &= matches - 1;
}
return make_pair(cleanA, cleanB);
}
你不能有类型bitset
的结果,因为你必须在编译时设置位集大小,而实际上你不知道有多少位位置是相等的。
- 使用CMake检测支持的C++标准
- 当套接字连接断开时检测C/C++Unix
- C/C++预处理器是否可以检测一些编译器选项
- WMI检测进程创建事件-c++
- 基于树莓pi的tensorflow lite量化ssd目标检测
- 下面是我为检测链接列表中的循环而制作的代码
- 落砂模拟碰撞检测C++和SFML
- 我可以检测和更改 gcc/g++ 中结构的当前数据对齐设置吗?
- 为什么C++编译器没有检测到正确声明的类?
- 检测win32服务创建和删除的最佳方法
- 正在LLVM中检测整数比较条件
- 如何在鼠标挂钩过程中检测拖动
- 位移操作和位掩码未检测到重复字符
- 检测 COFF 对象文件中C++内联符号
- qmake:检测目标位宽(32 位或 64 位)
- 增强精神解析器规则以检测语句中的特殊结尾
- 用于C++的静态二进制检测或二进制重写工具和框架
- Tensorflow对象检测在Python和C++(OpenCV)之间有不同的结果
- C++中的张量流对象检测
- 如何在OpenCV中检测这是谁的脸?