- 비트 연산을 무엇에 사용 했습니까?
- 왜 그렇게 편리할까요?
- 누군가 아주 간단한 튜토리얼을 추천 해 주시겠습니까?
답변:
모든 사람이 플래그 사용 사례에 푹 빠져있는 것처럼 보이지만, 이것이 비트 연산자의 유일한 응용 프로그램은 아닙니다 (가장 일반적이지만). 또한 C #은 다른 기술이 거의 사용되지 않을만큼 충분히 높은 수준의 언어이지만 여전히 알아 두어야 할 가치가 있습니다. 내가 생각할 수있는 것은 다음과 같습니다.
<<
그리고 >>
당신이 정말로 모든 마이크로를 통해 초조해하는 경우 사업자는 빠르게 증식 물론 2의 힘으로, 닷넷 JIT 최적화는 아마 당신을 당신 (그리고뿐만 아니라 다른 언어의 어떤 점잖은 컴파일러)이 작업을 수행하지만, 할 수 있습니다 확실히하기 위해 이것을 쓸 수도 있습니다.
이러한 연산자의 또 다른 일반적인 용도는 두 개의 16 비트 정수를 하나의 32 비트 정수로 채우는 것입니다. 처럼:
int Result = (shortIntA << 16 ) | shortIntB;
이것은 종종 레거시 이유로이 트릭을 사용하는 Win32 함수와 직접 인터페이스하는 경우에 일반적입니다.
물론 이러한 연산자는 숙제 질문에 대한 답변을 제공 할 때와 같이 경험이없는 사용자를 혼동하고 싶을 때 유용합니다. :)
그것은 훨씬 더 나은 가독성을 가지고 있기 때문에 실제 코드에서 당신은 훨씬 더 떨어져 대신 곱셈을 사용하여 수 있습니다 불구하고 JIT는 그것을 최적화 shl
및 shr
지침 어쨌든 때문에 성능 저하가 없다.
^
연산자 (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();
}
}
내 응용 프로그램의 보안을 위해 비트 연산자를 사용합니다. 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
}
나는 당신이 생각하는 스도쿠를 해결하는 것이 얼마나 실용적인지 모르겠지만 그것이 있다고 가정합시다.
보드를 보여주고 퍼즐을 직접 풀 수 있지만 동작이 합법적인지 확인하는 스도쿠 해결사 또는 단순한 프로그램을 작성하고 싶다고 상상해보십시오.
보드 자체는 아마도 다음과 같은 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 #에서는 다음과 같이 비트 패턴을 계산할 수 있습니다 ( value
is 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);
}
코드는 C로되어 있지만 C #에 쉽게 적용 할 수 있습니다.
그것들은 다른 응용 프로그램의 전체 부하에 사용할 수 있습니다. 여기에 비트 연산을 사용하는 이전에 여기에 게시 한 질문이 있습니다.
다른 예를 보려면 플래그가 지정된 열거를 살펴보십시오.
내 예에서는 비트 연산을 사용하여 이진수의 범위를 -128 ... 127에서 0..255로 변경했습니다 (표현식을 부호 있음에서 부호 없음으로 변경).
여기에 MSN 기사->
http://msdn.microsoft.com/en-us/library/6a71f45d%28VS.71%29.aspx
유용합니다.
그리고이 링크 :
매우 기술적이며 모든 것을 다루고 있습니다.
HTH
가장 일반적인 용도 중 하나는 데이터를 압축하기 위해 비트 필드를 수정하는 것입니다. 패킷을 경제적으로 사용하려는 프로그램에서 주로 이것을 볼 수 있습니다.
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
있지만 실제 코드의 의도를 숨기고 성능에 거의 아무것도 저장하지 않으며 미묘한 버그를 숨길 수 있기 때문에 일반적으로 나쁜 생각이라고 만 포함합니다. .
여러 가지 이유로 사용하게됩니다.
나는 당신이 다른 사람들을 생각할 수 있다고 확신합니다.
즉, 때때로 스스로에게 물어볼 필요가 있습니다 : 노력할만한 가치가있는 메모리와 성능 향상입니다. 그런 종류의 코드를 작성한 후 잠시 쉬었다가 다시 돌아 오십시오. 이 문제로 어려움을 겪고 있다면 더 유지하기 쉬운 코드로 다시 작성하십시오.
반면에 비트 연산 (암호화를 생각해보십시오)을 사용하는 것이 적절한 경우가 있습니다.
더 좋은 방법은 다른 사람이 읽도록하고 광범위하게 문서화하는 것입니다.
계략!
예전에는 Reversi 플레이어의 말을 나타내는 데 사용했습니다. 그것은 8X8이므로 저에게 long
유형을 가져 왔습니다. 예를 들어 모든 조각이 보드에 어디에 있는지 알고 싶다면 or
두 플레이어가 조각합니다.
플레이어의 가능한 모든 단계를 원한다면 오른쪽으로 말하십시오. 플레이어의 말을 >>
1로 표현 AND
하고 상대방의 말과 함께 이제 공통 1이 있는지 확인하십시오 (즉, 오른쪽에 상대 말이 있음을 의미합니다). 그럼 계속하세요. 자신의 조각으로 돌아 가면-움직이지 마십시오. 분명한 부분에 도달하면 그곳으로 이동하여 도중에 모든 조각을 캡처 할 수 있습니다.
(이 기술은 체스를 포함한 많은 종류의 보드 게임에서 널리 사용됩니다)