도입
우리가 일상에서 쓰는 숫자는 대부분 10진수입니다.
0,1,2,3,4,5,6,7,8,9
컴퓨터 내부에서는 전기적 상태를 기준으로 데이터를 표현하기 때문에 0과 1만 사용하는 2진수가 훨씬 자연스럽습니다.
문제는 2진수만 그대로 읽고 쓰기에는 길이가 너무 길어진다는 점입니다. 그래서 사람이 보기 편한 표현으로 8진수나 16진수를 함께 사용합니다. 특히 16진수는 4비트 단위와 정확히 대응되기 때문에 메모리 주소, 바이트 값, 색상 코드, 디버깅 출력에서 자주 등장합니다.
즉 진법을 이해한다는 것은 단순히 숫자 변환 공식을 외우는 것이 아니라, 컴퓨터가 숫자를 어떻게 표현하고 사람이 그것을 어떻게 읽기 쉽게 바꿔 쓰는지 이해하는 일에 가깝습니다.
필요성
예를 들어 10진수 255 는 2진수로 11111111, 16진수로 FF 입니다. 값은 같지만 표현 방식이 다릅니다.
컴퓨터 내부 비트 패턴을 확인할 때는 2진수가 직접적이고, 바이트 단위 값을 짧게 표현할 때는 16진수가 편합니다. 반면 사용자가 보는 수량이나 금액, 카운트는 보통 10진수로 표현하는 편이 자연스럽습니다.
그래서 진법은 저수준 비트 표현, 메모리 주소, 문자 인코딩, 색상 코드, 네트워크 패킷, 파일 포맷, 디버깅 로그를 이해하는 기본 도구가 됩니다.
- 비트와 바이트 단위의 내부 표현을 이해할 때
- 2진수와 16진수로 메모리 값이나 주소를 읽을 때
- Java 같은 언어에서 정수 리터럴을 여러 진법으로 표현할 때
- 색상 코드, 문자 코드, 네트워크 패킷, 바이너리 포맷을 분석할 때
정의
진법은 영어로 base 또는 radix 라고 부릅니다. 예를 들어 10진수는 base 10, 2진수는 base 2, 16진수는 base 16입니다.
base가 10이면 사용할 수 있는 기본 숫자는 0 부터 9 까지 10개입니다. base가 2이면 0, 1 두 개만 사용합니다. base가 16이면 0 부터 9 까지와 A 부터 F 까지를 함께 사용합니다.
중요한 점은 숫자 기호만 달라지는 것이 아니라, 자리의 가중치도 진법에 따라 달라진다는 것입니다. 10진수에서는 오른쪽부터 10^0, 10^1, 10^2 이고, 2진수에서는 2^0, 2^1, 2^2 입니다.
"진법의 본질은 숫자 모양이 아니라
각 자리가 몇의 거듭제곱을 의미하는지 정하는 자릿값 체계에 있습니다."
핵심 원리
10진수 345 는 단순히 3, 4, 5라는 숫자를 붙인 것이 아닙니다. 실제 값은 3 × 10^2 + 4 × 10^1 + 5 × 10^0 입니다.
같은 원리로 2진수 1011 은 1 × 2^3 + 0 × 2^2 + 1 × 2^1 + 1 × 2^0 이며, 10진수로는 11 입니다.
즉 어떤 진법이든 각 자리의 가중치는 오른쪽부터 base^0, base^1, base^2 순서로 커집니다. 이 원리만 이해하면 모든 진법 변환의 핵심은 거의 잡은 것입니다.
자리값 계산 공식
(dn ... d2 d1 d0)_base
= dn × base^n
+ ...
+ d2 × base^2
+ d1 × base^1
+ d0 × base^0
주요 진법
| 진법 | Base | 사용 기호 | 예시 | 실무 해석 |
|---|---|---|---|---|
| 2진수 | 2 | 0, 1 |
1011₂ |
비트 단위 내부 표현에 가장 직접적 |
| 8진수 | 8 | 0~7 |
13₈ |
3비트 단위 표현과 연결되지만 현대 실무에서는 16진수보다 덜 자주 사용 |
| 10진수 | 10 | 0~9 |
11₁₀ |
사람이 읽는 일반적인 수량 표현 |
| 16진수 | 16 | 0~9, A~F |
B₁₆ |
4비트 단위 표현과 연결되어 바이트, 주소, 색상 코드 표현에 편리 |
변환 1. 다른 진법에서 10진수로
2진수 1011₂ 을 10진수로 바꿔 보겠습니다.
1011₂
= 1 × 2^3 + 0 × 2^2 + 1 × 2^1 + 1 × 2^0
= 8 + 0 + 2 + 1
= 11₁₀
16진수 2A₁₆ 도 같은 방식입니다. 여기서 A 는 10을 의미합니다.
2A₁₆
= 2 × 16^1 + A × 16^0
= 2 × 16 + 10 × 1
= 32 + 10
= 42₁₀
변환 2. 10진수에서 다른 진법으로
10진수 42 를 2진수로 바꿔 보겠습니다.
42 ÷ 2 = 21 ... 0
21 ÷ 2 = 10 ... 1
10 ÷ 2 = 5 ... 0
5 ÷ 2 = 2 ... 1
2 ÷ 2 = 1 ... 0
1 ÷ 2 = 0 ... 1
나머지를 아래에서 위로 읽기
42₁₀ = 101010₂
같은 값을 16진수로 바꿀 때는 16으로 나눕니다.
42 ÷ 16 = 2 ... 10
10은 16진수에서 A
42₁₀ = 2A₁₆
변환 3. 2진수와 16진수
16은 2^4 이므로, 16진수 한 자리는 정확히 2진수 4자리와 대응됩니다.
| 16진수 | 2진수 | 10진수 |
|---|---|---|
0 |
0000 |
0 |
1 |
0001 |
1 |
2 |
0010 |
2 |
3 |
0011 |
3 |
4 |
0100 |
4 |
5 |
0101 |
5 |
6 |
0110 |
6 |
7 |
0111 |
7 |
8 |
1000 |
8 |
9 |
1001 |
9 |
A |
1010 |
10 |
B |
1011 |
11 |
C |
1100 |
12 |
D |
1101 |
13 |
E |
1110 |
14 |
F |
1111 |
15 |
1010 1111₂
= A F₁₆
= AF₁₆
반대로
0xAF
= A F
= 1010 1111₂
Java 리터럴
int decimal = 42; // 10진수
int binary = 0b101010; // 2진수
int octal = 052; // 8진수
int hex = 0x2A; // 16진수
System.out.println(decimal); // 42
System.out.println(binary); // 42
System.out.println(octal); // 42
System.out.println(hex); // 42
052 는 10진수 52가 아니라 8진수 52입니다. 즉 값은 5 × 8 + 2 = 42 입니다. 앞에 붙은 0 하나가 의미를 바꾸므로, 실무에서는 8진수 리터럴을 조심해서 써야 합니다.Java 변환
Integer.toString(value, radix) 와 Integer.parseInt(text, radix) 로 원하는 진법의 문자열 변환과 해석을 처리할 수 있다int value = 42;
String binary = Integer.toString(value, 2);
String octal = Integer.toString(value, 8);
String decimal = Integer.toString(value, 10);
String hex = Integer.toString(value, 16);
System.out.println(binary); // 101010
System.out.println(octal); // 52
System.out.println(decimal); // 42
System.out.println(hex); // 2a
int parsed = Integer.parseInt("2A", 16);
System.out.println(parsed); // 42
패턴 1. 2진수는 비트 이해의 기본이다
2진수는 0 과 1 만 사용합니다. 그래서 한 자리는 하나의 비트와 직접 대응됩니다.
예를 들어 권한 플래그, 상태 비트, 네트워크 프로토콜 필드처럼 각 비트가 별도의 의미를 가지는 경우 2진수로 보면 훨씬 직관적입니다.
예시: 권한 플래그
읽기 = 001₂
쓰기 = 010₂
실행 = 100₂
읽기 + 쓰기 = 011₂
패턴 2. 16진수는 2진수를 사람이 읽기 쉽게 압축한다
예를 들어 1바이트는 8비트입니다. 이것을 2진수로 쓰면 8자리지만, 16진수로 쓰면 2자리면 충분합니다.
1111 1111₂
= FF₁₆
= 255₁₀
이 때문에 16진수는 메모리 덤프, 색상 코드, 문자 코드, 해시 값, 바이너리 데이터 출력에서 매우 자주 등장합니다.
패턴 3. 8진수는 3비트 단위와 연결된다
8진수는 0 부터 7 까지의 숫자를 사용합니다. 8은 2^3 이므로 8진수 한 자리는 2진수 3자리와 대응됩니다.
111 101 100₂
= 754₈
예를 들어 Unix 계열 파일 권한에서 755, 644 같은 표기는 8진수 기반으로 읽는 것이 자연스럽습니다.
한계와 주의점
10 이라는 문자열은 10진수에서는 십이지만, 2진수에서는 이, 8진수에서는 팔, 16진수에서는 십육입니다. 즉 같은 문자열이라도 어떤 진법으로 읽느냐에 따라 값이 달라집니다.
또 프로그래밍 언어마다 접두사 규칙이 다를 수 있습니다. Java에서는 0b 가 2진수, 0x 가 16진수, leading 0 이 8진수를 의미합니다. 따라서 010 을 10진수 10으로 착각하면 버그가 생길 수 있습니다.
마지막으로 진법 변환은 표현을 바꾸는 일이지, 정수 타입의 범위나 오버플로 문제를 없애 주는 것이 아닙니다. 같은 값이라도 타입이 표현할 수 있는 범위를 넘어가면 문제가 됩니다.
- 같은 숫자 문자열도 진법에 따라 전혀 다른 값을 의미할 수 있음
- Java에서 leading
0은 8진수 리터럴로 해석될 수 있음 - 16진수의
A~F는 10~15를 뜻함 - 진법 변환은 표현 변환이지 타입 범위 문제를 해결하는 것은 아님
자주 하는 실수
1010을 항상 천십으로만 생각함- 2진수의 자리값을
10^n으로 계산함 - 16진수
A,B,F를 문자로만 보고 숫자 값으로 읽지 못함 - Java에서
010을 10진수 10으로 착각함 - 2진수와 16진수 변환에서 4비트 단위로 끊지 않음
- 진법 변환과 부호 표현, 보수 표현, 오버플로 문제를 한꺼번에 섞어 생각함
실무 루틴
- 먼저 숫자 문자열이 몇 진법인지 확인한다.
- 접두사
0b,0x, leading0같은 언어별 표기 규칙을 확인한다. - 10진수로 바꿀 때는 각 자리 × 진법의 거듭제곱으로 계산한다.
- 10진수에서 다른 진법으로 바꿀 때는 목표 진법으로 반복 나누기와 나머지를 사용한다.
- 2진수와 16진수는 4비트 단위로 직접 묶어 변환한다.
- 실무 코드에서는 직접 계산보다 표준 라이브러리의 parse/format 함수를 우선 사용한다.
디버깅
0b, 0x, leading 0 접두사를 확인한다.점검 체크리스트
- 이 숫자는 몇 진법으로 쓰였는가
- 언어별 접두사 규칙이 적용되었는가
- 자리값 계산에서 base^n 을 제대로 사용했는가
- 2진수 ↔ 16진수 변환에서 4비트 그룹이 맞는가
- parseInt / toString 의 radix 인자가 맞는가
- 진법 문제가 아니라 오버플로 또는 부호 표현 문제는 아닌가
요약
- ✅ 진법은 숫자를 표현할 때 사용하는 base 또는 radix 다.
- ✅ 위치적 기수법에서는 각 자리의 값이 숫자 × base의 거듭제곱으로 계산된다.
- ✅ 2진수는
0,1두 기호만 사용한다. - ✅ 8진수는
0~7을 사용하며 3비트 단위와 대응된다. - ✅ 10진수는 일상에서 가장 일반적으로 쓰는 base 10 표현이다.
- ✅ 16진수는
0~9,A~F를 사용하며 4비트 단위와 대응된다. - ✅ Java 정수 리터럴은 10진수, 16진수, 8진수, 2진수로 표현할 수 있다.
- ✅ 같은 문자열도 진법이 다르면 실제 값이 달라질 수 있다.
- ✅ 실무에서는 직접 계산보다 표준 라이브러리의 parse/format 기능을 우선 사용하는 편이 안전하다.