一次乘法提取位


301

我看到在使用了一个有趣的技术,答案另一个问题,并想好一点理解。

我们获得了一个无符号的64位整数,并且对以下位感兴趣:

1.......2.......3.......4.......5.......6.......7.......8.......

具体来说,我们希望将它们移动到前八位,如下所示:

12345678........................................................

我们不在乎由指示的位的值.,也不必保留它们。

溶液是屏蔽掉不需要的位,并且乘以结果0x2040810204081。事实证明,这可以解决问题。

这种方法有多普遍?可以使用这种技术来提取位的任何子集吗?如果不是,如何确定该方法是否适用于特定的一组位?

最后,如何找到正确的(a?)乘法器以提取给定的位?


29
如果发现有趣,请查看以下列表:graphics.stanford.edu/~seander/bithacks.html其中许多(ab)使用更宽整数的乘法/除法来获得有趣的结果。(“用4个操作反转字节中的位”部分显示了当您没有足够的空间并且需要两次屏蔽/乘法时如何处理位移位/乘法技巧)
viraptor 2013年

@viraptor:很好的一点。如果您了解此方法的局限性,则可以真正使用乘法来完成很多位操作。
Expedito

9
所以有趣的是存在AVX2(这可悲的是尚未公布),这不正是你所描述的操作的指令:software.intel.com/sites/products/documentation/studio/composer/...
JPvdMerwe

3
寻找巧妙的位旋转
Barmar,2013年

1
Um livro queconheçosobre o assunto(e gosto bastante)éo“ Hacker's Delight” 链接
Salles

Answers:


235

非常有趣的问题和巧妙的把戏。

让我们看一个简单的例子,对单个字节进行操作。为了简单起见,使用无符号8位。想象你的电话号码是xxaxxbxx你想要的ab000000

该解决方案包括两个步骤:位屏蔽,然后是乘法。位掩码是一个简单的AND操作,可将不感兴趣的位变为零。在上述情况下,您的蒙版将是00100100和结果00a00b00

现在最困难的部分是:将其转换为ab......

乘法是一堆移位和加法运算。关键是允许溢出“移走”我们不需要的位并将所需的位放在正确的位置。

乘以4(00000100)会使所有的数都左移2并达到极限a00b0000。为了b使a向上移动,我们需要乘以1(将a保持在正确的位置)+ 4(将b向上移动)。该总和为5,并与较早的4组合,得出的魔术数为20或00010100。原稿是00a00b00在掩盖后的。乘法给出:

000000a00b000000
00000000a00b0000 +
----------------
000000a0ab0b0000
xxxxxxxxab......

通过这种方法,您可以扩展到更大的数字和更多的位。

您提出的问题之一是“可以用任意数量的位来完成吗?” 我认为答案是“否”,除非您允许多次屏蔽操作或多次乘法。问题是“冲突”的问题-例如,上面问题中的“杂散b”。想象一下,我们需要对诸如此类的数字执行此操作xaxxbxxcx。按照较早的方法,您会认为我们需要{x 2,x {1 + 4 + 16}} = x 42(哦,这是所有问题的答案!)。结果:

00000000a00b00c00
000000a00b00c0000
0000a00b00c000000
-----------------
0000a0ababcbc0c00
xxxxxxxxabc......

如您所见,它仍然有效,但“仅”。他们的关键是在我们想要的位之间存在“足够的空间”,以便我们可以压缩所有内容。我不能在c之后添加第四个位d,因为我会得到c + d的实例,位可能会携带,...

因此,如果没有正式证据,我将回答您问题的更有趣的部分,如下所示:“不,这不适用于任何数量的位。要提取N位,您需要在位之间添加(N-1)个空格提取,或进行其他遮罩乘法步骤。”

对于“位之间必须有(N-1)个零”规则,我可以想到的唯一例外是:如果要提取原始位中彼此相邻的两个位,并且要将它们保留在同样的顺序,那么您仍然可以这样做。出于(N-1)规则的目的,它们算作两位。

还有另一种见解-受以下@Ternary答案的启发(请参阅此处的评论)。对于每个有趣的位,您只需要在其右边需要尽可能多的零即可,因为您需要为需要去的位提供空间。但是,它也需要左边的位数与结果位的左侧一样多。因此,如果位b结束于n的位置m,则它需要在其左侧具有m-1个零,而在其右侧具有nm 0。特别是当这些位在原始编号中的顺序与重新排序后的顺序不同时,这是对原始标准的重要改进。例如,这意味着一个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)规则:“如果位之间至少有(N-1)个空格,它将始终有效;或者,如果知道最终结果中的位顺序,则如果位b结束于位置m, n,它的左边必须有m-1个零,而右边则需要nm个零。”

@Ternary指出,此规则不太有效,因为可能会在“目标区域的右边”添加一些进位,即当我们要查找的位全部为1时。继续上面我给出的示例,在一个16位字中使用五个紧密包装的位:如果我们以

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)/(nm)规则。如果有一个以上的位在右侧具有“完全(nm)个未使用的位”(不计算模式的最后一位-在上面的示例中为“ c”),那么我们需要加强规则-我们必须如此反复!

我们不仅要查看满足(nm)标准的位数,而且还要查看处于(n-m + 1)n-m位的位数,等等。让我们将它们的数量Q0(精确到下一位)称为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
大。还有一个微妙的问题:由于进位,m-1 / nm测试有时会失败。尝试... ... b..c d -你有B + C风在第五位,该位,如果他们俩都是1,使进位即则会覆盖D(!)
三元

1
结果:n-1位空间禁止应配置的配置(即a.b..c ... d),而m-1 / nm允许不起作用的配置(a.b..c) ... d)。我还无法提出一种简单的方法来表征哪些将起作用,哪些将不起作用。
2013年

你很厉害!进位问题意味着我们需要在每个位的右边稍微留些空间作为“保护”。乍一看,如果至少有两个位的右边恰好具有最小的nm,则需要将空间增加1。更普遍的是,如果有P个这样的位,则需要将log2(P)个额外的位添加到右边。具有最小值(mn)的任何对象的权利。看起来合适吗?
弗洛里斯

好吧,最后一条评论太简单了。我认为我最近编辑的答案表明log2(P)是不正确的方法。@Ternary自己的答案(如下)优雅地显示了如果您没有有把握的解决方案,您如何才能知道特定的位组合-我相信上面的工作在此方面做了进一步的阐述。
Floris

1
这可能是一个巧合,但是当赞成投票的人数达到127时,这个答案就被接受了。如果您读了那么久,您将对我微笑...
Floris

154

确实,这是一个非常有趣的问题。我要花两分钱,就是说,如果您可以通过位向量理论上的一阶逻辑来说明这样的问题,那么定理证明就是您的朋友,并且可以为您提供快速的解决方案您问题的答案。让我们重新陈述定理所要求的问题:

“存在一些64位常量'mask'和'multiplicand',因此对于所有64位位向量x,在表达式y =(x&mask)*被乘数中,我们的y.63 == x.63 ,y.62 == x.55,y.61 == x.47,等等。”

如果该句子实际上是一个定理,则确实是常量“掩码”和“被乘数”的某些值满足此属性。因此,让我们用定理证明者可以理解的东西(即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
我很佩服!我不知道“位向量理论上的一阶逻辑”甚至不是人们研究的真实主题,更不用说它可以给出如此有趣的结果了。非常感谢分享。
弗洛里斯

@AndrewBacker:有人能向我阐明所谓的“按工作做”这件事有什么意义吗?我的意思是,它不支付任何费用。您不能仅靠SO代表生活。也许可以在面试中给您一些观点。也许。如果工作场所足够好,可以识别SO代表的价值,而这不是给定的……
恢复莫妮卡

3
当然。对于很多人来说,SO也是一个游戏(任何有分数的东西)。只是人性,就像在/ r / new中打猎一样,因此您可以发表第一个评论并获得因果报应。只要答案仍然是正确的,这没什么不好的。我更高兴能够在某人可能确实注意到某人确实做到的情况下,为某人的时间和精力做出支持。鼓励是一件好事:)而且...这是一个很老的评论,而且仍然是正确的。我不知道还不清楚。
安德鲁·贝克

88

乘法器中的每个1位用于将其中一个位复制到其正确位置:

  • 1已经在正确的位置,因此请乘以0x0000000000000001
  • 2必须向左移动7位,因此我们乘以0x0000000000000080(设置了位7)。
  • 3必须向左移动14位位置,因此我们乘以0x0000000000000400(设置了位14)。
  • 以此类推,直到
  • 8必须向左移动49位,所以我们乘以0x0002000000000000(设置了位49)。

乘数是各个位的乘数之和。

这之所以起作用,是因为要收集的位不太紧密,因此在我们的方案中不属于在一起的位的乘法要么超出64位,要么落入较低的无关位部分。

请注意,原始编号中的其他位必须为0。这可以通过用AND操作屏蔽它们来实现。


2
很好的解释!您的简短回答可以快速找到“幻数”的值。
Expedito

4
这确实是最好的答案,但是如果不先阅读(上半部分)@floris的答案,那将不会有太大帮助。
Andrew Backer

29

(我以前从未见过。这个技巧很棒!)

我将进一步说明弗洛里斯的断言,即在提取n位时,您需要n-1在任何非连续位之间留出空间:

我最初的想法(我们将在稍后看到这不太有效)是您可以做得更好:如果您要提取n位,那么i如果有人(非-与前面ii-1位或n-i后面的位连续)。

我将给出一些示例来说明:

...a..b...c...有效(没有人在之后的2位中a,在之前的位和在之后的位中b,没有人在之前的2位中c):

  a00b000c
+ 0b000c00
+ 00c00000
= abc.....

...a.b....c...之所以失败,b是因为它位于之后的2位a(并且在我们移动时被拉到别人的位置a):

  a0b0000c
+ 0b0000c0
+ 00c00000
= abX.....

...a...b.c...失败,因为b它位于前面的2位中c(并且在我们移动时被推到其他人的位置c):

  a000b0c0
+ 0b0c0000
+ b0c00000
= Xbc.....

...a...bc...d... 之所以有效,是因为连续的位会一起移位:

  a000bc000d
+ 0bc000d000
+ 000d000000
= abcd000000

但是我们有一个问题。如果使用n-i而不是使用,n-1可能会出现以下情况:如果在我们关心的零件之外发生碰撞,我们会在最后屏蔽掉某些东西,但其进位位最终会干扰重要的未屏蔽范围,该怎么办? ?(请注意:该n-1要求通过确保i-1在我们移去i第th个位时未屏蔽范围之后的位是清楚的,从而确保不会发生这种情况)

...a...b..c...d...进位位的潜在故障c位于n-1after 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位无符号)

当且仅当两者相等时,魔术移位/乘法才能提取我们的位。


我喜欢它-对,对于每个位,您只需要在其右边需要零个零就可以了。而且,它需要左边的位数与结果位的左侧一样多。所以,如果一个位b位置结束mn,那么它需要有m-1零到其左侧,n-m-1零到其右。特别是当这些位在原始编号中的顺序与重新排序后不同时,这是对原始标准的重要改进。这个很有趣。
弗洛里斯

13

除了这个非常有趣的问题的出色答案之外,了解这种逐位乘法技巧自2007年以来在计算机国际象棋社区中广为人知,它的名称为Magic BitBoards也可能是有用的。

许多计算机国际象棋引擎使用多个64位整数(称为位板)来表示各种样集(每个占用的正方形1位)。假设某个原点正方形上的滑动块(车子,主教,女王)可以移动到最多K正方形(如果没有阻挡块的话)。将这些分散的K位中的按位和与占用正方形的位板一起使用,可以得到K嵌入在64位整数中的特定位字。

魔术乘法可用于将这些分散的K位映射到K64位整数的低位。K然后,这些低位可用于索引预先计算的位板表,该表代表原始块上的棋子可以实际移动到的允许的正方形(请注意阻止块等)。

使用此方法的典型国际象棋引擎有2个表(一个用于菜鸟,一个用于主教,皇后使用两者的组合),其中包含这样的预先计算结果的64个条目(每个原点正方形一个)。收视率最高的封闭源代码(Houdini)和开放源代码象棋引擎(Stockfish)当前都使用这种方法,以实现极高的性能。

使用穷举搜索(使用早期截止时间进行优化)或尝试和错误(例如,尝试许多随机的64位整数)来找到这些魔术系数。在移动生成过程中,没有使用没有发现魔术常数的位模式。但是,当要映射的位具有(几乎)相邻索引时,通常需要按位进位效果。

AFAIK是@Syzygy所采用的非常通用的SAT求解器,尚未在计算机国际象棋中使用,而且似乎也没有关于这种魔术常数的存在和唯一性的正式理论。


我本以为拥有正式CS背景的人会在看到这个问题后直接跳入SAT方法。也许CS人觉得国际象棋没意思?:(
恢复莫妮卡

@KubaOber基本上是另一回事:电脑象棋是由那些用C或汇编语言编程的位棋手占主导地位,并且讨厌任何抽象(C ++,模板,OO)。我认为这吓跑了真正的CS专家:-)
TemplateRex
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.