개요.
입력의 범위가 0x00000000 부터 0x7FFFFFFF(십진법 1073741824) 까지로 한정되어있다고 생각하자
기본적으로 팩토리오에선 모든 정수를 부호 있는 32비트 정수로 취급하기 때문에 제일 오른쪽 비트를 0번비트, 제일 왼쪽 비트를 31번비트라고 했을 경우 31번 비트가 부호를 의미하게된다. 0이면 양수, 1이면 음수
즉, 0x00000000~0x7FFFFFFF는 31번 비트가 0이기 때문에 양수로 취급되어 0 ~ 2,147,483,647을 의미하게 되고
0x80000000~0xFFFFFFFF는 31번 비트가 1이기 때문에 음수로 취급되어 ~2,147,483,648 ~ -1을 의미하게 된다
마찬가지로 만약 32비트 정수가 아니라 31비트 정수를 사용하고 싶다고 한다면.
0x00000000~0x3FFFFFFF는 30번 비트가 0이기 때문에 양수로 취급되어 0 ~ 1,073,741,823을 의미하게 되고
0x40000000~0x7FFFFFFF는 30번 비트가 1이기 때문에 음수로 취급되어 -1,073,741,824 ~ -1을 의미하게 된다
0x80000000~0xFFFFFFFF는 31비트 정수이기 때문에 존재하지 않는 경우다
0x00000000~0x7FFFFFFF라는 31비트 정수 입력이 들어올 때 이걸 32비트 정수로 변환하는걸 생각해보자
0x00000000~0x3FFFFFFF가 들어오는 경우는 양수가 들어오는 경우다.
이 경우에는 그대로 출력을 0x00000000~0x3FFFFFFF로 내보내주게 된다면 0 ~ 1,073,741,823를 의미한다는 사실은 변하지 않는다.
0x40000000~0x7FFFFFFF가 들어오는 경우는 음수가 들어오는 경우다.
이 경우에는 그대로 출력을 0x40000000~0x7FFFFFFF로 내보내주게 된다면 32비트의 세계에서는 1,073,741,824~2,147,483,647를 의미하기 때문에 기존의 값이 였던 -1,073,741,824 ~ -1와는 달라지게 된다.
만약 출력을 기존에 값에다가 31번비트를 1로 바꾸게 된다면,
즉 출력을 0xC0000000~0xFFFFFFFF로 내보내주게 된다면 -1,073,741,824 ~ -1를 의미하게 되어 원하는 결과가 나오게 된다.
입력의 제일 왼쪽 비트가 0일 땐(양수) 제일 왼쪽에 0을 한번더 붙이고
입력의 제일 왼쪽 비트가 1일 땐(음수) 제일 왼쪽에 1을 한번더 붙이면 부호확장이 이루어지게 된다.
이걸 팩토리오 회로로 구현해보자
구현1.
팩토리오 회로는 전부 부호있는 32비트 연산으로 이루어져있기 때문에 이를 고려하여서 설계하여야 한다.
제일먼저 생각해볼만한 회로는 입력이 0x40000000이상일 경우 0x80000000을 빼주는 것이다
output = (input>=0x40000000) ? input-0x80000000 : input
입력이 0x00000000~0x3FFFFFFF라면 (input>=0x40000000) 조건을 만족 못하므로 그냥 input이 그대로 output이 된다
입력이 0x40000000~0x7FFFFFFF라면 (input>=0x40000000) 조건을 만족하므로 원래 값에서 0x80000000을 뺀 값이 output이 된다.
즉 0xC0000000~0xFFFFFFFF 를 얻을 수 있다.
output = (input >=0x40000000) ? input-0x80000000 : input 를 좀더 팩토회로에 맞게 정리하자면
output = input - (input>=0x40000000) ? 0x80000000 : 0
32비트의 세계에서는 0x80000000을 빼거나 0x80000000을 더하거나 같은 결과를 얻기 때문에
output = input + (input>=0x40000000) ? 0x80000000 : 0
(input>=0x40000000)라는 연산은 수량비교조합기를 통해서 할 수 있다
다만 아쉬운 점은출력으로 내보낼 수 있는 값은 입력 값 또는 1만 내보낼 수 있다는 것이다. 그래서 위의 식은 아래처럼 정리해야한다.
output = input + ((input>=0x40000000) ? 1 : 0) * 0x80000000
1번회로 = (input>=0x40000000) ? 1 : 0
2번회로 = (1번회로) * 0x80000000
밑에 두개는 그냥 input 2틱 딜레이
output = input + (2번회로)
조합기 4개, 2틱으로 처리하였다
구현2.
2틱은 뭔가 아쉬워서 1틱으로 구현하는 방법은 없을까?를 고민하던 도중 떠오른 방법
아래 수식을 사용한다
output = ( (input<0x40000000) ? input : 0 ) + ( (input>=0x40000000) ? (input-0x80000000) : 0 )
0x40000000보다 작을때는 입력이 그대로 출력이 되고 클때는 0x80000000 뺀 값이 출력된다.
이 식은 한편 이렇게도 쓸수 있다
output = ( (input<0x40000000) ? input : 0 ) + ( (input-0x80000000>=0x40000000-0x80000000) ? (input-0x80000000) : 0 )
output = ( (input<0x40000000) ? input : 0 ) + ( (input-0x80000000>=-0x40000000) ? (input-0x80000000) : 0 )
이러면 오른쪽 식에서 조건문의 입력과 조건문의 출력이 동일하게 input-0x80000000이기 때문에 수량비교조합기에서 입력 값을 고를 수 있게 된다.
1번회로 = (input<0x40000000) ? input : 0
2번회로 = (input-0x80000000>=-0x40000000) ? (input-0x80000000) : 0
output = (1번회로) + (2번회로)
1틱만에 구현에 성공
이 회로에 대해 단점은 사용하는 신호들에 대해서 상수조합기(3번)를 쓰든 뭘하든 해서 입력 0x80000000을 따로 입력으로 넣어줘야 한다는 것이다
그래서 완전 임의의 신호에 대해서는 사용못함. 아니면 존재하는 모든 신호 다 넣던가 ㅋㅋ
구현3.
또 다른 방법이 없을까 고민하던 도중 떠오른것
shift left 연산 <<는 왼쪽으로 비트를 옮기면서 무조건 가장 오른쪽에 0을 채워넣는다
만약 0b00001111 (15)가 입력이고 <<1 연산을 한다면 출력은 0b00011110 (30)이 됨. *2를 하는것과 같다
이게 8비트 부호있는 자료형이라고 가정한다면
만약 0b11111111 (-1)가 입력이고 <<1 연산을 한다면 출력은 0b11111110 (-2)이 됨. 음수일때도 역시 *2를 하는것과 같다
만약 0b01010101 (85)가 입력이고 <<1 연산을 한다면 출력은 0b10101010 (-86)이 됨.
이 경우에는 *2는 아닌것 같아보이긴 하지만 *2를 한 값인 170에서 256(2^8)을 빼면서 -86이 된거라 역시 본질은 *2와 같다고 볼 수 있음
부호있는 자료형일때는 아무튼 결과적으로는 부호 비트가 바뀔 수 있다
shift right 연산 >> 는 오른쪽으로 비트를 옮기면서 제일 왼쪽에 비트를 채워 넣는 방법이 두가지가 있는데
무조건 0을 채운다 -> 논리 시프트
원래 제일 왼쪽에 비트가 0이였으면 0, 1이였으면 1을 채운다 -> 산술 시프트
입력이 0b11111110(-2)이고 >>1 연산을 한다면 논리시프트 일 경우 0b01111111 (127)이 나온다
산술시프트일 경우에는 제일 왼쪽 비트가 1이기 때문에 그자리를 1로 채워서 0b11111111(-1)이 나온다
<<1이 곱하기 2와 같았던것 처럼, 산술시프트 >>1는 나누기 2와 같음
팩토리오에서 >> 연산은 산술시프트를 하도록 되어있어서 /2 처럼 사용할 수 있음
만약 논리시프트>>가 필요하다면 아래글을 참고하자
https://gall.dcinside.com/mgallery/board/view/?id=factorio&no=53092
개쩌는 1틱 논리시프트(펌)https://forums.factorio.com/viewtopic.php?p=550049#p550049 [0.17.45] Arithmetic Combinator: right-shift (>gall.dcinside.com아무튼 이 특성을 이용해서 31비트를 32비트로 부호확장하는 것이 가능하다
output = ((input<<1)>>1)
또는
output = ((input*2)/2)
2틱을 사용하긴 했지만 정말 간단하다
조합기를 2개만 사용하기 때문에 구현1의 완벽한 상위호환
구현4.
여기서 한번 더 고민을 한다. 1틱으로는 기똥찬 방법이 없을까?
그리고 떠올렸다
기본적인 아이디어는 구현3과 같다
단지 입력으로 초록선과 빨간선을 둘다 연결할 뿐
이렇게 연결할 경우 input+input의 값이 조합기의 입력으로 들어가기 때문에 0 tick으로 *2를 한 것과 같다!
나머지는 /2 또는 >>1을 하기만 하면 끝
1 조합기 & 1 tick
하루 종일 좋은 아이디어 찾아 고민했는데 정답은 아주 심플 그자체
자신이 원하는 회로 목표를 달성하기 위해 상당히 다양한 관점으로 접근하는 괜찮은 예시가 될 것 같아서 이렇게 한번 글로 정리를 해보았음
요새 팩토 회로 다루면서 느끼는 점은
1. 덧셈 연산은 zero tick으로 할 수 있기 때문에 이걸 최대한 활용하면 tick 수 줄이기가 참 유용하다
2. 노란별(각자) 연산도 회로 부피 줄이기에 매우 유용하고 임의의 신호를 처리하기 참 좋기때문에 적극 사용하자
3. 수량비교 조합기는 임의의 다수 신호 중에서 원하는 신호만 선별하여 필터링하기에 정말 유용한 도구
이걸 어느정도 깨닫고 나니 회로질이 점점 재밌어지는 것 같다 ㅋㅋ
축소변환이 이제 남아있네
아니 이것도 그냥 빼버리면 되나?
축소변환?
32->31는 그냥 & 0x7FFFFFFF 하면 끝이잖아