컴퓨터는 오직 0과 1만 이해할 수 있는데, 어떻게 문자열이 존재할 수 있을까요?
답은 의외로 간단합니다.
도메인 이름을 IP 주소에 연결하거나, 바코드를 특정 상품에 연결하는 것처럼 말이죠.
즉, 우리는
숫자를 문자에 매핑할 수 있습니다!
예를 들어 97을 "a"에,
122를 "z"에 연결하는 식이죠.
바로 이 방식 덕분에 숫자만 다루는 컴퓨터 세상에서도 문자를 사용할 수 있습니다.
그런데 어떤 숫자가 어떤 문자와 짝을 이룰까요?
이 질문에 답하기 위해 사람들은 서로 다른 문자 매핑 시스템을 만들어 왔습니다.
그 시작이 바로 ASCII입니다.
ASCII는 "American Standard Code for Information Interchange(미국 정보 교환 표준 부호)"의 약자입니다.
ASCII 표를 찾아볼 수도 있고, Ruby에게 직접 물어봐서 문자를 해당 ASCII 값으로 변환할 수도 있습니다.
이렇게 말이죠:
"a".ord # 97
여러 문자를 한 번에 변환하려면:
"abc".bytes # [97, 98, 99]
반대로 정수가 있다면 그에 해당하는 문자를 얻을 수도 있습니다.
이렇게요:
97.chr # "a"
ASCII 인코딩의 구성 요소
ASCII 인코딩에는 다음이 포함됩니다:
- 제어 문자 (줄바꿈, 탭, 널 문자 등)
- 기호 (괄호, 등호, 물음표 등)
- 숫자 (0-9)
- 영문 알파벳 (a-z, A-Z)
하지만 이 글 뒤에서 살펴보겠지만, 이 문자 범위에는 명확한 한계가 있습니다.
왜일까요?
중국어나 일본어 같은 다른 언어의 문자와 기호가 포함되어 있지 않기 때문입니다.
실제 세계에서의 ASCII 활용
사실 이런 숫자-문자 매핑 작업은 여러분 몰래 백그라운드에서 자동으로 일어납니다.
하지만 실무에서 활용할 수 있는 사례들도 있습니다!
예를 들면:
HTTP 명세는 URL에 특정 문자가 포함되는 것을 허용하지 않습니다.
하지만 이런 유효하지 않은 문자들을 ASCII 형식으로 인코딩하면 대부분의 최신 웹 서버가 정상적으로 해석해 줍니다.
example.com/a+++ => example.com/a%2B%2B%2B
%2B가 무엇일까요?
바로 + 문자를 ASCII 인코딩 형식으로 표현한 것입니다.
이 지식은 문자 변환에도 활용할 수 있습니다.
예를 들어:
ASCII 표를 잘 살펴보면 소문자에서 32를 빼면 대문자로 변환할 수 있다는 것을 알 수 있습니다.
("a".ord - 32).chr
# "A"
물론 반대 방향으로도 동작합니다.
("A".ord + 32).chr
# "a"
네, 맞습니다.
Ruby에는 upcase와 downcase 메서드가 이미 존재합니다.
하지만 코딩 면접이나 알고리즘 문제를 풀 때 이 원리가 유용하게 쓰일 수 있으니 기억해 두세요.
유니코드(Unicode)란 무엇인가?
ASCII는 최대 127개(확장 ASCII를 포함하면 256개)의 문자만 인코딩할 수 있어서 표현 가능한 문자에 큰 제약이 있습니다.
해결책은?
유니코드입니다.
유니코드는 최대 백만 개의 서로 다른 문자를 표현할 수 있는 문자열 인코딩 시스템입니다.
ASCII보다 훨씬 넉넉한 공간이죠!
이제 모든 언어의 문자, 새로운 기호, 심지어 이모지까지 담을 수 있게 되었습니다.
유니코드 문자의 예시입니다:
ɑΩϕβΣπ
위 문자들은 그리스 알파벳으로, ASCII로는 표시할 수 없는 문자들입니다.
Ruby에서 유니코드 사용하기
Ruby는 유니코드를 지원하며, Ruby 1.9부터 기본적으로 활성화되어 있습니다.
그래서 이런 것도 가능합니다:
π = 3.141592
심지어 이것도 가능하죠:
def ★★★ puts "별 3개! 정말 잘했어요!" end ★★★ # "별 3개! 정말 잘했어요!"
재미있지 않나요?
다만 이런 기호들은 키보드에 없기 때문에 메서드나 변수 이름으로 사용하기엔 현실적이지 않습니다.
게다가 실제로는 눈에 보이지 않는 유효한 유니코드 문자도 존재합니다.
예시:
def puts "보이지 않는 메서드" end
언뜻 보면 이름 없는 메서드처럼 보이는데, 원래라면 허용되지 않는 문법입니다.
하지만 눈에 보이지 않는 유니코드 문자가 숨어 있기 때문에 실제로 동작합니다!
문자열 인코딩 관련 메서드
Ruby는 다양한 인코딩 시스템을 다루기 위한 메서드들을 제공합니다.
예를 들면:
"abc".encoding.name # "UTF-8"
그런데 현재 설정된 인코딩(encoding.name)과 문자열의 실제 인코딩이 일치하지 않는 특수한 상황이 가끔 발생합니다.
웹사이트, 파일, 데이터베이스 또는 외부 소스에서 데이터를 읽어올 때 이런 문제를 마주칠 수 있습니다.
이 경우 InvalidByteSequenceError가 발생합니다.
이런 오류가 생기면 인코딩을 변경해야 합니다.
어떻게 할까요?
encode 메서드를 사용하면 됩니다:
"abcΣΣΣ".encode("ASCII", "UTF-8", undef: :replace)
# "abc???"
여기서는 UTF-8(유니코드)에서 ASCII로 변환하고 있습니다. Σ 문자는 ASCII에 존재하지 않기 때문에 Ruby에게 대체하라고 지시한 것입니다.
기본적으로 정의되지 않은 문자는 물음표(?)로 대체됩니다.
하지만 이 동작은 변경할 수 있습니다.
이렇게요:
"abcΣΣΣ".encode("ASCII", "UTF-8", invalid: :replace, undef: :replace, replace: "")
# "abc"
혹은 "fallback" 옵션을 사용할 수도 있습니다:
"abcΣΣΣ".encode("ASCII", "UTF-8", fallback: {"Σ" => "E"})
# "abcEEE"
이 코드의 의미는 다음과 같습니다:
"UTF-8(유니코드)에서 ASCII로 모든 문자를 변환하되, ASCII에 존재하지 않는 문자는 fallback 해시를 사용해 번역하라."
또 하나 알아둘 메서드로 force_encoding이 있는데, 이 메서드는 변환 과정 없이 인코딩 정보만 변경합니다.
예시:
"abc½½½".force_encoding("iso-8859-1")
사용 가능한 인코딩 목록은 Encoding.aliases 메서드로 확인할 수 있습니다.
마치며
이번 글에서는 컴퓨터가 인코딩 테이블을 통해 숫자로부터 문자를 만들어내는 원리를 배웠습니다. Ruby에서 ASCII와 유니코드를 다루는 방법도 함께 익혔죠.
이제 에디터를 열고 직접 실습하면서 재미있게 연습해 보세요 😃
읽어주셔서 감사합니다.