2.2-Integer_Representations
bit로 Integer 를 표현하는 2가지 방법을 알아본다.
양의 정수를 표현하는 방법과 음의 정수를 포함한 모든 정수를 표현하는 2가지 방법이다.
사실 이 2가지 방법은 수학적 특성이나 machine-level 구현에서 연관성이 있다.
또한, 기존의 표현에서 다른 길이의 표현으로 옮기는 방법에 대해 살펴본다.
C언어는 다양한 정수형 데이터 타입이 존재한다.
이들은 32bit, 64bit 에 따라 "전형적인" 정해진 Byte 를 점유한다.
기본적으로 음수 표현을 지원하며, unsigned
키워드를 통해 양수만 표현함으로서 범위를 늘릴 수 있다.
한 가지 중요한 점은 음수가 가능한 절댓값이 정수보다 1 크다는 점이다.
ex) short
:
machine 에 따라 다르기 때문에,
C언어 표준에서 보장하는 범위가 존재한다.
더 작은 범위이며, 양수와 음수의 절댓값 크기가 같다.
각각의 bit를 표현하기 위해
각각의
우리는 해당 bit vector를 양의 정수를 표현하는 함수로
(Binary 2 Unsigned)
해당 함수는 다음과 같이 정의될 수 있다.
ex)
따라서,
이 함수의 중요한 특징은 특정 정수값
즉, 일대일 맵핑이 가능하다.
일대응 대응이기 때문에, 특정 정수는 bit vector 로 변환 가능하며, 반대로 bit vector 를 특정 정수로 표현 가능하다.
이를 역함수
음수 표현은 보통 2의 보수 방식으로 표현된다.
우리는 이 방식을
여기서 MSB(Most Significant Bit) 는
양수 표현에 비해 이 마지막 bit,
이로 인해,
해당 가중치로 인해, 최솟값은
반대로 최댓값은
해당 함수 또한, bit vector 와 일대일 대응이 되는
역시 역함수를
2의 보수 즉, 정수 표현에서는 최솟값이 최댓값보다 절댓값이 크다.
구체적으로,
이 점은 간혹 문제를 일으키는데, 비트패턴의 절반은 음수가, 절반은 비음수가 차지한다.
근데,
Unsigned value 와 Signed Value 간에 다음과 같은 관계가 성립한다.
특이한 점으로
부호 비트를 사용하는 방법, 1의 보수 방법 등이 있으나,
2의 보수가 편리하기 때문에 2의 보수를 주로 사용한다.
여담으로... 수학적 구조 중에 군의 구조를 이룬다고 볼 수 있다.
특히,
C언어에서는 같은 사이즈의 데이터 간의 casting 은 비트 표현을 바꾸지 않는다.
단, 해석이 바뀌기 때문에 그 값은 다르게 출력된다.
ex) short v = -12345
, (unsigned) v
의 값은 53191 이다. (둘의 비트패턴은 동일하다.)
비트표현이 바뀌지 않지만, 값은 바뀐다는 점을 이용해,
라고 정의할 수 있다. (정의그대로, 2의 보수로 표현된 값을 비트패턴으로, 그것을 다시 Unsigned value 로 해석한다) 단, 범위에 주의해야한다.
바로 계산할 수 있는 형태로는
성립하는 이유는 MSB 가
또는, 비트표현에서
라고 할 수 있다.
C언어 표준은 부호 있는 정수 표현에 대해 규정하지 않지만, 대부분의 machine 은 2의 보수법을 사용한다.
보통 정수는 부호 있는 정수 표현으로 저장된다.
C언어는 부호 있는 표현, 없는 표현간의 casting 이 가능하다.
표준은 역시 그 방법에 대해서는 규정하지 않는다.
하지만, 대부분의 machine 은 비트패턴을 변하지 않는 상태로의 변환을 지원한다.
앞에서 계속 C언어의 표준과 그 구현을 같이 보고 있다.
표준은 보통 기능 자체를 제시하며, 그 구현은 구현체에 맡기는 경향이 있다.
이에 대한 GPT의 답변은
표준은 기능 제시 위주이며, 구현은 구현체, 컴파일러에게 맡기는 편이라고 한다.
이를
그 이유로는 시스템 프로그래밍에 사용되는 만큼, 각 machine 에 맞게 최적화된 구현을 맡기려는 의도가 있다.
각 타입으로의 변환은 문법에 의해 몀명시적(explicit) 일수도, 암시적(implicit) 일 수 있다.
이 암시적 변환은 종종 이해할 수 없는 결과를 내거나, 버그를 일으키는 원인이 되기도 한다.
C언어는 음수가 아니라고 가정하고, 부호 있는 정수를 부호 없는 정수로 암시적으로 형변환한다.
이 때,
의 결과는 False
가 된다.
그 이유는 뒤의
따라서,
이므로 False
이다.
큰 범위의 데이터를 작은 범위의 데이터 축소는 불가능할 수 있지만,
작은 범위의 데이터를 큰 범위의 데이터로 확장하는 것은 가능하다.
각 자료형의 한계값은 limits.h
에 저장되어 있다.
그 안에는
#define INT_MAX 2147483647
#define INT_MIN = (-INT_MAX - 1)
로 표현되어 있다.
이런 표기에는 2의 보수의 비대칭성, 즉 양수 음수의 표현범위가 다르다는 점이 포함되어있다.
Unsigned Number 에 대한 확장은
이 방법을
이다.
Signed Number 에 대한 확장은 MSB의 값으로 채워주면 된다.
이를
이다.
조금 더 살펴보면, 양수일때는 MSB 가 0이기 때문에
반대로 음수일 때는 MSB 가 1이기 떄문에 1을 나열하는 것과 같다.
이 때, 1을 나열하는 것이 정말 같은 값을 표현하는지 생각해보면,
위의 표기를 이용하면,
이다. MSB 가 1임을 가정했다.
이때,
이므로,
변환에도 상대적인 순서가 존재한다.
먼저 size 에 대한 변환 후, 부호에 대한 변환을 거친다.
ex) short
-> unsigned int
음수인 short
를 변환하면, 먼저 size 를 맞추기 위해
상대적으로 큰 수로 변한다.
int
를 short
로, 다시 int
로 형변환을 하면, 저장하는 bit가 한번 줄어들었기 때문에
같은 int
지만 값이 달라진다.
더 작은 bit의 데이터로 줄어들 때, 그 차이만큼의 bit가 잘린다(truncate).
그 점 때문에 부호없는 정수에 대해,(Unsigned)
이유는
부호 있는 정수와 부호 없는 정수간 변환에 비트의 패턴이 바뀌지 않기 때문에,
부호 있는 정수에서도 비슷한 관계가 성립한다.
단, 부호 있는 정수에서는
부호 없는 정수로 값을 계산하고, 비트패턴이 안 변한다는 사실을 이용한 것이다.
암시적 형변환은 티가 잘 나지 않기 때문에 여러 버그의 원인이 되곤한다.
float sum_elements(float a[], unsigned length) {
int i;
float result = 0;
for(i = 0; i <= length - 1; i++)
result += a[i];
return result;
}
이 코드는 length = 0
일 때, 에러가 발생한다.
그 이유는, unsigned
인 length
에 0
이 저장되어 있는데, lenth - 1
은 -1
이 아닌, int_max
가 되기 때문에 의도하지 않은 메모리 접근이 발생한다.
따라서, i <= length - 1
를 i < length
로 바꾸면 해결 가능하다.
// library function
size_t strlen(const char *s);
int strlonger(char *s, char *t) {
return strlen(s) - strlen(t) > 0;
}
32bit 에서 size_t
는 unsigned
로 정의되어 있다.
이 코드는 t
가 더 긴 문자열일 때, 즉, strlen(s) - strlen(t) < 0
일 때 문제가 생긴다.
이 때 해당 식의 값은 음수이나, unsigned
로 계산되기 때문에, True
라고 뜬다.
return strlen(s) > strlen(t)
로 해결 가능하다.
이러한 미묘한 버그로 인해, 최근 언어에서는 지원 자체를 안하는 경우가 많다.
java 는 모두 부호 있는 정수이며, >>
와 >>>
로 분리하기도 한다.
부호 없는 정수는 우리가 수적 해석 없이 비트패턴으로서 해석할 때 도움이 된다.
이것은 우리가
또한, 수학 관련 패키지, modulo, 다중정밀도 산술(큰 수에 대한 연산) 같이 숫자가 words 배열로 이루어진 곳에서 도움이 된다.