我在answer到another question中看到了一种有趣的技巧,并且想要更好地理解它。
我们给出了一个无符号的64位整数,我们对以下几位感兴趣:
1.......2.......3.......4.......5.......6.......7.......8.......
具体来说,我们希望将它们移到前八位,如下所示:
12345678........................................................
我们不关心.
指示的位值,并且不必保留它们。
solution用于屏蔽不需要的位,并将结果乘以0x2040810204081
。事实证明,这就是诀窍。
这种方法有多普遍?这种技术可以用来提取任何比特子集吗?如果不是,那么如何判断该方法是否适用于特定的一组位?
最后,如何找到(a?)正确的乘数来提取给定的比特?
答案 0 :(得分:231)
非常有趣的问题和聪明的伎俩。
让我们看一个操作单个字节的简单示例。使用无符号8位简化。想象一下,您的号码为xxaxxbxx
,您想要ab000000
。
解决方案包括两个步骤:一个掩码,然后乘法。位掩码是一种简单的AND操作,可将无趣的位转换为零。在上述情况下,您的掩码为00100100
,结果为00a00b00
。
现在困难的部分:将其转变为ab......
。
乘法是一堆移位和加法运算。关键是允许溢出"转移"我们不需要的东西,把我们想要的东西放在正确的位置。
乘以4(00000100
)会将剩下的所有内容都移到2,然后转到a00b0000
。为了让b
向上移动,我们需要乘以1(以使a保持在正确的位置)+ 4(将b向上移动)。这个总和是5,并且与之前的4相加,我们得到一个幻数20或00010100
。掩蔽之后,原始是00a00b00
;乘法给出:
000000a00b000000
00000000a00b0000 +
----------------
000000a0ab0b0000
xxxxxxxxab......
通过这种方法,您可以扩展到更大的数字和更多的位。
你提出的一个问题是#34;这可以用任意数量的位完成吗?"我认为答案是" no",除非您允许多次屏蔽操作或多次乘法。问题是"碰撞" - 例如,"流浪b"在上面的问题。想象一下,我们需要对xaxxbxxcx
之类的数字执行此操作。按照早期的方法,你会认为我们需要{x 2,x {1 + 4 + 16}} = x 42(噢 - 一切的答案!)。结果:
00000000a00b00c00
000000a00b00c0000
0000a00b00c000000
-----------------
0000a0ababcbc0c00
xxxxxxxxabc......
正如您所看到的,它仍然有效,但仅仅是"。他们关键的是有足够的空间和#34;在我们想要的位之间,我们可以挤压一切。我无法在c之后添加第四位d,因为我会得到我得到c + d的实例,可能带有位,......
因此,如果没有正式的证据,我会回答你问题中更有趣的部分如下:"不,这对任意数量的位都不起作用。要提取N位,您需要在要提取的位之间留出(N-1)个空格,或者有额外的掩码乘法步骤。"
我能想到的唯一例外是"在比特之间必须有(N-1)个零"规则是这样的:如果你想提取原始中彼此相邻的两个位,并且你希望它们保持相同的顺序,那么你仍然可以这样做。并且出于(N-1)规则的目的,它们被计为两位。
还有另一种见解 - 受以下@Ternary答案的启发(见我在那里的评论)。对于每个有趣的位,您只需要在其右侧有多个零,因为您需要空间来存在需要的位。但是,它左边需要尽可能多的位,因为左边有结果位。因此,如果位b在n的位置m结束,则它需要在其左侧具有m-1个零,并且在其右侧具有n-m个零。特别是当比特在原始数字中的顺序与重新排序后的顺序不同时,这是对原始标准的重要改进。这意味着,例如,一个16位字
a...e.b...d..c..
可以转入
abcde...........
即使e和b之间只有一个空格,d和c之间只有两个空格,其他三个空格。无论N-1发生了什么?在这种情况下,a...e
成为"一个块" - 它们乘以1以最终在正确的位置,所以"我们免费得到e"。对于b和d也是如此(b需要右边三个空格,d需要左边相同的三个空格)。因此,当我们计算幻数时,我们发现存在重复:
a: << 0 ( x 1 )
b: << 5 ( x 32 )
c: << 11 ( x 2048 )
d: << 5 ( x 32 ) !! duplicate
e: << 0 ( x 1 ) !! duplicate
显然,如果您希望这些数字的顺序不同,则必须进一步区分它们。我们可以重新制定(N-1)
规则:&#34;如果位之间至少有(N-1)个空格,它将始终有效;或者,如果最终结果中的比特顺序是已知的,那么如果比特b在n的位置m结束,则其左边需要m-1个零,并且右边的n为零。&#34;
a...e.b...d..c..
为简单起见,我将位位置ABCDEFGHIJKLMNOP
我们要做的数学是
ABCDEFGHIJKLMNOP
a000e0b000d00c00
0b000d00c0000000
000d00c000000000
00c0000000000000 +
----------------
abcded(b+c)0c0d00c00
到目前为止,我们认为低于abcde
(位置ABCDE
)的任何内容都无关紧要,但事实上,正如@Ternary指出的那样,b=1, c=1, d=1
然后是(b+c)
位置G
会导致位置F
,这意味着位置(d+1)
中的F
会进入E
- 我们的结果是被宠坏的。请注意,感兴趣的最低有效位(此示例中为c
)右侧的空格并不重要,因为乘法将导致填充零,从最低有效位开始。
所以我们需要修改我们的(m-1)/(n-m)规则。如果有多个位具有&#34;正好(nm)未使用的位(不计算模式中的最后一位 - &#34; c&#34;在上例中),那么我们需要加强规则 - 我们必须反复这样做!
我们不仅需要查看符合(nm)标准的位数,还要查看位于(n-m + 1)的位数等。让我们调用它们的数字Q0(确切地说n-m
到下一位),Q1(n-m + 1),直到Q(N-1)(n-1)。那么我们冒险携带
Q0 > 1
Q0 == 1 && Q1 >= 2
Q0 == 0 && Q1 >= 4
Q0 == 1 && Q1 > 1 && Q2 >=2
...
如果你看一下,你可以看到,如果你写一个简单的数学表达式
W = N * Q0 + (N - 1) * Q1 + ... + Q(N-1)
,结果为W > 2 * N
,那么您需要将RHS标准增加一位到(n-m+1)
。此时,只要W < 4
,操作就是安全的;如果这不起作用,再增加一个标准,等等。
我认为按照上述步骤可以帮助您解决问题......
答案 1 :(得分:151)
确实非常有趣的问题。我正在用我的两分钱,这就是说,如果你可以通过比特向量理论的一阶逻辑设法说明这样的问题,那么定理证明是你的朋友,并且可以为你提供非常快的速度回答你的问题。让我们重新陈述被问为一个定理的问题:
“存在一些64位常量'掩码'和'被乘数',这样,对于所有64位位向量x,在表达式y =(x&amp; mask)*被乘数中,我们得到y.63 = = x.63,y.62 == x.55,y.61 == x.47等。“
如果这个句子实际上是一个定理,那么常量'mask'和'multiplicand'的某些值确实满足这个属性。所以让我们用一个定理证明者可以理解的东西来表达这个,即SMT-LIB 2输入:
(set-logic BV)
(declare-const mask (_ BitVec 64))
(declare-const multiplicand (_ BitVec 64))
(assert
(forall ((x (_ BitVec 64)))
(let ((y (bvmul (bvand mask x) multiplicand)))
(and
(= ((_ extract 63 63) x) ((_ extract 63 63) y))
(= ((_ extract 55 55) x) ((_ extract 62 62) y))
(= ((_ extract 47 47) x) ((_ extract 61 61) y))
(= ((_ extract 39 39) x) ((_ extract 60 60) y))
(= ((_ extract 31 31) x) ((_ extract 59 59) y))
(= ((_ extract 23 23) x) ((_ extract 58 58) y))
(= ((_ extract 15 15) x) ((_ extract 57 57) y))
(= ((_ extract 7 7) x) ((_ extract 56 56) y))
)
)
)
)
(check-sat)
(get-model)
现在让我们问定理证明者Z3这是否是一个定理:
z3.exe /m /smt2 ExtractBitsThroughAndWithMultiplication.smt2
结果是:
sat
(model
(define-fun mask () (_ BitVec 64)
#x8080808080808080)
(define-fun multiplicand () (_ BitVec 64)
#x0002040810204081)
)
宾果!它以0.06秒的速度再现原始帖子中给出的结果。
从更一般的角度来看,我们可以将其视为一阶程序综合问题的一个实例,这是一个新兴的研究领域,很少有论文发表。搜索"program synthesis" filetype:pdf
可以帮助您入门。
答案 2 :(得分:85)
乘法器中的每1位用于将其中一个位复制到正确的位置:
1
已经处于正确位置,因此乘以0x0000000000000001
。2
必须向左移动7位,因此我们乘以0x0000000000000080
(设置第7位)。3
必须向左移动14位,因此我们乘以0x0000000000000400
(设置位14)。8
必须向左移动49位,因此我们乘以0x0002000000000000
(设置位49)。乘数是各个位的乘数之和。
这只能起作用,因为要收集的位不是太靠近,因此在我们的方案中不属于一起的位的乘法要么超出64位,要么落在较低的无关注部分。 / p>
请注意,原始号码中的其他位必须为0
。这可以通过使用AND操作屏蔽它们来实现。
答案 3 :(得分:29)
(我之前从未见过。这个技巧很棒!)
我将对Floris的断言进行一些扩展,即在提取n
位时,任何非连续位之间需要n-1
空格:
我最初的想法(我们会在一分钟内看到这不起作用)是你可以做得更好:如果你想提取n
位,你提取时会发生碰撞/如果您之前的i
位或之后的i
位中有任何人(非连续的位i-1
),则移位位n-i
。
我将举几个例子来说明:
...a..b...c...
有效(a
之后的2位,前一位和b
之后的位,没有人在c
之前的2位中:
a00b000c
+ 0b000c00
+ 00c00000
= abc.....
...a.b....c...
失败,因为b
位于a
之后的2位(当我们转移a
时会被拉入别人的位置):
a0b0000c
+ 0b0000c0
+ 00c00000
= abX.....
...a...b.c...
失败,因为b
位于c
之前的2位(当我们转移c
时会被推入别人的位置):
a000b0c0
+ 0b0c0000
+ b0c00000
= Xbc.....
...a...bc...d...
可以工作,因为连续的位一起移动:
a000bc000d
+ 0bc000d000
+ 000d000000
= abcd000000
但我们遇到了问题。如果我们使用n-i
代替n-1
,我们可能会遇到以下情况:如果我们在部件之外发生碰撞会怎么样?关心,最后我们会屏蔽掉的东西,但是其携带位最终会干扰重要的未屏蔽范围? (并注意:n-1
要求确保在我们移位i-1
位时确保未屏蔽范围之后的i
位清零时不会发生这种情况。 / p>
...a...b..c...d...
进位位潜在失败,c
位于n-1
之后b
,但满足n-i
条件:
a000b00c000d
+ 0b00c000d000
+ 00c000d00000
+ 000d00000000
= abcdX.......
那么为什么我们不回到那个“n-1
位空间”要求呢?
因为我们可以做得更好:
...a....b..c...d..
失败“n-1
位空间”测试,但工作用于我们的位提取技巧:
+ a0000b00c000d00
+ 0b00c000d000000
+ 00c000d00000000
+ 000d00000000000
= abcd...0X......
我无法想出一个很好的方法来表征这些字段不在重要位之间有n-1
空格,但仍然可以用于我们的操作。但是,由于我们提前知道我们感兴趣的哪些位,我们可以检查我们的过滤器以确保我们不会遇到进位冲突:
将(-1 AND mask) * shift
与预期的全结果进行比较,-1 << (64-n)
(对于64位无符号)
当且仅当两者相等时,魔术移位/乘法才能提取我们的位。
答案 4 :(得分:12)
除了对这个非常有趣的问题已经很好的答案之外,知道自2007年以来计算机国际象棋界已经知道这种按位乘法技巧可能是有用的,其名称为Magic BitBoards 。
许多计算机国际象棋引擎使用多个64位整数(称为位板)来表示各种棋子组(每个占用方块1位)。假设如果没有阻挡件,某个原点上的滑块(车,主教,女王)可以移动到最多K
个方块。使用bitwise-和那些分散的K
位和占用方块的位板,可以在64位整数中嵌入一个特定的K
位字。
Magic multiplication可用于将这些分散的K
位映射到64位整数的低K
位。然后可以使用这些较低的K
位来索引预先计算的位板表,该位表示其原点上的块可以实际移动到的允许的方块(处理阻塞件等)
使用这种方法的典型国际象棋引擎有两个表(一个用于车,一个用于主教,两个使用两者的组合),64个条目(每个原点方格一个)包含这样的预先计算结果。最高评级的闭源(Houdini)和开源象棋引擎(Stockfish)目前都使用这种方法,因为它具有非常高的性能。
使用exhaustive search(使用早期截止优化)或使用trial and erorr(例如,尝试大量随机64位整数)来查找这些魔术乘数。在移动生成期间没有使用位模式,其中没有找到魔法常数。然而,当待映射比特具有(几乎)相邻索引时,通常需要逐位进位效应。
AFAIK是@Syzygy非常普遍的SAT求解器,并没有被用于计算机国际象棋,也似乎没有关于这种魔法常数的存在性和唯一性的任何形式理论。