비트 연산의 실제 적용 [닫기]


79
  1. 비트 연산을 무엇에 사용 했습니까?
  2. 왜 그렇게 편리할까요?
  3. 누군가 아주 간단한 튜토리얼을 추천 해 주시겠습니까?

답변:


83

모든 사람이 플래그 사용 사례에 푹 빠져있는 것처럼 보이지만, 이것이 비트 연산자의 유일한 응용 프로그램은 아닙니다 (가장 일반적이지만). 또한 C #은 다른 기술이 거의 사용되지 않을만큼 충분히 높은 수준의 언어이지만 여전히 알아 두어야 할 가치가 있습니다. 내가 생각할 수있는 것은 다음과 같습니다.


<<그리고 >>당신이 정말로 모든 마이크로를 통해 초조해하는 경우 사업자는 빠르게 증식 물론 2의 힘으로, 닷넷 JIT 최적화는 아마 당신을 당신 (그리고뿐만 아니라 다른 언어의 어떤 점잖은 컴파일러)이 작업을 수행하지만, 할 수 있습니다 확실히하기 위해 이것을 쓸 수도 있습니다.

이러한 연산자의 또 다른 일반적인 용도는 두 개의 16 비트 정수를 하나의 32 비트 정수로 채우는 것입니다. 처럼:

int Result = (shortIntA << 16 ) | shortIntB;

이것은 종종 레거시 이유로이 트릭을 사용하는 Win32 함수와 직접 인터페이스하는 경우에 일반적입니다.

물론 이러한 연산자는 숙제 질문에 대한 답변을 제공 할 때와 같이 경험이없는 사용자를 혼동하고 싶을 때 유용합니다. :)

그것은 훨씬 더 나은 가독성을 가지고 있기 때문에 실제 코드에서 당신은 훨씬 더 떨어져 대신 곱셈을 사용하여 수 있습니다 불구하고 JIT는 그것을 최적화 shlshr지침 어쨌든 때문에 성능 저하가 없다.


^연산자 (XOR)를 다루는 몇 가지 흥미로운 트릭이 있습니다. 이것은 실제로 다음과 같은 속성으로 인해 매우 강력한 연산자입니다.

  • A^B == B^A
  • A^B^A == B
  • 당신이 알고 있다면 A^B그것은 무엇을 얘기하는 것은 불가능 A하고 B,하지만 당신은 그들 중 하나를 알고 있다면, 당신은 다른 사람을 계산할 수 있습니다.
  • 연산자는 곱셈 / 나눗셈 / 더하기 / 빼기와 같은 오버플로를 겪지 않습니다.

이 연산자를 사용하여 본 몇 가지 트릭 :

중간 변수없이 두 개의 정수 변수 교체 :

A = A^B // A is now XOR of A and B
B = A^B // B is now the original A
A = A^B // A is now the original B

항목 당 하나의 추가 변수 만있는 이중 연결 목록입니다. 이것은 C #에서 거의 사용되지 않지만 모든 바이트가 중요한 임베디드 시스템의 저수준 프로그래밍에 유용 할 수 있습니다.

아이디어는 첫 번째 항목에 대한 포인터를 추적하는 것입니다. 마지막 항목에 대한 포인터; 추적하는 모든 항목에 대해 pointer_to_previous ^ pointer_to_next. 이렇게하면 양쪽 끝에서 목록을 순회 할 수 있지만 오버 헤드는 기존 연결 목록의 절반에 불과합니다. 순회를위한 C ++ 코드는 다음과 같습니다.

ItemStruct *CurrentItem = FirstItem, *PreviousItem=NULL;
while (  CurrentItem != NULL )
{
    // Work with CurrentItem->Data

    ItemStruct *NextItem = CurrentItem->XorPointers ^ PreviousItem;
    PreviousItem = CurrentItem;
    CurrentItem = NextItem;
}

끝에서 횡단하려면 첫 번째 줄을에서 FirstItem로 변경하면 됩니다 LastItem. 그것은 바로 거기에 또 다른 메모리 절약입니다.

^C #에서 정기적으로 연산자를 사용하는 또 다른 곳 은 복합 유형 인 내 유형에 대한 HashCode를 계산해야 할 때입니다. 처럼:

class Person
{
    string FirstName;
    string LastName;
    int Age;

    public int override GetHashCode()
    {
        return (FirstName == null ? 0 : FirstName.GetHashCode()) ^
            (LastName == null ? 0 : LastName.GetHashCode()) ^
            Age.GetHashCode();
    }
}

13
+1, 아름다운 xor 스왑. 감사합니다.
Grozz 2010 년

4
xor-list 접근 방식에 +1, 이전에는 본 적이 없습니다.
snemarch

2
이것이 건설적이지 않다는 라벨이
Alex Gordon

"미숙 한 사람을 혼동하고 싶을 때"+1은 너무 많은 수석 개발자 / 아키텍트의 MO를 정했습니다. 나는 경험을 통해 겸손한 실용주의가 올 것이라고 진정으로 생각했지만 아쉽게도 아닙니다. Brogrammers가 점령했고 세상은 더 나쁩니다.
Davos

그리고이 +1000 "어떤 실제 코드에서나 곱셈을 사용하는 것이 훨씬 나을 것입니다. 왜냐하면 훨씬 더 나은 가독성을 가지고 있고 JIT가이를 shl 및 shr 명령으로 최적화하므로 성능 저하가 없습니다." 코드 골프가 실제 코드에서 왜 그렇게 널리 퍼져 있습니까?
Davos

74

내 응용 프로그램의 보안을 위해 비트 연산자를 사용합니다. Enum 안에 다양한 레벨을 저장하겠습니다.

[Flags]
public enum SecurityLevel
{
    User = 1, // 0001
    SuperUser = 2, // 0010
    QuestionAdmin = 4, // 0100
    AnswerAdmin = 8 // 1000
}

그런 다음 사용자에게 수준을 할당합니다.

// Set User Permissions to 1010
//
//   0010
// | 1000
//   ----
//   1010
User.Permissions = SecurityLevel.SuperUser | SecurityLevel.AnswerAdmin;

그런 다음 수행중인 작업의 권한을 확인합니다.

// Check if the user has the required permission group
//
//   1010
// & 1000
//   ----
//   1000
if( (User.Permissions & SecurityLevel.AnswerAdmin) == SecurityLevel.AnswerAdmin )
{
    // Allowed
}

1
@justin, 대단히 감사합니다. 이 User.Permissions = SecurityLevel.SuperUser | SecurityLevel.AnswerAdmin;
Alex Gordon

이들은 플래그가 지정된 열거 형입니다.
Dave

@jenny-코드 주석에 설명이 추가되었습니다.
Justin Niessner 2010 년

1
@Dave-정확합니다. 그러나 플래그가 지정된 열거 형을 사용하는 경우 비트 연산자를 사용하여 값에 대한 검사를 수행합니다. 편리하고 매우 간단합니다. 제 생각에는 OP가 요구 한 것입니다.
Justin Niessner 2010 년

7
@Justin : 지금은 쓸모 없지만 Enum.HasFlag : msdn.microsoft.com/en-us/library/system.enum.hasflag.aspx
Reed Copsey

16

나는 당신이 생각하는 스도쿠를 해결하는 것이 얼마나 실용적인지 모르겠지만 그것이 있다고 가정합시다.

보드를 보여주고 퍼즐을 직접 풀 수 있지만 동작이 합법적인지 확인하는 스도쿠 해결사 또는 단순한 프로그램을 작성하고 싶다고 상상해보십시오.

보드 자체는 아마도 다음과 같은 2 차원 배열로 표현 될 것입니다.

uint [, ] theBoard = new uint[9, 9];

0은 셀이 여전히 비어 있고 [1u, 9u] 범위의 값이 보드의 실제 값임을 의미합니다.

이제 어떤 움직임이 합법적인지 확인하고 싶다고 상상해보십시오. 분명히 몇 개의 루프로 할 수 있지만 비트 마스크를 사용하면 작업을 훨씬 빠르게 할 수 있습니다. 규칙을 준수하는 단순한 프로그램에서는 중요하지 않지만 솔버에서는 가능합니다.

각 행, 각 열 a 및 각 3x3 상자에 이미 삽입 된 숫자에 대한 정보를 저장하는 비트 마스크 배열을 유지할 수 있습니다.

uint [] maskForNumbersSetInRow = new uint[9];

uint [] maskForNumbersSetInCol = new uint[9];

uint [, ] maskForNumbersSetInBox = new uint[3, 3];

숫자 세트에 해당하는 1 비트를 사용하여 숫자에서 비트 패턴으로의 매핑은 매우 간단합니다.

1 -> 00000000 00000000 00000000 00000001
2 -> 00000000 00000000 00000000 00000010
3 -> 00000000 00000000 00000000 00000100
...
9 -> 00000000 00000000 00000001 00000000

C #에서는 다음과 같이 비트 패턴을 계산할 수 있습니다 ( valueis an uint).

uint bitpattern = 1u << (int)(value - 1u);

위의 줄 1u에서 비트 패턴에 해당하는 것은 00000000 00000000 00000000 00000001왼쪽으로 이동합니다 value - 1. 예를 들어 value == 5다음과 같은 경우

00000000 00000000 00000000 00010000

처음에 각 행, 열 및 상자의 마스크는 0입니다. 보드에 숫자를 입력 할 때마다 마스크를 업데이트하므로 새 값에 해당하는 비트가 설정됩니다.

행 3에 값 5를 삽입한다고 가정합니다 (행과 열은 0부터 번호가 매겨 짐). 행 3의 마스크는에 저장됩니다 maskForNumbersSetInRow[3]. 삽입하기 전에 이미 {1, 2, 4, 7, 9}행 3에 숫자가 있다고 가정 해 보겠습니다 . 마스크의 비트 패턴은 maskForNumbersSetInRow[3]다음과 같습니다.

00000000 00000000 00000001 01001011
bits above correspond to:9  7  4 21

목표는이 마스크의 값 5에 해당하는 비트를 설정하는 것입니다. 비트 또는 연산자 ( |)를 사용하여 수행 할 수 있습니다 . 먼저 값 5에 해당하는 비트 패턴을 만듭니다.

uint bitpattern = 1u << 4; // 1u << (int)(value - 1u)

그런 다음을 사용 operator |하여 마스크에 비트를 설정합니다.maskForNumbersSetInRow[3]

maskForNumbersSetInRow[3] = maskForNumbersSetInRow[3] | bitpattern;

또는 더 짧은 형식 사용

maskForNumbersSetInRow[3] |= bitpattern;

00000000 00000000 00000001 01001011
                 |
00000000 00000000 00000000 00010000
                 =
00000000 00000000 00000001 01011011

이제 마스크는 {1, 2, 4, 5, 7, 9}이 행 (3 행)에 값 이 있음을 나타냅니다 .

확인하고자하는 경우 행에 어떤 값이 operator &있으면 해당 비트가 마스크에 설정되어 있는지 확인하는 데 사용할 수 있습니다 . 마스크에 적용된 해당 연산자의 결과와 해당 값에 해당하는 비트 패턴이 0이 아니면 값이 이미 행에있는 것입니다. 결과가 0이면 값은 행에 없습니다.

예를 들어 값 3이 행에 있는지 확인하려면 다음과 같이 할 수 있습니다.

uint bitpattern = 1u << 2; // 1u << (int)(value - 1u)
bool value3IsInRow = ((maskForNumbersSetInRow[3] & bitpattern) != 0);

00000000 00000000 00000001 01001011 // the mask
                 |
00000000 00000000 00000000 00000100 // bitpattern for the value 3
                 =
00000000 00000000 00000000 00000000 // the result is 0. value 3 is not in the row.

다음은 보드에 새 값을 설정하고 적절한 비트 마스크를 최신 상태로 유지하고 이동이 합법적인지 확인하는 방법입니다.

public void insertNewValue(int row, int col, uint value)
{

    if(!isMoveLegal(row, col, value))
        throw ...

    theBoard[row, col] = value;

    uint bitpattern = 1u << (int)(value - 1u);

    maskForNumbersSetInRow[row] |= bitpattern;

    maskForNumbersSetInCol[col] |= bitpattern;

    int boxRowNumber = row / 3;
    int boxColNumber = col / 3;

    maskForNumbersSetInBox[boxRowNumber, boxColNumber] |= bitpattern;

}

마스크가 있으면 다음과 같이 이동이 합법적인지 확인할 수 있습니다.

public bool isMoveLegal(int row, int col, uint value)
{

    uint bitpattern = 1u << (int)(value - 1u);

    int boxRowNumber = row / 3;
    int boxColNumber = col / 3;

    uint combinedMask = maskForNumbersSetInRow[row] | maskForNumbersSetInCol[col]
                        | maskForNumbersSetInBox[boxRowNumber, boxColNumber];

    return ((theBoard[row, col] == 0) && ((combinedMask & bitpattern) == 0u);
}

3
정말 흥미로운 것들이지만 약간 내 머리 위로 날아갔습니다. 여기에서 진행되는 작업에서 약간의 빌드가 순서대로 진행될 수있는 것 같습니다 (비트 예제가있는 일부 주석). 마스크를 만드는 데 약간의 변화가 있다는 것은 나에게 약간의 흔들림입니다. 대답에 시간을 쏟았으니 그냥 물어보세요.
Mark


3

하드웨어와 통신해야하는 경우 어느 시점에서 비트 트위들 링을 사용해야합니다.

픽셀 값의 RGB 값 추출.

너무 많은 것들


2
  1. 하나의 제한된 크기 변수를 통해 함수에 많은 인수를 전달하는 데 사용할 수 있습니다.
  2. 장점은 낮은 메모리 오버 헤드 또는 낮은 메모리 비용입니다. 따라서 성능이 향상됩니다.
  3. 나는 그 자리에서 튜토리얼을 쓸 수 없지만 거기에 있다고 확신합니다.

이런, 방금이 C # 태그를 봤는데 C ++ 인 줄 알았어요. ... 천천히 읽어야합니다 ... :)
C Johnson

2

그것들은 다른 응용 프로그램의 전체 부하에 사용할 수 있습니다. 여기에 비트 연산을 사용하는 이전에 여기에 게시 한 질문이 있습니다.

Java에서 비트 AND, 비트 포함 OR 질문

다른 예를 보려면 플래그가 지정된 열거를 살펴보십시오.

내 예에서는 비트 연산을 사용하여 이진수의 범위를 -128 ... 127에서 0..255로 변경했습니다 (표현식을 부호 있음에서 부호 없음으로 변경).

여기에 MSN 기사->

http://msdn.microsoft.com/en-us/library/6a71f45d%28VS.71%29.aspx

유용합니다.

그리고이 링크 :

http://weblogs.asp.net/alessandro/archive/2007/10/02/bitwise-operators-in-c-or-xor-and-amp-amp-not.aspx

매우 기술적이며 모든 것을 다루고 있습니다.

HTH


2

항목 조합에 1 개 이상의 옵션이있을 때마다 비트 단위는 일반적으로 쉬운 수정입니다.

몇 가지 예에는 보안 비트 (Justin의 샘플을 기다리는 중 ..), 일정 예약 등이 있습니다.



2

C #에서 가장 자주 사용하는 작업 중 하나는 해시 코드를 생성하는 것입니다. 이를 사용하는 합리적으로 좋은 해싱 방법이 있습니다. 예를 들어 X와 Y가 모두 int 인 좌표 클래스의 경우 사용할 수 있습니다.

public override int GetHashCode()
{
  return x ^ ((y << 16) | y >> 16);
}

이렇게하면 동일한 객체 (동일성이 비교되는 두 객체에서 X 및 Y 매개 변수가 모두 동일 함을 의미한다고 가정)에 의해 생성 될 때 동일하다고 보장되는 숫자를 빠르게 생성하는 동시에 값이 낮은 객체에 대해 충돌 패턴을 생성하지 않습니다 (예 : 대부분의 응용 프로그램에서 가장 일반적).

다른 하나는 플래그 열거를 결합하는 것입니다. 예RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase

.NET과 같은 프레임 워크에 대해 코딩 할 때 더 일반적으로 필요하지 않은 몇 가지 저수준 작업이 있습니다 (예 : C #에서는 UTF-8을 UTF-16으로 변환하는 코드를 작성할 필요가 없습니다. 하지만 물론 누군가는 그 코드를 작성해야했습니다.

가장 가까운 이진수로 반올림 (예 : 1010에서 10000까지 반올림)하는 것과 같은 몇 가지 비트 트위들 링 기법이 있습니다.

        unchecked
        {
            --x;
            x |= (x >> 1);
            x |= (x >> 2);
            x |= (x >> 4);
            x |= (x >> 8);
            x |= (x >> 16);
            return ++x;
        }

필요할 때 유용하지만 그다지 일반적이지 않은 경향이 있습니다.

마지막으로, << 1대신에 수학을 미세 최적화하는 데 사용할 수도 * 2있지만 실제 코드의 의도를 숨기고 성능에 거의 아무것도 저장하지 않으며 미묘한 버그를 숨길 수 있기 때문에 일반적으로 나쁜 생각이라고 만 포함합니다. .


1
기술적으로 비트 시프트 연산자는 비트 단위 연산자로 간주 될 수 없습니다. 이유는 Wikipedia 기사의 비트 시프트 섹션을 확인하십시오. en.wikipedia.org/wiki/Bitwise_operation
Justin Niessner

1

이진 정렬. 구현시 비트 시프트 연산자 대신 나누기 연산자를 사용하는 문제가있었습니다. 이로 인해 컬렉션 크기가 10,000,000 이상이 된 후 BS가 실패했습니다.


1

여러 가지 이유로 사용하게됩니다.

  • 메모리 효율적인 방식으로 옵션 플래그 저장 (및 확인!)
  • 계산 프로그래밍을 수행하는 경우 수학 연산자 대신 비트 연산을 사용하여 일부 연산을 최적화하는 것이 좋습니다 (부작용주의).
  • 그레이 코드 !
  • 열거 된 값 생성

나는 당신이 다른 사람들을 생각할 수 있다고 확신합니다.

즉, 때때로 스스로에게 물어볼 필요가 있습니다 : 노력할만한 가치가있는 메모리와 성능 향상입니다. 그런 종류의 코드를 작성한 후 잠시 쉬었다가 다시 돌아 오십시오. 이 문제로 어려움을 겪고 있다면 더 유지하기 쉬운 코드로 다시 작성하십시오.

반면에 비트 연산 (암호화를 생각해보십시오)을 사용하는 것이 적절한 경우가 있습니다.

더 좋은 방법은 다른 사람이 읽도록하고 광범위하게 문서화하는 것입니다.


1

계략!

예전에는 Reversi 플레이어의 말을 나타내는 데 사용했습니다. 그것은 8X8이므로 저에게 long유형을 가져 왔습니다. 예를 들어 모든 조각이 보드에 어디에 있는지 알고 싶다면 or두 플레이어가 조각합니다.
플레이어의 가능한 모든 단계를 원한다면 오른쪽으로 말하십시오. 플레이어의 말을 >>1로 표현 AND하고 상대방의 말과 함께 이제 공통 1이 있는지 확인하십시오 (즉, 오른쪽에 상대 말이 있음을 의미합니다). 그럼 계속하세요. 자신의 조각으로 돌아 가면-움직이지 마십시오. 분명한 부분에 도달하면 그곳으로 이동하여 도중에 모든 조각을 캡처 할 수 있습니다.
(이 기술은 체스를 포함한 많은 종류의 보드 게임에서 널리 사용됩니다)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.