컴퓨터의 비트, 바이트 그리고 인코딩 이해하기

‘컴퓨터가 정보를 저장하고 처리하는 방식의 기본 원리 정리’

컴퓨터는 모든 정보를 0과 1로 저장하지만, 이 단순한 비트들이 어떻게 복잡한 데이터를 표현하는지 이해하는 것은 중요합니다.

이 글에서는 컴퓨터의 데이터 표현 방식의 핵심 개념들을 정리했습니다.

비트와 바이트의 기본 개념

비트의 중요성과 명명법

2진수에서 가장 오른쪽 비트는 LSB(Least Significant Bit), 왼쪽 비트는 MSB(Most Significant Bit)라고 부릅니다.

MSB는 숫자의 크기나 부호를 결정하는 중요한 역할을 합니다.

컴퓨터는 미리 정해진 수의 비트를 한 덩어리로 사용하도록 설계되었기 때문에, 필요에 따라 0으로 패딩값을 채워 넣는 경우가 있습니다.

MSB 부분에 0으로 채워넣는 것을 리딩 제로(leading zero)라고 부릅니다.

바이트와 데이터 처리

비트는 너무 작은 단위이기 때문에, 8비트를 묶은 바이트라는 개념이 등장했습니다.

컴퓨터 메모리는 바이트 단위로 주소가 부여되며, 각 바이트는 고유한 주소를 가집니다.

64비트 시스템에서 CPU는 일반적으로 레지스터 크기에 맞춰 8바이트(64비트)씩 메모리에서 데이터를 읽어옵니다.

하지만 프로그램 요구에 따라 1바이트, 2바이트, 4바이트 등 다양한 크기로 접근 가능합니다.

16진수 표기법

2진수로 표현하면 너무 길고 복잡하기 때문에, 4비트씩 묶어 16진수로 표현하여 인간이 읽고 이해하기 쉽게 합니다.

2진수 : 1101 0011 1111 1100 0001
16진수 :   d    3    f    c    1

16진수는 0-9와 a-f를 사용하며, 보통 0x 접두사를 붙여 표기합니다(예: 0xd3fc1).

정수 표현과 오버플로우

오버플로우의 이해

오버플로우란 MSB에서 올림이 발생하여 저장 가능한 비트 수를 초과하는 상황을 의미합니다.

어셈블리 레벨에서는 레지스터에 오버플로 비트가 존재하고, 이 비트에는 MSB에서 발생한 올림값이 들어갑니다.

참고로 자바와 같은 고급 언어에서 발생하는 오버플로우(예: int 타입에서 20억을 넘기는 경우)는 어셈블리 레벨에서 직접 플래그를 설정하는 것과는 다릅니다.

자바는 오버플로우가 발생해도 예외를 발생시키지 않고 결과값을 해당 타입의 범위 내로 자릅니다.

음수 표현 방식

컴퓨터에서 음수를 표현하는 방식은 여러 가지가 있습니다:

  1. 부호와 크기: MSB로 부호를 나타내고 나머지 비트로 절대값을 표현합니다. 하지만 0을 두 가지 방식으로 표현할 수 있고(+0, -0), 덧셈 연산이 복잡해진다는 단점이 있습니다.

  2. 1의 보수: 각 비트를 뒤집어 음수를 표현합니다. 이 방식도 0이 두 가지로 표현되며, 덧셈 시 순환올림을 처리해야 하는 복잡성이 있습니다.

  3. 2의 보수: 현대 컴퓨터에서 가장 널리 사용되는 방식입니다. 모든 비트를 뒤집고 1을 더하면 음수가 됩니다. XOR과 AND 연산만으로 계산이 가능하고, 0의 표현이 유일하다는 장점이 있습니다.

예: 0001(1)의 비트를 뒤집으면 1110이고, 여기에 1을 더하면 1111(-1)이 됩니다.

2의 보수를 사용하더라도 singed 자료구조이면 MSB가 1이면 음수, 0이면 양수로 판단합니다.

부동소수점 표현

고정소수점 vs 부동소수점

고정소수점은 비트의 특정 부분을 정수부와 소수부로 고정하여 실수를 표현합니다. 사용은 간단하지만, 표현 범위가 제한적입니다.

부동소수점은 가수와 지수로 나누어 실수를 표현합니다.

소수점 위치가 지수에 따라 변하기 때문에 부동 소수점이라고 불리며, 고정 소수점에 비해 더 넓은 범위의 값을 표현할 수 있습니다.

IEEE 754 표준

부동소수점에서 지수는 2의 보수가 아닌 편향 표현 방식을 사용합니다.

지수부분의 값에서 편향값(32비트 float의 경우 127)을 뺀 값을 실제 지수로 사용합니다.

예를 들어, 지수부가 122로 저장되어 있으면 실제 지수는 122-127=-5이고, 249로 저장되어 있으면 실제 지수는 249-127=122입니다.

이 방식은 특수값(0, 무한대, NaN) 표현과 부동소수점 비교 연산을 단순화합니다.

문자와 인코딩

ASCII에서 유니코드로

처음에는 아스키 코드가 사용되었습니다. 이는 7비트로 영문자와 기본 기호들을 표현합니다. 하지만 다른 언어와 문자를 표현하기에는 부족했습니다.

이후 유니코드가 등장하여 전 세계 모든 문자를 표현할 수 있게 되었습니다.

유니코드는 처음에 16비트로 시작했지만, 현재는 21비트까지 확장되었습니다.

UTF-8 인코딩

유니코드를 효율적으로 저장하기 위해 UTF-8과 같은 인코딩 방식이 사용됩니다.

UTF-8은 아스키 문자는 1바이트로, 그 외 문자는 필요에 따라 2-4바이트로 가변적으로 인코딩합니다.

  • ASCII 문자(7비트): 첫 비트를 0으로 설정하고 1바이트 사용
  • 확장 문자(11비트 필요): 첫 바이트는 110으로 시작, 두번째 바이트는 10으로 시작
  • 더 복잡한 문자(16비트 이상): 3-4바이트 사용

BASE64 인코딩

바이너리 데이터를 텍스트 기반 시스템에서 안전하게 전송하기 위해 BASE64 인코딩이 사용됩니다.

이는 3바이트의 데이터를 4개의.ASCII 문자로 변환합니다. 때문에 원본 데이터보다 항상 33% 더 많은 공간이 필요합니다.

BASE64가 필요한 이유:

  1. 텍스트 기반 프로토콜(HTTP, JSON, XML 등)에서 바이너리 데이터 전송
   {
  "name": "profile.jpg",
  "image_data": "iVBORw0KGgoAAAANSUhEUgAA..."
}
  1. 특수 제어 문자로 인한 데이터 손상 방지
  2. 레거시 시스템 호환성
  3. 방화벽과 보안 필터 통과

현대 시스템에서는 HTTP/2, WebSocket 등 바이너리 프로토콜도 많이 사용되지만, 텍스트 기반 시스템과의 호환성을 위해 BASE64는 여전히 중요합니다.

데이터 타입과 인코딩의 관계

텍스트와 숫자 데이터는 인코딩 방식에 따라 다르게 처리됩니다.

UTF-8과 같은 문자 인코딩에서는 모든 것이 문자로 취급됩니다.

예를 들어, 숫자 “1”은 ASCII/UTF-8에서 0x31(이진수로 00110001)로 인코딩되는 문자이지, 산술적 값 1을 직접 나타내는 것이 아닙니다.

실제 숫자 값 1은 바이너리로 0x01로 표현됩니다.

이로 인해 HTTP와 같은 텍스트 기반 프로토콜에서 숫자 데이터를 전송할 때도 추가 공간이 필요합니다:

정수 12345 (바이너리): 4바이트 (int32 기준) 문자열 “12345” (UTF-8): 5바이트

데이터 타입의 구분은 일반적으로 프로토콜 수준에서 이루어집니다.

JSON에서는 따옴표 유무로 문자열과 숫자를 구분합니다. 이게 숫자 그대로 바이트 코드를 내보낸다는것은 아닙니다.

즉, 네트워크 전송 시에는 모두 문자열로 전송되지만, JSON의 구문 규칙(따옴표 유무)에 따라 파싱될 때 다른 데이터 타입으로 해석되는 것일 뿐입니다.

이것이 바로 Protocol Buffers나 MessagePack 같은 바이너리 형식이 대량의 숫자 데이터를 다룰 때 더 효율적인 이유입니다.


© 2021. All rights reserved.

Powered by Hydejack v9.2.1