C++ 位摆弄:仅当int';是的

C++ 位摆弄:仅当int';是的,c++,bit-manipulation,C++,Bit Manipulation,我正在处理一个代码库,该代码库使用以下编码来表示替换采样:我们维护一个int数组,作为样本中位置和存在的指示器,其中正int表示另一个数组中的位置,负int表示我们不应该在该迭代中使用数据点 例如: data_points: [...] // Objects vector of length 5 == position.size() std::vector<int> position: [3, 4, -3, 1, -2] position[0] = 1 position[0] =

我正在处理一个代码库,该代码库使用以下编码来表示替换采样:我们维护一个int数组,作为样本中位置和存在的指示器,其中正int表示另一个数组中的位置,负int表示我们不应该在该迭代中使用数据点

例如:

data_points: [...] // Objects vector of length 5 == position.size()
std::vector<int> position: [3, 4, -3, 1, -2]
position[0] = 1
position[0] = ~position[0] // Now position[0] is -2
//  So if we did
position[0] = std::abs(position[0]) // This is wrong, position[0] is now 2!
// If we want to reset it to 1, we need to do another complement
position[0] = ~position[0] // Now position[0] is again 1
这会将位置向量更改为

    std::vector<int> position: [-4, 4, -3, 1, 1]

可能是第一个去源代码的位旋转黑客:

然而,我会质疑
position[I]=std::abs(position[I])
性能较差的假设。在签入此类代码之前,您肯定应该有分析结果来证明bit hack的优越性

您可以随意使用这两种产品的快速基准测试(拆卸),我不认为速度有什么不同:

还可以查看实际生成的程序集:

显然,clang看到了你的bithack,并没有留下深刻印象-它在所有情况下都会生成一个有条件的移动(无分支)gcc按您所说的做,但是还有两个
abs
的实现在存储中,一些实现利用了目标体系结构的寄存器语义

如果你进入(自动)矢量化,事情就会变得复杂。不管怎样,你都必须进行个人简介



结论:只需编写
std::abs
——您的编译器将为您完成所有的位处理。

我建议不要尝试位处理。部分原因是你处理的是有符号的数字,如果你胡乱摆弄,你会丢掉便携性。部分原因是位篡改不如可重用函数可读

一个简单的解决方案:

std::for_each(position.begin(), position.end(), [](int v) {
    return std::abs(v);
});

为什么/如何从有符号正整数的位补码中得到它的数学补码?(即符号翻转时的相同值)

没有。反正也不是一般的。它只在对负数使用1的补码表示法的系统上这样做,原因很简单,因为这就是表示法的指定方式。负数由正数的二进制补码表示


现在最常用的表示法是2的补码,但它不是这样工作的。

使用函数来表示意图。允许编译器的Optimizer比您以前做得更好

#include <cmath>

void include_index(int& val)
{
    val = std::abs(val);
}

void exclude_index(int& val)
{
    val = -std::abs(val);
}

bool is_included(int const& val)
{
    return val > 0;
}
“有没有一个小把戏,可以让我在没有if条件的情况下完成这项工作?”

是否需要将正在更改的数字的数值保持为负值

如果没有,可以使用
std::max
将负值设置为零

iValue = std::max(iValue, 0); // returns zero if iValue is less than zero
如果您需要保留数值,但只是从负值更改为正值,则

iValue = std::abs(iValue); // always returns a positive value of iValue

回答扩展问题:同样,首先编写直观明了的代码,然后检查编译器是否正确:

如果你让它有它的乐趣与自动矢量化,那么你可能不会理解(或善于判断)装配,所以你必须轮廓无论如何。具体例子: . clang也喜欢展开自动矢量化循环,而gcc则更为保守。无论如何,它仍然是免费的

编译器可能比您更了解目标平台上指令调度的细节。如果你不用一点魔法来掩盖你的意图,它本身就会做得很好。如果它仍然是代码中的一个热点,那么您可以去看看是否可以手工制作一个更好的版本(但可能必须在汇编中)

另外,因为我不太会像这样做,为什么/如何从有符号的正整数的位补码中得到它的数学补码?(即符号翻转时的相同值)

这个问题本身就值得一个答案,因为每个人都会告诉你这是你怎么做的,但没有人告诉你为什么

请注意
1-0=1
1-1=0
。这意味着,如果我们做
1-b
,其中
b
是一个位,结果与
b
相反,或者
不是b
~b
)。还要注意这个减法是如何永远不会产生借阅的,这一点非常重要,因为
b
最多只能是
1

还要注意,用
n
位减去一个数字意味着在处理借词时执行
n
1位减法。但我们的特殊情况永远不会导致借款

基本上,我们已经为按位not操作创建了一个数学定义。要翻转位
b
,请执行
1-b
。如果我们想翻转一个
n
位号,请对每一位执行此操作。但是按顺序进行
n
减法与减去两个
n
位数字是一样的。因此,如果我们想计算8位数字的按位not,
a
,我们只需计算
11111111-a
,对于任何
n
位数字也是如此。这同样有效,因为从
1
中减去一位将永远不会产生借用

但是
n
1
”位的顺序是什么?它是值
2^n-1
。因此,取一个数字的按位not,
a
,等于计算
2^n-1-a

现在,计算机中的数字存储为模为
2^n
的数字。这是因为我们只有有限的可用位。您可能知道,如果您使用8位并执行
255+1
,您将得到
0
。这是因为8位数字是以
2^8=256
255+1=256
为模的数字
256
显然等于
0
256

但是为什么不向后做同样的事情呢?按照这个逻辑,
0-1=255
,对吗?这的确是正确的。数学
include_index(int&):
  mov eax, DWORD PTR [rdi]
  sar eax, 31
  xor DWORD PTR [rdi], eax
  sub DWORD PTR [rdi], eax
  ret
exclude_index(int&):
  mov eax, DWORD PTR [rdi]
  mov edx, DWORD PTR [rdi]
  sar eax, 31
  xor edx, eax
  sub eax, edx
  mov DWORD PTR [rdi], eax
  ret
is_included(int const&):
  mov eax, DWORD PTR [rdi]
  test eax, eax
  setg al
  ret
iValue = std::max(iValue, 0); // returns zero if iValue is less than zero
iValue = std::abs(iValue); // always returns a positive value of iValue