unicode i18n programming text-processing

Unicode 내부: 자소, 코드 포인트 및 정규화 형식

자소 클러스터, 서로게이트 쌍 및 텍스트 정규화(NFC, NFD)의 필수 규칙을 다루며 Unicode의 내부 작동 방식을 심층적으로 분석합니다.

Unicode 내부: 자소, 코드 포인트 및 정규화 형식

코드에서 문자열 "é"의 길이가 1이 아니라 2라고 표시되거나, 단순한 이모지가 데이터베이스를 손상시킨 적이 있다면 Unicode의 숨겨진 복잡성을 경험하신 것입니다. 현대의 글로벌 디지털 세계에서 텍스트를 이해하는 것은 더 이상 1바이트를 1문자에 매핑하는 것만큼 간단하지 않습니다.

이 가이드에서는 가공되지 않은 코드 포인트부터 상위 수준의 자소 클러스터(Grapheme Cluster)에 이르기까지 Unicode의 핵심 개념을 살펴보고, 텍스트 정규화가 문자열 비교에서 왜 중요한 역할을 하는지 설명합니다.


1. 기본 구성 요소: 코드 포인트 및 코드 유닛

기본적으로 Unicode는 고대 이집트 상형 문자부터 최신 이모지에 이르기까지 인류가 사용한 모든 문자의 거대한 목록입니다.

코드 포인트 (ID)

**코드 포인트(Code Point)**는 문자에 할당된 고유 번호입니다. U+ 뒤에 16진수가 붙는 형식으로 작성됩니다. 예:

  • U+0041은 'A'
  • U+1F600은 '😀'

코드 유닛 (바이트)

**코드 유닛(Code Unit)**은 코드 포인트를 나타내는 데 사용되는 물리적 저장 단위입니다. 크기는 인코딩 방식에 따라 다릅니다(UTF-8은 8비트 단위, UTF-16은 16비트 단위를 사용).

UTF-16 서로게이트 쌍 (Surrogate Pair)

UTF-16은 JavaScript, Java 및 C#에서 내부적으로 사용하는 인코딩 방식입니다. 16비트 코드 유닛을 사용하기 때문에 $2^{16} = 65,536$개의 문자만 직접 표현할 수 있습니다. 이 범위를 벗어나는 문자(대부분의 이모지 등)를 표현하기 위해 UTF-16은 서로게이트 쌍을 사용합니다. 이는 두 개의 16비트 유닛이 결합하여 하나의 코드 포인트를 나타내는 방식입니다.

  • 예: '😀' 이모지는 하나의 코드 포인트이지만 두 개의 UTF-16 유닛을 차지합니다. JavaScript에서 "😀".length2를 반환하는 이유가 바로 이것입니다.

2. 자소 클러스터: 사용자가 보는 것

프로그래머는 코드 포인트를 보지만, 사용자는 **자소(Graphemes)**를 봅니다.

자소 클러스터란 무엇입니까?

**자소 클러스터(Grapheme Cluster)**는 하나의 시각적 단위로 표시되는 하나 이상의 코드 포인트 시퀀스입니다.

  • 예: 문자 'é'는 다음과 같이 저장될 수 있습니다.
    1. 단일 코드 포인트: U+00E9 (LATIN SMALL LETTER E WITH ACUTE)
    2. 두 코드 포인트의 조합: U+0065 (문자 'e') + U+0301 (결합용 양음 액센트) 사용자에게는 이들이 동일하게 보입니다. 하지만 컴퓨터에게는 완전히 다른 문자열입니다.

3. 정규화의 힘: NFC 및 NFD

문자열 비교를 신뢰할 수 있게 하려면 시각적으로 동일한 문자가 동일한 바이너리 표현을 갖도록 텍스트를 "정규화"해야 합니다.

정규화 형식 D (NFD) - 정준 분해

NFD는 문자를 구성 요소별로 분해합니다.

  • 'é'는 'e' + '´'(두 개의 코드 포인트)가 됩니다.

정규화 형식 C (NFC) - 정준 결합

NFC는 가능한 경우 구성 요소를 단일 문자로 결합합니다.

  • 'e' + '´'는 'é'(하나의 코드 포인트)가 됩니다.
  • 대부분의 웹 애플리케이션은 NFC를 표준으로 사용합니다.

호환성 정규화 (NFKC, NFKD)

이 형식들은 한 단계 더 나아가 시각적으로는 비슷하지만 의미가 완전히 동일하지는 않은 문자들을 정규화합니다. 예를 들어, 제곱을 나타내는 기호 '²'를 숫자 '2'로 변환합니다. 이는 검색 인덱싱에는 유용하지만 중요한 서식 정보를 잃을 수 있습니다.


4. 개발자를 위한 권장 사항

  1. 항상 사용자 입력 정규화: 문자열(사용자 이름 또는 비밀번호 등)을 비교할 때 저장하거나 확인하기 전에 항상 NFC로 정규화하세요.
  2. 자소 인식 라이브러리 사용: 사용자가 보는 대로 문자열 길이를 정확하게 계산해야 하는 경우 .length를 사용하지 마세요. 현대적인 브라우저에서는 라이브러리나 Intl.Segmenter API를 사용하세요.
  3. UTF-16 길이에 주의: 많은 문자가 서로게이트 쌍이라는 점을 기억하세요. Python이나 Rust에서는 문자열이 기본적으로 UTF-8이지만, JS/C#/Java에서는 인덱싱에 주의해야 합니다.

결론

Unicode는 레거시 문자 인코딩의 혼란을 해결하기 위해 설계된 현대 공학의 걸작입니다. 코드 포인트와 자소의 차이를 이해하고 NFC 및 NFD와 같은 정규화 형식을 마스터함으로써, 언어나 장치에 관계없이 모든 사용자에게 텍스트를 올바르게 처리하는 애플리케이션을 구축할 수 있습니다.