본문으로 건너뛰기

Ch 2.3 진법 (Radix)

컴퓨터는 0과 1만을 처리할 수 있는 전자 기기입니다. 하지만 우리는 일상생활에서 10진수(Decimal)를 주로 사용하고 있습니다. 변수를 더욱 깊게 이해하기 위해 컴퓨터가 값을 어떻게 저장하고 읽는지를 아는 것이 중요합니다.

1. 진법의 개념​

진법(Numeral System)이란 수를 표현하는 방식입니다. '몇 가지 숫자를 사용하는가'에 따라 진법이 달라집니다.

진법사용 숫자활용예시(10을 표현)
10진법0~9 (10가지)일상생활10
2진법0, 1 (2가지)컴퓨터 내부1010
8진법0~7 (8가지)파일 권한 등12
16진법09, AF (16가지)색상, 메모리 주소A
10진수 15를 각 진법으로:

10진수: 15
2진수: 1111 (1×8 + 1×4 + 1×2 + 1×1)
8진수: 17 (1×8 + 7×1)
16진수: F (15×1)

왜 컴퓨터는 2진수를 사용할까?​

컴퓨터의 기본 소자인 트랜지스터 는 전기가 흐르거나(1) 흐르지 않거나(0), 두 가지 상태만 갖습니다. 이 두 상태를 조합하여 모든 숫자와 문자를 표현합니다.

전기 흐름:  ━━━━┐     ┌━━━━┐     ┌━━━━
전기 없음: └─────┘ └─────┘
[0] [1] [0] [1] [0] = 01010 = 10(십)

2. 자바에서 다양한 진법 리터럴 사용하기​

자바는 서로 다른 진법을 구분하기 위해 특수한 접두사를 사용합니다.

진법접두사예시
10진수(없음)10
2진수0b 또는 0B0b1010
8진수0 (숫자 0)012
16진수0x 또는 0X0xA
public class RadixLiteral {
public static void main(String[] args) {
int decNum = 10; // 10진수
int binNum = 0b1010; // 2진수 (0b 접두사)
int octNum = 012; // 8진수 (0 접두사)
int hexNum = 0xA; // 16진수 (0x 접두사)

// 모두 같은 값(10)을 저장하고 있음
System.out.println("10진수: " + decNum); // 10
System.out.println("2진수: " + binNum); // 10
System.out.println("8진수: " + octNum); // 10
System.out.println("16진수: " + hexNum); // 10

System.out.println("모두 같은 값? " + (decNum == binNum && binNum == octNum && octNum == hexNum));
// true - 자바는 내부적으로 모두 10진수로 처리
}
}
경고

8진수 함정: 012는 10이 아니라 8진수 12(십진수 10)입니다! 실수로 0을 앞에 붙이면 8진수로 해석되므로 주의하세요.

int wrong = 010; // 8진수 10 = 십진수 8 (!)
int right = 10; // 십진수 10

3. 비트(bit)와 바이트(byte)​

bit (비트): 컴퓨터의 최소 저장 단위. 0 또는 1.
│
├── 4 bits = 1 nibble (니블)
│
└── 8 bits = 1 byte (바이트)
│
├── 1,024 bytes = 1 KB (킬로바이트)
│
├── 1,024 KB = 1 MB (메가바이트)
│
├── 1,024 MB = 1 GB (기가바이트)
│
└── 1,024 GB = 1 TB (테라바이트)
public class BitByteDemo {
public static void main(String[] args) {
// 1 byte = 8 bits → -128 ~ 127 or 0 ~ 255
byte oneByte = 127;
System.out.println("1 byte 최대값: " + oneByte);
System.out.println("2진수 표현: " + Integer.toBinaryString(oneByte)); // 1111111

// 16진수 2자리 = 1 byte
int hex1byte = 0xFF; // 11111111 = 255
System.out.println("0xFF = " + hex1byte); // 255

// 파일 크기 단위 변환
long fileBytes = 5_368_709_120L; // 5 GB in bytes
long fileMB = fileBytes / (1024 * 1024);
long fileGB = fileBytes / (1024 * 1024 * 1024);
System.out.println("파일 크기: " + fileMB + " MB = " + fileGB + " GB");
}
}

4. 진법 변환 메서드 활용​

자바의 Integer 클래스는 진법 변환을 위한 다양한 메서드를 제공합니다.

public class RadixConversion {
public static void main(String[] args) {
int num = 255;

// 10진수 → 다른 진법 문자열로 변환
System.out.println("10진수: " + num);
System.out.println("2진수: " + Integer.toBinaryString(num)); // 11111111
System.out.println("8진수: " + Integer.toOctalString(num)); // 377
System.out.println("16진수: " + Integer.toHexString(num)); // ff
System.out.println("16진수(대문자): " + Integer.toHexString(num).toUpperCase()); // FF

System.out.println("---");

// 다른 진법 문자열 → 10진수로 변환
// parseInt(문자열, 기수) 형식
System.out.println("2진수 '11111111' = " + Integer.parseInt("11111111", 2)); // 255
System.out.println("8진수 '377' = " + Integer.parseInt("377", 8)); // 255
System.out.println("16진수 'FF' = " + Integer.parseInt("FF", 16)); // 255
System.out.println("16진수 'ff' = " + Integer.parseInt("ff", 16)); // 255 (대소문자 무관)

// 임의 진법으로 출력
System.out.println("255를 5진법으로: " + Integer.toString(255, 5)); // 2010
System.out.println("255를 7진법으로: " + Integer.toString(255, 7)); // 513
}
}

5. 음수의 2진수 표현 (2의 보수법)​

컴퓨터는 음수를 2의 보수(Two's Complement) 방식으로 표현합니다.

양수 1의 2진수 표현 (8 bits):
00000001

-1을 구하는 방법 (2의 보수):
1단계: 1의 보수 (각 비트 반전) 11111110
2단계: 1을 더함 11111111

따라서 -1 = 11111111 (8 bits)

비교:
1 = 00000001
-1 = 11111111
2 = 00000010
-2 = 11111110
public class TwosComplement {
public static void main(String[] args) {
// 양수와 음수의 2진수 표현 비교
System.out.println("1 = " + Integer.toBinaryString(1));
System.out.println("-1 = " + Integer.toBinaryString(-1)); // 32개의 1

System.out.println("127 = " + Integer.toBinaryString(127));
System.out.println("-128 = " + Integer.toBinaryString(-128));

// 부호 비트 (최상위 비트)
// 0으로 시작하면 양수, 1로 시작하면 음수
int positive = 0b01111111; // 127 (양수, MSB=0)
int negative = 0b10000000; // 128... 이지만 부호 있는 byte에서는 -128

byte b1 = (byte) 0b01111111; // 127
byte b2 = (byte) 0b10000000; // -128 (부호 비트 1)

System.out.println("0b01111111 as byte: " + b1); // 127
System.out.println("0b10000000 as byte: " + b2); // -128
}
}

6. 메모리에서의 데이터 저장 방식: 빅엔디안 vs 리틀엔디안​

여러 바이트로 이루어진 데이터를 메모리에 저장할 때, 바이트를 어떤 순서로 배치할지에 대한 규칙입니다.

정수 0x12345678 (4 bytes)을 저장할 때:

빅엔디안 (Big-Endian) - 큰 쪽부터:
주소: 0x100 0x101 0x102 0x103
값: 0x12 0x34 0x56 0x78

리틀엔디안 (Little-Endian) - 작은 쪽부터:
주소: 0x100 0x101 0x102 0x103
값: 0x78 0x56 0x34 0x12
노트

자바는 빅엔디안(Big-Endian) 방식을 사용합니다. JVM이 항상 빅엔디안을 사용하기 때문에 어떤 플랫폼에서 실행해도 동일하게 동작합니다. 반면 대부분의 Intel/AMD x86 CPU는 리틀엔디안을 사용합니다.

7. 16진수의 실전 활용​

16진수는 2진수를 사람이 읽기 쉽게 압축한 형태로 실무에서 자주 사용됩니다.

색상 코드 (HTML/CSS)​

public class ColorCode {
public static void main(String[] args) {
// 웹 색상 코드는 16진수 RRGGBB 형식
// 예: #FF5733 = R:255, G:87, B:51

int color = 0xFF5733; // 16진수로 색상 저장

int red = (color >> 16) & 0xFF; // 상위 8비트
int green = (color >> 8) & 0xFF; // 중간 8비트
int blue = color & 0xFF; // 하위 8비트

System.out.printf("색상 코드: #%06X%n", color);
System.out.printf("R: %d (%02X)%n", red, red);
System.out.printf("G: %d (%02X)%n", green, green);
System.out.printf("B: %d (%02X)%n", blue, blue);
}
}

출력:

색상 코드: #FF5733
R: 255 (FF)
G: 87 (57)
B: 51 (33)

메모리 주소 표현​

public class MemoryAddress {
public static void main(String[] args) {
// 객체의 해시코드 (메모리 주소와 유사)
Object obj = new Object();
int hashCode = System.identityHashCode(obj);

System.out.printf("해시코드 (10진수): %d%n", hashCode);
System.out.printf("해시코드 (16진수): 0x%X%n", hashCode);
// 예: 해시코드 (10진수): 1829164700
// 해시코드 (16진수): 0x6D06D69C
}
}

8. 실전 예제: RGB 색상을 16진수로 변환​

public class RGBConverter {
// RGB 값을 16진수 색상 코드로 변환
static String toHexColor(int r, int g, int b) {
// 각 값이 0~255 범위인지 검증
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
throw new IllegalArgumentException("RGB 값은 0~255 범위여야 합니다.");
}
return String.format("#%02X%02X%02X", r, g, b);
}

// 16진수 색상 코드를 RGB로 분리
static int[] fromHexColor(String hexColor) {
// '#' 제거 후 파싱
String hex = hexColor.startsWith("#") ? hexColor.substring(1) : hexColor;
int r = Integer.parseInt(hex.substring(0, 2), 16);
int g = Integer.parseInt(hex.substring(2, 4), 16);
int b = Integer.parseInt(hex.substring(4, 6), 16);
return new int[]{r, g, b};
}

public static void main(String[] args) {
// RGB → 16진수
System.out.println("흰색 (255,255,255): " + toHexColor(255, 255, 255)); // #FFFFFF
System.out.println("검정 (0,0,0): " + toHexColor(0, 0, 0)); // #000000
System.out.println("빨강 (255,0,0): " + toHexColor(255, 0, 0)); // #FF0000
System.out.println("파랑 (0,0,255): " + toHexColor(0, 0, 255)); // #0000FF
System.out.println("자바 오렌지 (237,119,50): " + toHexColor(237, 119, 50)); // #ED7732

// 16진수 → RGB
int[] rgb = fromHexColor("#1E90FF"); // 도저블루
System.out.printf("도저블루 #1E90FF: R=%d, G=%d, B=%d%n", rgb[0], rgb[1], rgb[2]);
// 도저블루 #1E90FF: R=30, G=144, B=255

// 진법별 같은 값 표현
System.out.println("\n=== 같은 값을 다양한 진법으로 ===");
int value = 255;
System.out.printf("10진수: %d%n", value);
System.out.printf("2진수: %s%n", Integer.toBinaryString(value));
System.out.printf("8진수: %s%n", Integer.toOctalString(value));
System.out.printf("16진수: %s%n", Integer.toHexString(value).toUpperCase());
}
}

출력:

흰색 (255,255,255): #FFFFFF
검정 (0,0,0): #000000
빨강 (255,0,0): #FF0000
파랑 (0,0,255): #0000FF
자바 오렌지 (237,119,50): #ED7732
도저블루 #1E90FF: R=30, G=144, B=255

=== 같은 값을 다양한 진법으로 ===
10진수: 255
2진수: 11111111
8진수: 377
16진수: FF

9. 비트 연산 기초 (심화)​

public class BitwiseBasics {
public static void main(String[] args) {
int a = 0b1010; // 10진수 10
int b = 0b1100; // 10진수 12

System.out.println("a = " + Integer.toBinaryString(a) + " (" + a + ")");
System.out.println("b = " + Integer.toBinaryString(b) + " (" + b + ")");

// AND (&): 둘 다 1일 때만 1
System.out.println("a & b = " + Integer.toBinaryString(a & b) + " (" + (a & b) + ")"); // 1000 = 8

// OR (|): 하나라도 1이면 1
System.out.println("a | b = " + Integer.toBinaryString(a | b) + " (" + (a | b) + ")"); // 1110 = 14

// XOR (^): 서로 다를 때만 1
System.out.println("a ^ b = " + Integer.toBinaryString(a ^ b) + " (" + (a ^ b) + ")"); // 0110 = 6

// NOT (~): 비트 반전
System.out.println("~a = " + (~a)); // -11

// 왼쪽 시프트 (<<): 2배 곱하기
System.out.println("a << 1 = " + (a << 1)); // 20 (10 * 2)

// 오른쪽 시프트 (>>): 2로 나누기
System.out.println("a >> 1 = " + (a >> 1)); // 5 (10 / 2)
}
}
팁

비트 연산 실전 활용:

  • n & 1: 짝수/홀수 판별 (결과가 0이면 짝수, 1이면 홀수)
  • n << 1: 2배 곱하기 (나누기보다 빠름)
  • n >> 1: 2로 나누기
  • a ^ b ^ b == a: XOR 특성을 이용한 swap

정리​

  • 2진수: 컴퓨터 내부 표현 (0b 접두사)
  • 8진수: 파일 권한 등에 사용 (0 접두사, 주의 필요!)
  • 16진수: 색상, 메모리 주소, 해시값 표현 (0x 접두사)
  • 진법 변환: Integer.toBinaryString(), Integer.toHexString(), Integer.parseInt(str, radix)
  • 2의 보수: 컴퓨터의 음수 표현 방식
  • 빅엔디안: 자바 JVM의 바이트 저장 순서
  • 1 byte = 8 bits= 16진수 2자리