【发布时间】:2017-02-19 15:59:54
【问题描述】:
我最近找到了that SO question。
接受的答案和大黄提出的答案工作正常,但我不明白它们是如何工作的。而且我不想在我的项目中使用我不理解的代码。我知道基本的位操作是什么(移位、AND、OR 等),但我不明白这些操作组合最终是如何完成它们正在做的事情的。
感谢您查看此问题并希望能够帮助我。
【问题讨论】:
标签: c++ bit-manipulation
我最近找到了that SO question。
接受的答案和大黄提出的答案工作正常,但我不明白它们是如何工作的。而且我不想在我的项目中使用我不理解的代码。我知道基本的位操作是什么(移位、AND、OR 等),但我不明白这些操作组合最终是如何完成它们正在做的事情的。
感谢您查看此问题并希望能够帮助我。
【问题讨论】:
标签: c++ bit-manipulation
64 位整数 value 表示为一个 8×8 块 - 假设我们理解每个单元格的“内容”如下:
1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64
虽然value实际上是按顺序存储的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ...
我们还说将其向左移动四位 (value << 4) 会导致
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
或
5 6 7 8 9 10 11 12
13 14 15 16 17 18 19 20 ...
并将其向右移动四 (value >> 4) 给出
0 0 0 0 1 2 3 4
5 6 7 8 9 10 11 12 ...
现在开始
uint64 reflect_vert (uint64 value)
{
value = ((value & 0xFFFFFFFF00000000ull) >> 32) | ((value & 0x00000000FFFFFFFFull) << 32);
value = ((value & 0xFFFF0000FFFF0000ull) >> 16) | ((value & 0x0000FFFF0000FFFFull) << 16);
value = ((value & 0xFF00FF00FF00FF00ull) >> 8) | ((value & 0x00FF00FF00FF00FFull) << 8);
return value;
}
这里,0xFFFFFFFF00000000ull-like 片段是位掩码,结合 AND 操作,从value 中选择位。
另请注意,0xFF 对应于设置了八位的一个字节,因此0xFFFFFFFF 有效地描述了4*8=32 选定的位。由于每行的长度为8 位,因此对应于4 行。
具体来说,value & 0xFFFFFFFF00000000ull 选择(保留!)value 的高 32 位,即前四行,并丢弃其余行,而 value & 0x00000000FFFFFFFFull 选择低 32 位并丢弃第一行。 (它实际上并没有丢弃任何东西,而是将那些不匹配的元素/位置的值设置为零。)
中的班次操作
((value & 0xFFFFFFFF00000000ull) >> 32)
((value & 0x00000000FFFFFFFFull) << 32)
然后将这些位向下 (>> 32) 移动到低 32 位或向上 (<< 32) 的位置。通过 OR-ing 将它们组合在一起,
value = ((value & 0xFFFFFFFF00000000ull) >> 32) | ((value & 0x00000000FFFFFFFFull) << 32);
你已经有效地交换了它们。现在,由于低 32 位对应于块的“下半部分”,我们只需像这样交换行:
33 34 35 36 37 38 39 40 \
41 42 43 44 45 46 47 48 |__
49 50 51 52 53 54 55 56 | |
57 58 59 60 61 62 63 64 / |
1 2 3 4 5 6 7 8 \ |
9 10 11 12 13 14 15 16 |__|
17 18 19 20 21 22 23 24 |
25 26 27 28 29 30 31 32 /
对0xFFFF0000FFFF0000ull 和0x0000FFFF0000FFFFull 执行相同的操作,使用宽度移位16 将两行与它们的相邻行交换:
49 50 51 52 53 54 55 56 \__
57 58 59 60 61 62 63 64 / |
33 34 35 36 37 38 39 40 \__|
41 42 43 44 45 46 47 48 /
17 18 19 20 21 22 23 24 \__
25 26 27 28 29 30 31 32 / |
1 2 3 4 5 6 7 8 \__|
9 10 11 12 13 14 15 16 /
最后,0xFF00FF00FF00FF00ull 和 0x00FF00FF00FF00FFull 8 每隔一行交换一次,结果是
57 58 59 60 61 62 63 64 _
49 50 51 52 53 54 55 56
41 42 43 44 45 46 47 48 _
33 34 35 36 37 38 39 40
25 26 27 28 29 30 31 32 _
17 18 19 20 21 22 23 24
9 10 11 12 13 14 15 16 _
1 2 3 4 5 6 7 8
此时方块已成功垂直翻转。
reflect_diag 方法使用相同的方法选择性地交换位。这里要注意的是0x0100000000000000 选择了最高八位(顶行,中间左侧):
0000 0001 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000
而0x0000000000000080 选择最低八位(底行,中间右侧)
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 1000 0000
位。它们正好相隔49 位,因此将它们移动49 会交换它们的位置。
再举个例子,0x4020100804020100 模式选择位
0100 0000 0010 0000
0001 0000 0000 1000
0000 0100 0000 0010
0000 0001 0000 0000
而其对应的 0x0080402010080402 选择
0000 0000 1000 0000
0100 0000 0010 0000
0001 0000 0000 1000
0000 0100 0000 0010
您会注意到位之间的距离形成了一种模式,该模式允许整个块以与彼此的原始位置对齐的方式移动。
另请注意,与水平和垂直翻转的版本相比,此代码不会覆盖原始值,而是组成新的输出。 Michiel 的代码进行就地移位并将移位编码为八进制,所以>> 010 实际上意味着>> 8,020 是16 等等。
【讨论】: