오늘 백준 문제를 풀고 코드를 이리저리 만져보다 발견한 주의점이다.
해당 문제: https://csloth.tistory.com/24
백준 2420번 사파리 월드 [C언어]
문제 출처: https://www.acmicpc.net/problem/2420 2420번: 사파리월드 첫째 줄에 두 도메인의 유명도 N과 M이 주어진다. (-2,000,000,000 ≤ N, M ≤ 2,000,000,000) www.acmicpc.net Sol) #include int main()..
csloth.tistory.com
C언어에서 정수 자료형의 저장 방식은 다음과 같다.
1. 2진수로 저장한다.
2. 어떤 한 자료형에서, 가장 왼쪽 비트는 부호 비트로 사용한다(0이면 양수, 1이면 음수로 판단).
3. 뺄셈, 혹은 음수 저장에는 2's complement(2의 보수) 방식을 사용한다.
위 문제에서 입력값의 제한 범위는 -2,000,000,000 ~ 2,000,000,000 이었다. int 자료형의 정수 저장 범위는 -2,147,483,648 ~ 2,147,483,647 이므로, 위 문제의 입력값들은 int 자료형으로 문제 없이 저장할 수 있다. 그렇다면, 위 문제를 다음과 같은 코드로 풀어도 될까?
#include <stdio.h>
int main(){
long long s1, s2;
long long diff;
scanf("%d %d", &s1, &s2); //s1 과 s2는 모두 int범위 안에 들어오므로 %d로 받아도 될까?
diff = s1 >= s2 ? s1 - s2 : s2 - s1;
printf("%lld\n", diff);
return 0;
}
1. s1과 s2모두 입력 과정에서는 int 범위 안에 들어온다.
2. 우리는 s1과 s2를 long long으로 선언 해 줬다.
3. 따라서, 입력만 int형태로 받을 뿐이지, 계산 결과는 int가 아닌 long long형이 되므로 계산 결과에도 문제가 없을 것이다.
그렇다면 위와 같은 코드에 s1 에는 2,000,000,000 을, s2 에는 -2,000,000,000 을 저장해주면 diff는 어떤 값으로 출력될까?
우리의 예상대로라면, 위 코드는 아무 문제가 없으므로 4,000,000,000 이 출력돼야 한다.
하지만 놀랍게도, 위 코드는 4589934592 라는 이상한 값을 출력하게 된다.
위와 같은 문제는 C언어에서 정수를 저장하는 방식, 그 중에서도 특히 '음수인 정수'를 저장하는 방식때문에 발생하게 된다.
위 코드에서 s1과 s2는 모두 long long 형이다. 즉, 8바이트(64비트) 만큼의 저장공간을 보유하고 있을 것이다. 그런데 우리가 %d, 즉 4바이트(32비트)만큼 버퍼에서 정수를 읽어와 8바이트인 저장공간에 저장하게 되면 다음과 같이 저장된다.
<2,000,000,000을 %d로 버퍼에서 읽어 올 당시>
2^31 | 2^30 | 2^29 | 2^28 | 2^27 | 2^26 | 2^25 | 2^24 |
0(부호비트) | 1 | 1 | 1 | 0 | 1 | 1 | 1 |
2^23 | 2^22 | 2^21 | 2^20 | 2^19 | 2^18 | 2^17 | 2^16 |
0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
2^15 | 2^14 | 2^13 | 2^12 | 2^11 | 2^10 | 2^9 | 2^8 |
1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
<2,000,000,000이 s1에 저장된 형태>
2^63 | 2^62 | 2^61 | 2^60 | 2^59 | 2^58 | 2^57 | 2^56 |
0(부호비트) | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2^55 | 2^54 | 2^53 | 2^52 | 2^51 | 2^50 | 2^49 | 2^48 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2^47 | 2^46 | 2^45 | 2^44 | 2^43 | 2^42 | 2^41 | 2^40 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2^39 | 2^38 | 2^37 | 2^36 | 2^35 | 2^34 | 2^33 | 2^32 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2^31 | 2^30 | 2^29 | 2^28 | 2^27 | 2^26 | 2^25 | 2^24 |
0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 |
2^23 | 2^22 | 2^21 | 2^20 | 2^19 | 2^18 | 2^17 | 2^16 |
0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
2^15 | 2^14 | 2^13 | 2^12 | 2^11 | 2^10 | 2^9 | 2^8 |
1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
< -2,000,000,000을 %d로 버퍼에서 읽어 올 당시>
2^31 | 2^30 | 2^29 | 2^28 | 2^27 | 2^26 | 2^25 | 2^24 |
1(부호비트) | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
2^23 | 2^22 | 2^21 | 2^20 | 2^19 | 2^18 | 2^17 | 2^16 |
1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
2^15 | 2^14 | 2^13 | 2^12 | 2^11 | 2^10 | 2^9 | 2^8 |
0 | 1 | 1 | 0 | 1 | 1 | 0 | 0 |
2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
< -2,000,000,000이 s2에 잘못 저장된 형태>
2^63 | 2^62 | 2^61 | 2^60 | 2^59 | 2^58 | 2^57 | 2^56 |
0(long long에서의 부호비트) | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2^55 | 2^54 | 2^53 | 2^52 | 2^51 | 2^50 | 2^49 | 2^48 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2^47 | 2^46 | 2^45 | 2^44 | 2^43 | 2^42 | 2^41 | 2^40 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2^39 | 2^38 | 2^37 | 2^36 | 2^35 | 2^34 | 2^33 | 2^32 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2^31 | 2^30 | 2^29 | 2^28 | 2^27 | 2^26 | 2^25 | 2^24 |
1(int에서의 부호 비트) | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
2^23 | 2^22 | 2^21 | 2^20 | 2^19 | 2^18 | 2^17 | 2^16 |
1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
2^15 | 2^14 | 2^13 | 2^12 | 2^11 | 2^10 | 2^9 | 2^8 |
0 | 1 | 1 | 0 | 1 | 1 | 0 | 0 |
2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
각 숫자들이 저장된 형태를 보면, 양수일 경우 %d로 읽어와도 부호비트가 0이기때문에 아무런 문제도 발생하지 않는다.
하지만 음수일 경우 %d로 읽어오면 64비트 저장공간 중 2^0부터 시작해 처음 32비트만큼만 업데이트되게 된다.
그런데, 32비트에서의 부호비트의 위치와 64비트에서의 부호비트의 위치가 다르기때문에 형식지정자보다 더 큰 자료형에 음수인 정수를 저장하게 되면 원래 값과는 전혀 다른 어떤 한 양수로 취급돼 버리게 된다.
이때문에 위 코드에서는 -2,000,000,000 이 양수인 6589934592 이 되어 4589934592 와 같은 값이 나오게 되는 것이다.
-2,000,000,000을 long long 자료형에 올바르게 저장하기 위해서는 이를 %lld로 64비트만큼 읽어와 위에서는 업데이트 되지 못했던 나머지 32개의 비트들도 아래와 같이 모두 1로 반전될 수 있게 해 주어야 한다.
< -2,000,000,000이 s2에 올바르게 저장된 형태>
2^63 | 2^62 | 2^61 | 2^60 | 2^59 | 2^58 | 2^57 | 2^56 |
1(long long에서의 부호비트) | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
2^55 | 2^54 | 2^53 | 2^52 | 2^51 | 2^50 | 2^49 | 2^48 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
2^47 | 2^46 | 2^45 | 2^44 | 2^43 | 2^42 | 2^41 | 2^40 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
2^39 | 2^38 | 2^37 | 2^36 | 2^35 | 2^34 | 2^33 | 2^32 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
2^31 | 2^30 | 2^29 | 2^28 | 2^27 | 2^26 | 2^25 | 2^24 |
1(int에서의 부호 비트) | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
2^23 | 2^22 | 2^21 | 2^20 | 2^19 | 2^18 | 2^17 | 2^16 |
1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
2^15 | 2^14 | 2^13 | 2^12 | 2^11 | 2^10 | 2^9 | 2^8 |
0 | 1 | 1 | 0 | 1 | 1 | 0 | 0 |
2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
<결론>
음수인 정수를 다룰 때에는 항상 변수의 자료형의 크기와 형식 지정자가 읽어올 자료형의 크기를 맞춰 주자.
'Notes for C' 카테고리의 다른 글
후위 증감연산자와 전위 증감연산자의 비밀 (0) | 2022.08.17 |
---|---|
scanf 함수 (0) | 2022.08.11 |
메모리 관련 함수 (0) | 2022.08.07 |