단일 곱셈으로 비트 추출


301

다른 질문 에 대한 답변 에 흥미로운 기술이 사용되는 것을 보았고 조금 더 이해하고 싶습니다.

부호없는 64 비트 정수가 제공되며 다음 비트에 관심이 있습니다.

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

구체적으로 다음과 같이 상위 8 개 위치로 이동하려고합니다.

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

로 표시된 비트의 값에 신경 .쓰지 않으며 보존 할 필요가 없습니다.

용액 불필요한 비트를 마스크하고 의한 결과를 곱 하였다 0x2040810204081. 이것은 밝혀 졌 듯이 트릭을 수행합니다.

이 방법은 얼마나 일반적입니까? 이 기술을 사용하여 비트의 하위 집합을 추출 할 수 있습니까? 그렇지 않다면, 방법이 특정 비트 세트에 대해 작동하는지 여부를 어떻게 알 수 있습니까?

마지막으로 주어진 비트를 추출하기 위해 (a?) 올바른 승수를 찾는 방법은 무엇입니까?


29
그 중 하나가 흥미로운 것을 발견했다면 graphics.stanford.edu/~seander/bithacks.html 이 목록을 살펴보십시오. 많은 것들이 (ab) 더 넓은 정수 곱셈 / 나눗셈을 사용하여 흥미로운 결과를 얻습니다. ( "공간이 충분하지 않고 두 번 마스크 / 곱해야 할 때 비트 시프트 / 곱셈 트릭을 다루는 방법"부분은 "4 개의 연산으로 바이트에서 비트 반전"부분)
viraptor

@viraptor : 훌륭한 지적입니다. 이 방법의 한계를 이해하면 비트 연산과 관련하여 곱셈을 사용하여 많은 것을 달성 할 수 있습니다.
Expedito

9
흥미롭게도 AVX2에는 슬프게도 아직 사용할 수없는 명령이 있습니다. software.intel.com/sites/products/documentation/studio/composer/…
JPvdMerwe

3
현명한 비트 트위들 링
Barmar

1
Um livro que conheço sobre o assunto (e gosto bastante) é o "Hacker 's Delight" 링크
Salles

답변:


235

매우 흥미로운 질문과 영리한 속임수.

단일 바이트를 조작하는 간단한 예를 살펴 보겠습니다. 단순화를 위해 부호없는 8 비트 사용 당신의 숫자가 xxaxxbxx당신 이 원한다고 상상해보십시오 ab000000.

이 솔루션은 비트 마스킹과 곱셈의 두 단계로 구성되었습니다. 비트 마스크는 흥미롭지 않은 비트를 0으로 바꾸는 간단한 AND 연산입니다. 위의 경우 마스크가 00100100되고 결과가 00a00b00됩니다.

이제 어려운 부분은로 바꾸는 것입니다 ab.......

곱셈은 ​​시프트 및 덧셈 연산입니다. 핵심은 오버플로가 필요없는 비트를 "이동"하고 원하는 비트를 올바른 위치에 배치하는 것입니다.

4 ( 00000100)를 곱 하면 모든 것이 2 씩 왼쪽으로 이동하여 이동합니다 a00b0000. b위로 이동 하려면 1 (a를 올바른 위치에 유지) + 4 (b를 위로 이동)를 곱해야합니다. 이 합계는 5이며 이전 4와 결합하면 매직 넘버 20 또는 00010100입니다. 원본은 00a00b00마스킹 후 였습니다 . 곱셈은 ​​다음을 제공합니다.

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

이 방법을 사용하면 더 많은 수와 더 많은 비트로 확장 할 수 있습니다.

당신이 물었던 질문 중 하나는 "이것은 몇 비트라도 가능합니까?" 여러 마스킹 작업이나 곱셈을 허용하지 않는 한 대답은 "아니오"라고 생각합니다. 문제는 "충돌"문제입니다 (예 : 위 문제의 "stray b"). 우리가 이것을 같은 숫자로해야한다고 상상해보십시오 xaxxbxxcx. 이전의 접근 방식에 따르면 {x 2, x {1 + 4 + 16}} = x 42 (oooh-모든 것에 대한 답변)가 필요하다고 생각할 것입니다. 결과:

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

보시다시피 여전히 작동하지만 "단지"입니다. 여기서 핵심은 모든 비트를 압축 할 수있는 비트 사이에 "충분한 공간"이 있다는 것입니다. 나는 c + d를 얻는 인스턴스를 얻을 수 있기 때문에 c 바로 뒤에 네 번째 비트 d를 추가 할 수 없었습니다.

따라서 공식적인 증거 없이는 다음과 같이 질문의 더 흥미로운 부분에 대답 할 것입니다. "아니오, 이것은 여러 비트에 대해 작동하지 않습니다. N 비트를 추출하려면 원하는 비트 사이에 (N-1) 개의 공백이 필요합니다. 추출하거나 추가 마스크 곱셈 단계를 수행하십시오. "

"비트 사이에 (N-1) 0이 있어야 함"규칙에 대해 생각할 수있는 유일한 예외는 다음과 같습니다. 원본에서 서로 인접한 두 개의 비트를 추출하려면 같은 순서로해도 여전히 할 수 있습니다. 그리고 (N-1) 규칙의 목적을 위해 두 비트로 계산됩니다.

아래 @Ternary의 답변에서 영감을 얻은 또 다른 통찰력이 있습니다 (내 의견 참조). 흥미로운 비트마다 거기에 가야하는 비트를위한 공간이 필요한만큼 오른쪽에 0 만 있으면됩니다. 또한 왼쪽에 결과 비트만큼 왼쪽에 많은 비트가 필요합니다. 따라서 비트 b가 n의 위치 m에서 끝나는 경우 왼쪽에 m-1 0이 있고 오른쪽에 nm 0이 있어야합니다. 특히 비트의 순서가 원래 순서와 순서가 다르면 원래 순서와 비교하여 중요한 개선 사항입니다. 예를 들어 16 비트 워드

a...e.b...d..c..

로 이동할 수 있습니다

abcde...........

비록 e와 b 사이에 하나의 공간, d와 c 사이에 2 개, 다른 것 사이에 3 개의 공간이 있지만 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가 0이어야합니다. "

@Ternary는 "목표 영역의 오른쪽에"비트를 추가 할 수있는 비트, 즉 우리가 찾고있는 비트가 모두 비트 일 때이 규칙이 제대로 작동하지 않는다고 지적했습니다. 16 비트 워드에서 5 개의 꽉 채워진 비트로 위에서 설명한 예를 계속하십시오.

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곱셈으로 인해 가장 중요하지 않은 비트에서 0으로 채워지는 패딩이 발생하기 때문에 가장 중요하지 않은 관심 비트 ( 이 예제에서) 오른쪽의 공간 은 중요하지 않습니다.

따라서 (m-1) / (nm) 규칙을 수정해야합니다. "정확하게 (nm) 사용되지 않은 비트가 오른쪽에있는 비트가 두 개 이상인 경우 (위의 예에서 패턴의 마지막 비트는 계산하지 않음-"c ") 규칙을 강화해야합니다. 반복적으로 그렇게하십시오!

우리는 (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 * NRHS 기준을 1 비트 증가시켜야합니다 (n-m+1). 이 시점에서 작업은 안전합니다 W < 4. 그래도 문제가 해결되지 않으면 기준을 한 번 더 늘리십시오.

위의 내용을 따르면 대답에 먼 길을 갈 것이라고 생각합니다 ...


1
큰. 또 하나의 미묘한 문제 : m-1 / nm 테스트는 캐리 비트로 인해 어느 정도 실패합니다. a ... b..c ... d-다섯 번째 비트에서 b + c로 바람을 일으킨다. 둘 다라면 1이 클로버 인 캐리 비트를 만드는 d (!)
Ternary

1
결과 : n-1 비트의 공간은 작동해야하는 구성을 금지하고 (즉, ... b..c ... d) m-1 / nm는 작동하지 않는 구성을 허용합니다 (a ... b..c ...디). 나는 어떤 것이 효과가 있고 어떤 것이 효과가 없는지를 특징 짓는 간단한 방법을 생각 해낼 수 없었다.
Ternary

잘 지내! 캐리 문제는 각 비트 오른쪽에 "보호"로 약간 더 많은 공간이 필요하다는 것을 의미합니다. 첫눈에, 오른쪽에 정확히 최소 nm의 비트가 2 개 이상 있으면 공간을 1 씩 늘려야합니다. 일반적으로 P와 같은 비트가 있으면 log2 (P) 추가 비트가 필요합니다. 최소값 (mn)을 가진 사람의 권리. 당신에게 맞는 것 같습니까?
Floris

마지막 의견은 너무 단순했습니다. 가장 최근에 편집 한 답변에 따르면 log2 (P)가 올바른 접근법이 아니라고 생각합니다. @Ternary의 답변 (아래)은 보장 된 솔루션이없는 경우 특정 비트 조합에 대해 말할 수있는 방법을 우아하게 보여줍니다. 위의 작업은 더 자세히 설명합니다.
Floris

1
우연의 일치 일지 모르지만,이 답변은 공감 율이 127에 도달했을 때 받아 들여졌습니다.이 글을 읽었다면 나와 함께 웃을 것입니다 ...
Floris

154

실제로 매우 흥미로운 질문입니다. 비트 센트 이론에 대한 1 차 논리 측면에서 이와 같은 문제를 관리 할 수 ​​있다면 정리 프로 바이더가 친구이며 잠재적으로 매우 빠른 것을 제공 할 수 있다는 것입니다. 당신의 질문에 대한 답변. 정리로 요구되는 문제를 다시 언급하자 :

"64 비트 상수 'mask'및 'multiplicand'가 있으므로 모든 64 비트 비트 벡터 x의 경우 y = (x & mask) * multiplicand 식에서 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 초 안에 재현합니다.

이것을 좀 더 일반적인 관점에서 보면, 우리는 이것을 1 차 프로그램 합성 문제의 실례로 볼 수 있는데, 이것은 논문이 거의 출판되지 않은 초기 연구 분야입니다. 검색을 "program synthesis" filetype:pdf시작해야합니다.


2
내가 감동! 나는 "비트 벡터 이론에 대한 1 차 논리"가 사람들이 연구 한 실제 주제라는 것을 몰랐습니다. 이것을 공유해 주셔서 감사합니다.
Floris

@AndrewBacker :이 소위 "작업으로"일에서 누군가가 어떤 점에 대해 나를 밝힐 수 있습니까? 내 말은, 아무것도 지불 하지 않습니다 . SO 담당자만으로는 살 수 없습니다. 인터뷰에서 몇 가지 요점을 알려줄 수도 있습니다. 아마도. 만약 직장이 SO 담당자의 가치를 인정할만큼 충분하고 그것이 주어진 것이 아니라면 ...
Reinstate Monica

3
확실한. SO는 또한 많은 사람들을위한 게임입니다. / r / new에서 사냥하는 것과 같은 인간의 본성 때문에 첫 번째 코멘트를 게시하고 업장을 얻을 수 있습니다. 대답이 여전히 좋은 한 그것에 대해 나쁜 것은 없습니다. 누군가가 실제로 그 사실을 알아 차렸을 때 누군가의 시간과 노력을 쏟아 부을 수있어서 더 행복합니다. 격려는 좋은 물건입니다 :) 그리고 ... 그것은 정말 오래된 의견이었고 여전히 그렇습니다. 어떻게 명확하지 않은지 모르겠습니다.
Andrew Backer

88

승수의 모든 1 비트는 비트 중 하나를 올바른 위치에 복사하는 데 사용됩니다.

  • 1이미 올바른 위치에 있으므로을 곱하십시오 0x0000000000000001.
  • 27 비트 위치를 왼쪽으로 이동해야하므로 0x0000000000000080(비트 7이 설정 됨)을 곱합니다 .
  • 314 비트 위치를 왼쪽으로 이동해야하므로 0x0000000000000400(비트 14가 설정 됨)을 곱하십시오 .
  • 그리고까지
  • 8왼쪽으로 49 비트 위치를 이동해야하므로 0x0002000000000000(비트 49가 설정 됨)을 곱합니다 .

승수는 개별 비트에 대한 승수의 합입니다.

이것은 수집되는 비트가 너무 가깝지 않기 때문에 작동합니다. 따라서 우리 체계에서 함께 속하지 않는 비트의 곱셈은 64 비트를 넘어서거나 관리하지 않는 하위 부분에 해당합니다.

원래 숫자의 다른 비트는이어야합니다 0. AND 연산으로 마스킹하여이를 달성 할 수 있습니다.


2
좋은 설명! 짧은 답변으로 "마법 번호"의 가치를 빠르게 찾을 수있었습니다.
Expedito

4
이것은 실제로 가장 좋은 답변이지만 @floris의 답변을 먼저 읽지 않으면 도움이되지 않았을 것입니다.
Andrew Backer

29

(전에는 본 적이 없습니다.이 방법은 훌륭합니다!)

n비트를 추출 할 때 n-1비 연속적인 비트 사이에 공간 이 필요 하다는 Floris의 주장에 대해 조금 확장하겠습니다 .

내 초기 생각 (분명히 작동하지 않는 방법을 볼 것입니다)은 더 잘 할 수 있다는 것입니다. n비트 를 추출 i하려면 누군가가 있다면 비트를 추출 / 시프 팅 할 때 충돌이 발생합니다 비트로 -consecutive i에서) i-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...캐리 비트의 잠재적 실패 cn-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 비트 부호없는 경우)

비트를 추출하기위한 매직 쉬프트 / 곱셈은 둘이 동일한 경우에만 작동합니다.


나는 그것을 좋아한다-당신은 각 비트마다 거기에 갈 필요가있는 비트를위한 공간을 필요로하는만큼 오른쪽에 0이 필요하다는 것이 옳다. 또한 왼쪽에 결과 비트가있는만큼 왼쪽에 많은 비트가 필요합니다. 따라서 비트 b가의 위치 mn있으면 m-1왼쪽에 n-m-10이 있고 오른쪽에 0이 있어야합니다. 특히 비트의 순서가 원래 순서와 순서가 다르면 원래 순서와 비교하여 중요한 개선 사항입니다. 이 재미.
Floris

13

이 매우 흥미로운 질문에 대한 이미 훌륭한 답변 외에도 2007 년 이후로 컴퓨터 체스 커뮤니티에서이 비트 곱셈 트릭이 알려져 있는데 여기서 Magic BitBoards 라는 이름으로 사용 됩니다 .

많은 컴퓨터 체스 엔진은 다양한 조각 세트 (점유 된 사각형 당 1 비트)를 나타 내기 위해 여러 개의 64 비트 정수 (비트 보드라고 함)를 사용합니다. 특정 원점의 슬라이딩 조각 (루크, 주교, 여왕)이 K블로킹 조각이없는 경우 최대 사각형으로 이동할 수 있다고 가정합니다 . K점유 된 정사각형의 비트 보드와 함께 비트 단위 및 분산 된 비트를 사용하면 K64 비트 정수에 포함 된 특정 비트 워드가 제공 됩니다.

이 곱셈 K비트를 K64 비트 정수 의 하위 비트에 매핑하기 위해 매직 곱셈을 사용할 수 있습니다 . K그런 다음 이 하위 비트를 사용하여 원래 사각형의 조각이 실제로 이동할 수있는 허용 된 사각형을 나타내는 사전 계산 된 비트 보드 테이블을 색인화하는 데 사용할 수 있습니다 (피스 차단 등).

이 방식을 사용하는 일반적인 체스 엔진에는 사전 계산 된 결과가 포함 된 64 개의 항목 (원 점당 1 개)의 2 개의 테이블 (루크 용 테이블, 주교 용 테이블, 두 가지 조합을 사용하는 퀸)이 있습니다. 최고 등급의 폐쇄 소스 ( Houdini )와 오픈 소스 체스 엔진 ( Stockfish )은 현재이 접근 방식을 매우 높은 성능으로 사용합니다.

이러한 매직 멀티 플라이어는 철저한 검색 (초기 컷오프에 최적화) 또는 시행 착오 (예 : 많은 임의의 64 비트 정수 시도 ) 를 사용하여 수행됩니다 . 이동 상수 중에는 마법 상수를 찾을 수없는 비트 패턴이 없었습니다. 그러나, 비트 캐리 효과는 일반적으로 매핑 될 비트가 (거의) 인접한 인덱스를 가질 때 필요하다.

@Syzygy의 매우 일반적인 SAT 솔버 접근법 인 AFAIK는 컴퓨터 체스에는 사용되지 않았으며, 그러한 마법 상수의 존재와 독창성에 관한 공식적인 이론도없는 것으로 보입니다.


본격적인 CS 배경을 가진 사람이라면 누구나이 문제를 보았을 때 SAT 접근 방식에 뛰어 들었다고 생각했을 것입니다. 아마도 CS 사람들은 체스가 흥미롭지 않습니까? :(
Monica Monica

@KubaOber 컴퓨터 체스는 C 또는 어셈블리로 프로그래밍하고 모든 종류의 추상화 (C ++, 템플릿, OO)를 싫어하는 비트 트위스터에 의해 지배됩니다. 내가 생각 :-) 실제 CS들 전원이 겁
TemplateRex
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.