[C#] Int를 비트단위로 저장해주는 클래스
안녕하십니까 밤말팅입니다.
블로그를 열고 첫 포스팅인데요, 앞으로 게임 개발에 도움이 될 여러 가지 클래스를 구상하고 제작하는 대로 포스팅 할
예정입니다. 내용이 괜찮다고 생각하시면 자주 들러주시고 피드백도 주시면 감사드리겠습니다!
그리고 첫 번째 포스팅 내용은 [Int를 비트단위로 저장해주는 클래스]입니다!
네트워크 전송을 하려고 할 때에는 변수 하나하나의 크기가 너무 아까울 때가 있습니다.
"이 변수는 127까지만 표현하면 되는데"
와 같은 고민을 하게 될 때가 있는데요, 4명이 한 방에 들어가는 2D 횡스크롤인 경우 극단적으로 이렇게도 할 수 있겠죠.
[유저 번호, 오른쪽을 보고 있는지, 이동 중인지, 남은 체력은 0~15]
물론 하드코딩으로도 할 수 있을 겁니다. 하지만 넣을 정보가 바뀔 때마다 다시 하드코딩을 해주어야 하고,
만들 때마다 계속해서 변수를 짜깁기하는 데 시간과 노력이 많이 들겠죠, 변수가 byte 사이에 걸쳐도 문제구요.
그냥 길이만 쫙 등록해주고 배열에다가 순차적으로 넣어주기만 하면 얼마나 편할까요, 알아서 bit를 넣을
공간을 마련해주고, 들어온 정보를 스스로 쪼개서 공간에 맞춰 넣어주는 클래스가 있으면요?
이런 것들을 처리해줄 만한 클래스를 만들어보도록 합시다!
전체 코드를 보고싶으신 분은 아래 글을 참조해주시면 되겠습니다~
[C#]Bit_Builder 전체 코드
[C#] Int를 비트단위로 저장해주는 클래스 안녕하십니까 밤말팅입니다. 블로그를 열고 첫 포스팅인데요, 앞으로 게임 개발에 도움이 될 여러 가지 클래스를 구상하고 제작하는 대로 포스팅 할 예
game-part-factory.tistory.com
[이 클래스는 아래와 같은 네임스페이스 참조가 필요합니다]
using <system>
<목표>
//Bit_Bulder를 생성합니다. bool,bool,6,8,16,22,10,15,bool,32,float이 들어있습니다.
//이름은 Build_Test로 하겠습니다.
Bit_Builder Build_Test = new Bit_Builder
(new Bit_Type[11] { Bit_Type.BOOL, Bit_Type.BOOL, (Bit_Type)6, (Bit_Type)8, (Bit_Type)16,
(Bit_Type)22, (Bit_Type)10, (Bit_Type)15, Bit_Type.BOOL, (Bit_Type)32, Bit_Type.FLOAT});
//각 위치에 원하는 값을 넣어줍니다.
Build_Test[0] = true;
Build_Test[1] = false;
Build_Test[2] = 30;
Build_Test[3] = -116;
Build_Test[4] = 29533;
Build_Test[5] = 1934581;
Build_Test[6] = -506;
Build_Test[7] = 13152;
Build_Test[8] = true;
Build_Test[9] = -205578954;
Build_Test[10] = 124782.3f;
//각 위치에 있는 값을 표시합니다.
for (int i = 0; i < Build_Test.Length; ++i)
{
Console.WriteLine("Build_Test[" + i + "] : " + Build_Test[i]);
};
//Bit_Test에 들어있는 바이트 배열의 길이를 표시합니다.
Console.WriteLine("\nTotal Byte : " + Build_Test.GetByteArray().Length);
위 코드와 같이 bit 타입과 길이를 넣은 클래스를 선언해준 후, 각 위치에 값을 넣습니다.
마찬가지로 각 위치에 접근하면 원하는 값을 다시 꺼내 주는 것까지가 이번 클래스의 목표가 되겠습니다.
물론 그냥 넣는 것이 아니라, bit에 맞게 위치를 잘 잡아주는 것이 중요하겠죠?
<구상>
구상에 앞서 저장 방식이 리틀 엔디언으로 이뤄졌다는 것을 전제로 합니다.
빅 엔디언의 경우 각 byte 순서를 뒤집어서 계산해야 합니다.
위쪽은 평범한 4byte 값입니다. 이걸 아래 위치에 넣기만 하면 되겠습니다.
원본 첫 번째 byte를 비트 위치에 맞게 두 개로 쪼갭니다.
두 개로 쪼갠 뒤에, 뒷부분은 목표 앞 byte에, 앞부분은 목표 뒷 byte에 넣습니다.
목표 byte에 첫 번째 byte를 넣은 후에는 뒷 byte를 목표 위치까지 시프트 한 후에 대입하면
짜잔! 4byte를 11bit로 끼워 넣는 데에 성공했습니다!
정보를 가져올 때에는 역순으로 진행하면 되겠습니다.
bit가 더 늘어난 경우는 별로 신경 안 쓰셔도 됩니다. 중간 값은 그대로 넣어도 무방하니까요.
대신 아래 경우만 한 번 더 따져봅시다.
앞에 공간이 부족해서 맨 뒤 byte가 들어갈 공간이 없는 상황입니다.
이때에는 맨 뒤 byte를 맨 앞 byte와 같이 한 번 잘라줍니다.
byte 하나를 더 쓰게 되었지만, 그 뒤에도 새로운 정보가 들어갈 것이니 괜찮습니다.
혹여나 이게 마지막 정보라면, 정보량을 제한하거나, 비트를 여유롭게 조절하면 효율적으로 쓸 수 있습니다.
이제 bit를 구겨 넣을 방법을 생각했으니 실제 클래스를 만들어보도록 합시다.
<구현>
public class Bit_Builder
{
//각 위치에 들어갈 타입(길이)을 명시한 배열
private Bit_Type[] containTypeArray;
//실제 결과물이 들어가는 배열
private byte[] containByteArray;
//각 변수가 들어갈 위치를 넣는 배열
private int[] locationByteArray;
//타입이 몇 개 들어있는지 반환
public int Length { get { return containTypeArray.Length; } }
}
기본이 될 클래스입니다.
각 변수의 타입을 모아놓은 배열을 일단 만들었습니다. 이걸로 각 변수를 어떻게 읽어올지 결정합니다.
실제 데이터가 들어간 byte 배열, 각 변수의 시작 bit를 보여주는 배열도 같이 만들어줍니다.
몇 개의 변수가 현재 클래스에 들어있는지 보여주는 Length도 만들어 놓겠습니다.
public enum Bit_Type
{
FLOAT,
BOOL,
}
위에서 Bit_Type이라는 것이 나왔는데요, 간단하게 float과 bool로만 이루어져 있습니다.
이 두 개를 쓰시지 않으시려면, 위에 Bit_Type []를 int []로 바꾸시면 됩니다~
숫자를 건드리지 않아서 float는 0, bool은 1로 보시면 되겠죠!
만약 본인이 원하는 변수가 있다면, 추가해도 무관합니다!
public Bit_Builder(Bit_Type[] wantTypes)
{
//비트 총길이 계산
int bitLength = 0;
//타입 배열에 받아온 타입을 넣습니다.
containTypeArray = wantTypes;
//타입마다 위치를 정해주기 위해 타입 배열의 길이만큼 위치 배열도 만듭니다.
locationBitArray = new int[wantTypes.Length];
//각 타입을 돌면서 비트 시작 위치를 잡아주는 반복문입니다.
for (int i = 0; i < containTypeArray.Length; ++i)
{
//int를 쓰기 때문에 32비트보다 큰 값은 자릅니다.
if (containTypeArray[i] > (Bit_Type)32)
{
containTypeArray[i] = (Bit_Type)32;
};
//지금까지 더해진 길이를 현재 위치에 넣습니다.
locationBitArray[i] = bitLength;
//비트 총길이에 현재 타입의 길이를 더합니다.
bitLength += TypeLength(containTypeArray[i]);
//반복..
};
//비트 총길이를 8로 나눈 후에, 소숫점 올림하여(마지막 남는 비트를 위함)
//결과물 바이트 배열을 만듭니다.
containByteArray = new byte[(int)Math.Ceiling(bitLength / 8.0d)];
}
우선 첫 번째로 생성자를 한 번 보도록 합시다.
모든 타입을 더해서 기록할 변수를 하나 마련한 뒤에
받아온 타입을 타입 배열에 넣고, 그 길이만큼 위치 배열도 만듭니다.
이후에는 타입 배열을 전부 돌면서 위치 배열을 채웁니다.
첫 번째 타입의 길이가 6이었으면, 두 번째 타입은 6부터 시작,
두 번째 타입의 길이가 7이므로, 세 번째 타입은 6+7 = 13부터 시작
과 같은 방식으로 위치를 채워나가는 것이 되겠죠!
모든 길이를 다 더한 값이 나오면, 8로 나눈 뒤, 소수점 올림 하여 byte 배열을 만듭니다.
만약 bit의 개수가 9개라면, 바이트가 2개 필요하겠죠?
그래서 소수점 올림으로 계산하도록 하겠습니다.
private static int TypeLength(Bit_Type wantType)
{
if(wantType == Bit_Type.FLOAT)
{
return 32;
}
else
{
return (int)wantType;
};
}
생성자에서 길이를 더할 때, 타입을 그대로 넣지 않고 TypeLength()를 사용했습니다.
이건 제가 float의 길이를 직접 넣어주기 위해서 작게 만들었습니다.
만약 다른 변수형을 Bit_Type에 추가하신다면, 여기서 길이를 정해주실 수 있습니다.
여기까지 하면 대충 필요한 것은 다 적은 듯하고, 이제 본격적으로 내용을 집어넣어 보도록 하겠습니다.
집어넣는 Set계열 함수는 공정이 더 많이 필요하기 때문에 먼저 설명하고,
Set에 따라가는 Get함수는 바로 이어서 설명하도록 하겠습니다.
private void SetBool(int targetIndex, bool value)
{
int bitLocation = locationBitArray[targetIndex] % 8;
int byteLocation = locationBitArray[targetIndex] >> 3;
if (value)
{
containByteArray[byteLocation] |= (byte)(0x80 >> bitLocation);
}
else
{
containByteArray[byteLocation] &= (byte)~(0x80 >> bitLocation);
};
}
시작은 가볍게 bool 변수를 넣는 작업입니다. 원래는 그냥 길이가 1인 값을 넣으면 되긴 하겠지만,
굳이 다른 작업을 할 필요가 없기 때문에 따로 함수를 만들도록 하겠습니다.
bitLocation은 bit 위치를 8로 나눈 나머지입니다. 만약 10이었으면, 2만 남겠죠.
이걸 활용해서 0x80 >> bitLocation을 하면, 원하는 위치에 접근할 수 있게 됩니다.
그리고 밑에 byteLocation은 3번 오른쪽 시프트를 하는데요, 이러면 2의 3 제곱인 8로 나눈 후 소수점을 버린 것과 같죠.
시작 비트가 3이면, 바이트[0]에 접근
시작 비트가 20이면, 바이트[2]에 접근
위과 같이 접근할 byte를 고를 수 있게 미리 잡아둘 수 있겠죠.
이걸 가지고 접근을 시도해봅시다.
만약 들어온 값이 참이면, 방금 만든 비트 마스크와 원래 데이터를 Or 연산합니다.
그렇게 되면 해당 위치에 1이 들어가게 됩니다.
반대로 거짓을 받는다면, 비트 마스크를 뒤집습니다.
뒤집은 상태로 데이터와 And 연산을 하게 되면, 원하는 위치 빼고는 원래 데이터를 유지하여
해당 위치를 무조건 0으로 바꿀 수 있게 됩니다.
그러면 벌써 bool값을 넣는 함수가 끝났네요!
가져와보기만 하면 되겠죠?
private bool GetBool(int targetIndex)
{
int bitLocation = locationBitArray[targetIndex] % 8;
byte targetByte = containByteArray[locationBitArray[targetIndex] >> 3];
return (targetByte & (0x80 >> bitLocation)) > 0;
}
가져오는 함수는 굉장히 굉장히 간단합니다. 사실 한 줄로도 줄일 수 있죠.
bitLocation은 그대로 bit 위치입니다.
targetByte는 byte 위치를 구한 뒤에, 실제로 그 byte를 가져온 값입니다.
비트 마스크와 And 연산을 하게 되면
짠! 아주 간단하게 해당 위치의 값을 알 수 있게 되겠죠.
이 값이 0보다 큰지 비교를 해서 리턴을 하게 해 놓으면,
해당 위치에 1이 있을 때, True라고 반환을 해주고, 0이 있으면 False라고 리턴을 해줄 겁니다.
bool까지는 굉장히 쉽군요!
하지만 아직 몸체인 SetByte와 GetByte를 안 들어갔다는 점!
이제부터 진짜 내용을 시작해 보도록 하겠습니다~
조금 길기 때문에 조금씩 나눠서 설명을 해드릴게요!
<SetByte함수의 분기점>
○ 넣으려는 변수의 길이가 8 이하
> 바이트 하나에 넣을 수 있음
> 바이트 두 개에 넣을 수 있음
○ 넣으려는 변수의 길이가 8 이상
> 0번 비트부터 쓸 수 있음
- 마지막 여분의 비트가 남음
> 0번 비트를 쓸 수 없음
- 마지막 비트를 바이트 하나에 넣을 수 있음
- 마지막 비트를 바이트 두 개에 넣을 수 있음
SetByte에서 확인해야되는 분기점들입니다.
해당 분기점들을 하나하나 따라가면서 만들어보도록 합시다!
private void SetByte(int targetIndex, byte[] wantByte)
{
//시작 bit의 위치
int bitLocation = locationBitArray[targetIndex] % 8;
//시작 byte의 위치
int byteLocation = locationBitArray[targetIndex] >> 3;
//끝나는 bit의 위치
int bitEnd = bitLocation + TypeLength(containTypeArray[targetIndex]);
//끝나는 byte의 위치
int byteEnd = (locationBitArray[targetIndex] + TypeLength(containTypeArray[targetIndex])) >> 3;
//원래 값에서 모두 채워진 byte의 수
int wholeByte = TypeLength(containTypeArray[targetIndex]) >> 3;
//원래 값에서 마지막 바이트에 있는 bit 수
int leftBit = TypeLength(containTypeArray[targetIndex]) % 8;
//원래 값에서 마지막 바이트에 있는 bit 수 + 시작 bit의 위치
int lastBit = leftBit + bitLocation;
//끝나는 byte가 byte배열보다 큰 경우에는 실행하지 않고 끊습니다.
if (byteEnd > containByteArray.Length)
{
return;
};
}
SetByte의 기본 모습입니다.
대상이 되는 위치와 값을 받는 함수가 되겠습니다.
시작 bit와 byte를 찾기 위해 아까 SetBool에서 했던 것과 마찬가지의 과정을 진행했구요,
끝나는 위치를 알기 위해 길이를 더해서 위치를 한 번 더 찾아주었습니다.
그리고 중간은 그냥 채우고 넘어가기 위해서 다 채워진 byte를 구분해주었구요.
다 채워진 이후에 남는 bit수도 따로 정리해두었습니다.
그리고 혹시나 마지막 byte를 두 개로 쪼개야 될 것을 구분하기 위해, 마지막 bit위치와 시작 bit위치를 더합니다.
그리고 클래스에 있는 byte배열의 길이를 넘어버리면 배열을 넘어갈 위험이 발생할 여지가 있으므로
이 이상의 요청이 들어오는 경우 실행하지 않도록 하겠습니다.
○ 넣으려는 변수의 길이가 8 이하
if (1 < TypeLength(containTypeArray[targetIndex]) && TypeLength(containTypeArray[targetIndex]) < 8)
{
//첫 번째 비트에 모두 넣을 수 있는 상황입니다.
if (bitEnd < 9)
{
int bitMask = (0xff >> bitLocation) ^ (0xff >> bitEnd);
containByteArray[byteLocation] &= (byte)~bitMask;
containByteArray[byteLocation] |= (byte)(bitMask & (wantByte[0] << (8 - bitEnd)));
return;
}
//첫 번째 비트에 모두 넣을 수 없는 상황입니다.
else
{
int bitMask = (0xff >> bitLocation);
containByteArray[byteLocation] &= (byte)~bitMask;
containByteArray[byteLocation] |= (byte)(bitMask & wantByte[0]);
bitMask = ~(0xff >> (bitEnd - 8));
containByteArray[byteLocation + 1] &= (byte)~bitMask;
containByteArray[byteLocation + 1] |= (byte)(bitMask & (wantByte[0] << (8 - TypeLength(containTypeArray[targetIndex]))));
};
}
8이하일 때, 잘라서 넣어야하는지 아닌지를 확인하기 위해 마지막 bit위치를 확인합니다.
마지막 bit위치는 (시작 위치 + 길이)로 되어있어서 9를 넘어가면 다음 byte에 걸쳤다는 의미가 되겠죠.
먼저 9를 넘지 않았을 때를 살펴봅시다.
임의의 숫자를 넣은 비트마스크입니다. 시작 위치만큼 11111111을 오른쪽 시프트해서
왼쪽에 시작 위치 수만큼 0을 넣습니다.
두 번째 것은 시작 위치 + 길이만큼 오른쪽 시프트한 값을 넣습니다.
두 개를 xor하게 되면 가운데 서로 다른 부분만 마스크가 떠지게 되어 원하는 위치를 받아올 수 있게 되겠죠.
여기에 첫 번째 byte를 밀어넣으면 되겠습니다.
목표로 하는 byte에서 방금 얻은 비트마스크를 역전시킨 후 And연산을 하게 되면, 넣을 위치만 0으로
초기화시켜줄 수 있습니다.
0으로 초기화시킨 부분에 위와 같은 과정을 거친 값을 Or연산 해주면
다른 bit는 건들지 않고 접근하려고 한 bit에만 원하는 값을 넣어줄 수 있겠습니다~
이제 두 바이트에 나눠서 담아야 할 경우를 살펴봐야 하겠죠?
원래 이렇게 설명을 드렸었는데요, 지금은 8bit 이내이기 때문에 초록색 부분(○)을 빼고 생각하면 되겠습니다~
일단 비트마스크를 (0xff >> bitLocation)로 설정해서 하늘색 부분(□)을 가져옵니다.
가져온 대로 그대로 넣어주면 되겠죠?
하지만 빨간색 부분(△)이 문제입니다. 8bit이내라는 말은, 맨 앞에 쓸데없는 부분이 있을 수 있다는 거겠죠~
그래서 (8-길이)만큼 왼쪽 시프트해주어 bit를 왼쪽에 정렬시킨 후에 빨간색 부분(△)을 넣도록 하겠습니다!
뒷 byte에 들어갈 bit수는 (마지막 위치 - 8)이 되겠죠,
이걸로 비트마스크를 만든 뒤에, 정렬시킨 값과 Or연산 해주면 값이 들어가게 됩니다~
읽을 때에는 이 두 개를 잘 붙혀주기만 하면 됩니다!
이걸로 8it 이하는 끝났군요
8bit를 넘어간 것만 처리해주면 값을 넣는 것은 얼추 끝나게 됩니다!
if (bitLocation == 0)
{
for (int i = 0; i < wholeByte; ++i)
{
containByteArray[byteLocation + i] = wantByte[i];
};
if(leftBit > 0)
{
containByteArray[byteLocation + wholeByte] &= (byte)~(0xff >> bitEnd % 8);
containByteArray[byteLocation + wholeByte] |= (byte)(wantByte[wholeByte] << (8 - leftBit));
};
return;
}
가볍게 앞에 bit가 없는 상황을 가정해봅시다.
0번 byte부터 모두 차 있는 byte까지 돌려가면서 목표 위치에 넣어줍니다.
그래도 남는 bit가 있을 때에는
이걸 다시 사용하면 되겠죠, 마지막 byte를 왼쪽 정렬해준 다음에 그 길이만큼 넣어주도록 합시다. 쉽네요!
int bitMask = 0xFF >> bitLocation;
//첫 번째 byte를 두 개로 나누기
byte firstByte = (byte)(wantByte[0] & bitMask);
byte lastByte = (byte)(wantByte[0] & ~bitMask);
//중간은 그냥 넣습니다.
for (int i = 1; i < wholeByte; ++i)
{
containByteArray[byteLocation + i] = wantByte[i];
};
containByteArray[byteLocation] &= (byte)~bitMask;
containByteArray[byteLocation] |= firstByte;
containByteArray[byteLocation + wholeByte] &= (byte)bitMask;
containByteArray[byteLocation + wholeByte] |= lastByte;
만약 앞에 bit가 있는 경우, 첫 번째 byte를 두 개로 쪼개줍니다.
그리고 중간에 모두 다 찬 바이트를 돌려주면서 그대로 넣어준 뒤에
쪼개둔 byte를 맨 앞과 맨 뒤에 넣어주면 아주 좋게 잘 되겠죠..?
if(wholeByte < 4)
{
if (lastBit > 8)
{
containByteArray[byteLocation + wholeByte] &= (byte)~bitMask;
containByteArray[byteLocation + wholeByte] |= (byte)(wantByte[wholeByte] & bitMask);
bitMask = ~(0xff >> lastBit % 8);
containByteArray[byteLocation + wholeByte + 1] &= (byte)~bitMask;
containByteArray[byteLocation + wholeByte + 1] |= (byte)((wantByte[wholeByte] << (8 - leftBit)) & bitMask);
}
else if (leftBit > 0)
{
bitMask = ~(~bitMask | (0xff >> bitLocation + leftBit));
containByteArray[byteLocation + wholeByte] &= (byte)~bitMask;
containByteArray[byteLocation + wholeByte] |= (byte)((wantByte[wholeByte] << (8 - lastBit)) & bitMask);
};
};
잘 되었으면 좋겠지만, 마지막 byte를 넣는 과정을 아직 안 안 넣었네요!
wholeByte가 4라면, 4개가 다 찬 상태이기 때문에, 실행을 시키지 않도록 하겠습니다.
마지막 bit위치를 비교해서 8보다 크다면, 두 개의 byte를 써야하는 것으로 보고 진행합니다.
과정은 SetBool과 똑같이 진행해주시면 됩니다~
GetByte는 SetByte의 역순입니다.
SetByte에서 썼던 비트마스크나 조건을 그대로 쓰는 대신
값을 짜깁기해서 완성시켜주는 것이 전부이죠.
간단하게 코드만 보여드리도록 하겠습니다.
private byte[] GetByte(int targetIndex)
{
int bitLocation = locationBitArray[targetIndex] % 8;
int byteLocation = locationBitArray[targetIndex] >> 3;
int bitEnd = bitLocation + TypeLength(containTypeArray[targetIndex]);
int byteEnd = (locationBitArray[targetIndex] + TypeLength(containTypeArray[targetIndex])) >> 3;
int wholeByte = TypeLength(containTypeArray[targetIndex]) >> 3;
int leftBit = TypeLength(containTypeArray[targetIndex]) % 8;
int lastBit = leftBit + bitLocation;
byte[] result = new byte[4];
if (byteEnd > containByteArray.Length)
{
return result;
};
if (1 < TypeLength(containTypeArray[targetIndex]) && TypeLength(containTypeArray[targetIndex]) < 8)
{
if (bitEnd < 9)
{
int bitMask = (0xff >> bitLocation) ^ (0xff >> bitEnd);
result[0] = (byte)((containByteArray[byteLocation] & bitMask) >> (8 - bitEnd));
}
else
{
int bitMask = (0xff >> bitLocation);
result[0] = (byte)(containByteArray[byteLocation] & bitMask);
bitMask = ~(0xff >> (bitEnd - 8));
result[0] |= (byte)((containByteArray[byteLocation + 1] & bitMask) >> (8 - TypeLength(containTypeArray[targetIndex])));
};
}
else
{
if (bitLocation == 0)
{
for (int i = 0; i < wholeByte; ++i)
{
result[i] = containByteArray[byteLocation + i];
};
if (leftBit > 0)
{
result[wholeByte] = (byte)(containByteArray[byteLocation + wholeByte] & ~(0xff >> bitEnd % 8));
result[wholeByte] = (byte)(result[wholeByte] >> (8 - leftBit));
};
}
else
{
int bitMask = 0xFF >> bitLocation;
result[0] = (byte)(containByteArray[byteLocation] & bitMask);
result[0] |= (byte)(containByteArray[byteLocation + wholeByte] & ~bitMask);
for (int i = 1; i < wholeByte; ++i)
{
result[i] = containByteArray[byteLocation + i];
};
if(wholeByte != 4)
{
if (lastBit > 8)
{
result[wholeByte] = (byte)(containByteArray[byteLocation + wholeByte] & bitMask);
bitMask = ~(0xff >> lastBit % 8);
result[wholeByte] |= (byte)((containByteArray[byteLocation + wholeByte + 1] & bitMask) >> (8 - leftBit));
}
else if(leftBit > 0)
{
bitMask = ~(~bitMask | (0xff >> bitLocation + leftBit));
result[wholeByte] = (byte)((containByteArray[byteLocation + wholeByte] & bitMask) >> (8 - lastBit));
};
};
};
};
if(TypeLength(containTypeArray[targetIndex]) % 8 == 0)
{
if (wholeByte <= result.Length && (result[wholeByte - 1] & (0x80)) > 0)
{
for (int i = 3; i>=wholeByte; --i)
{
result[i] = 0xff;
};
};
}
else
{
if (wholeByte < result.Length && (result[wholeByte] & (0x01 << leftBit - 1)) > 0)
{
for (int i = 3; i > wholeByte; --i)
{
result[i] = 0xff;
};
result[wholeByte] |= (byte)(0xff << leftBit);
};
};
return result;
}
조금 길긴 하지만, SetByte의 역이라고 생각해주시면 됩니다.
SetByte의 분기점을 그대로 따라가면 금방 해당하는 부분을 찾으실 수 있습니다~
대신 여기서 눈여겨 볼 부분이 있는데요,
if(TypeLength(containTypeArray[targetIndex]) % 8 == 0)
{
if (wholeByte <= result.Length && (result[wholeByte - 1] & (0x80)) > 0)
{
for (int i = 3; i>=wholeByte; --i)
{
result[i] = 0xff;
};
};
}
else
{
if (wholeByte < result.Length && (result[wholeByte] & (0x01 << leftBit - 1)) > 0)
{
for (int i = 3; i > wholeByte; --i)
{
result[i] = 0xff;
};
result[wholeByte] |= (byte)(0xff << leftBit);
};
};
맨 마지막에 나오는 이 부분입니다!
SetBool에서는 없었던 부분인데요,
그냥 맨 뒤에 있는 정보만 가지고 가면 음수와 양수의 구분이 되지 않습니다!
지금까지 만든 것은 uint를 다루는 클래스였던 것이죠!
하지만 이 부분을 추가하면서 int로 바꿔줄 수 있게 됩니다.
GetBool에서 봤었던 그림입니다. 첫 번째 bit를 가져오는 것이었죠~
이걸 이용해서 첫 번째 bit가 1이면 음수, 0이면 양수로 출력해줍시다!
26은 00000000 00000000 00000000 00011010
-26은 11111111 11111111 11111111 11100110
26을 표현하기 위해선 -32~31까지 표현가능한 6비트가 되겠네요!
마침 뒤에서 6번째 bit는 음수와 양수의 차이를 나타내주기 좋게 따라가고 있습니다~
위에서 첫 번째 bit를 가져왔으므로, 결과 byte에서 비어있는 bit만 부호에 맞게 채워주면 되겠습니다!
만약 bit수가 다행스럽게 8로 떨어지는 경우에는 반복문으로 앞에 있는 byte를 0xFF (1111 1111)로 채웁니다.
그러나 8로 떨어지지 않는 경우에는, 일단 비어있는 byte에는 0xFF를 채운 뒤에
값이 있는 마지막 바이트로 이동해서 앞부분을 마저 1로 채워줍니다.
어차피 저장을 할 때 앞에 필요없는 부분만 떼고 저장을 했기 때문에,
그 값은 양수일 때 0, 음수일 때 1이었을 겁니다.
그래서 GetByte에만 이 부분을 넣어주면 음수와 양수를 깔끔하게 돌려받을 수 있는 것이죠!
만약, 이 클래스를 uint로만 쓰고 싶다면 빼시면 되고,
아니면 클래스에 Unsigned 체크를 따로 선언해서 On/Off형식으로 사용하셔도 무방하겠습니다~
이제 기본적인 입/출력은 완성이 되었고
public bool SetByteArray(byte[] wantArray)
{
if (wantArray.Length == containByteArray.Length)
{
containByteArray = wantArray;
return true;
}
else
{
return false;
};
}
public byte[] GetByteArray()
{
return containByteArray;
}
저장하고 있는 값을 가져오거나, 다른 데에서 가져온 값을 넣는 함수를 조금 추가해보았습니다.
요긴하게 쓸 수 있겠죠?
근데 이렇게 해놓으면 기능은 충분하겠습니다만, 매번 함수를 써야하므로 좀 더 보기좋게 해봅시다.
이미 눈치채셨겠지만, 타이틀 이미지에 보여드린 배열은 실제 배열이 아닙니다.
여기 들어있는 배열은 단순한 byte 배열밖에 없죠.
이 클래스 자체를 배열처럼 쓸 수 있게 해봅시다!
public object this[int targetIndex]
{
get
{
//가져오는 내용
}
set
{
//저장하는 내용
}
}
이런식으로 클래스 내부에 선언을 해주도록 합시다~
이러면 배열과 같은 모습으로 접근이 가능하겠죠~
이제 내용을 채워봅시다.
get
{
if(targetIndex > containTypeArray.Length)
{
return null;
};
if (containTypeArray[targetIndex] == Bit_Type.BOOL)
{
return GetBool(targetIndex);
}
else if (containTypeArray[targetIndex] == Bit_Type.FLOAT)
{
return BitConverter.ToSingle(GetByte(targetIndex), 0);
}
else
{
return BitConverter.ToInt32(GetByte(targetIndex), 0);
};
}
먼저 접근하는 위치가 지원하는 타입 수보다 크면 안되기 때문에, 걸러주었습니다.
이후로는 타입에 따라 움직이게 되는데요,
bool인 경우에는 그대로 전달,
float인 경우에는 byte를 float으로 변환해주는 함수를 쓰고,
int인 경우에는 byte를 int로 변환해주는 함수를 따로 쓸 수 있습니다.
이런 식으로 새로 추가하는 자료형이 있다면, 해석하는 것도 따로 추가해주시면 되겠습니다.
set
{
if(targetIndex > containTypeArray.Length)
{
return;
};
if (containTypeArray[targetIndex] == Bit_Type.BOOL)
{
SetBool(targetIndex, (bool)value);
}
else
{
Type valueType = value.GetType();
byte[] wantByte = new byte[0];
if (valueType == typeof(float))
{
wantByte = BitConverter.GetBytes((float)value);
}
else
{
wantByte = BitConverter.GetBytes((int)value);
};
if(wantByte.Length > 0)
{
SetByte(targetIndex, wantByte);
};
};
}
Set도 마찬가지로 배열 크기를 넘지 못하게 막아두었습니다.
Boolean은 그대로 넣게 되어있고,
나머지는 들어온 값의 타입을 식별하도록 해놓았습니다.
식별한 타입에 따라 byte를 읽어오도록 설정을 해놓은 뒤에
받은 값의 길이가 0이상인 경우, 값을 제대로 받아왔다고 판단하여 해당 byte 배열을 위치에 넣습니다.
여기까지하면 타이틀에 나온대로 배열에 접근하듯 가볍게 접근할 수 있겠죠~
<마치며>
그렇게 길지 않은 코드였는데 생각보다 포스팅이 길어졌네요.
조금 편리하게 불필요한 데이터량을 줄이려고 만들어 본 것을 올려보는 시간이었는데요,
제 코드를 다시 읽어보면서 생각하는 시간도 되는 것 같네요!
다른 분들께도 도움이 되는 자료였으면 좋겠구요,
다음에 또 괜찮은 것이 생각나면 돌아오도록 하겠습니다!
여기까지 읽어주신 모든 분들께 감사드립니다!