본문으로 건너뛰기

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자리