NFC와 NFD, 자소분리 문제 정리

이 글은 AI가 작성했습니다.

개요

이 문서는 Unicode 정규화(NFC, NFD)의 기본 개념과 Hangul(한글) 자소 분리(자모 분해)에 따른 문제를 정리한다. 각 정규화 방식의 차이, 한글 처리에서 발생하는 실무적 문제, 그리고 이를 해결하기 위한 권장 방법과 구현 예제를 포함한다.

Unicode 정규화 개요

  • Unicode에는 서로 다른 코드 포인트 열이 시각적으로 동일하거나 의미상 동일한 문자로 해석되는 경우가 있다. 이를 canonical equivalence(정규 동치)라고 한다.
  • 정규화(normalization)는 동일한 문자를 표현하는 여러 코드 포인트 열을 일관된 형태로 변환하는 과정이다.
  • 주요 정규화 형태:
    • NFC (Normalization Form C): Canonical Composition. 가능한 한 결합(합성)된 형태로 변환한다.
    • NFD (Normalization Form D): Canonical Decomposition. 가능한 한 분해된 형태(기본 문자 + 결합되는 음절부호 등)로 변환한다.
    • NFKC / NFKD: Compatibility(호환성) 분해/합성. 호환성 매핑까지 적용하여 시각적·의미적으로 유사한 문자들을 더 폭넓게 통일한다(예: ① → 1).

NFC와 NFD의 차이 (핵심 포인트)

  • NFC: 가능한 경우 사전 정의된 합성 문자를 사용한다. 예: “é”를 U+00E9 (precomposed)로 표현.
  • NFD: 결합 문자를 분해하여 기본 문자와 결합 문자(combining marks)로 표현. 예: “e” (U+0065) + U+0301 (combining acute accent).
  • Canonical combining class(CCC): 결합 문자의 정렬 규칙을 정의한다. 결합 문자의 순서는 CCC 값에 따라 정렬되며, 정규화 과정에서 이 순서가 표준에 맞게 재배열된다.
  • NFC와 NFD는 canonical equivalence를 보장하므로, 두 문자열이 정규화된 동일한 형태이면 의미적으로 동일하다.

한글(자소분리) 관련 특성

  • 한글은 Unicode에서 두 가지 방식으로 표현될 수 있다.
    1. 완성형 음절 코드(Precomposed syllables): U+AC00 ~ U+D7A3 범위에 현대 한글 음절들이 미리 합성되어 있음(예: “가” = U+AC00).
    2. 자모 분해(초성·중성·종성 jamo): 각각의 초성·중성·종성을 나타내는 코드 포인트로 구성(예: “가” = U+1100 (ᄀ) + U+1161 (ᅡ)).
  • Unicode의 canonical decomposition은 현대 한글 음절을 표준 jamo(유니코드의 Hangul Jamo 블록, U+1100 계열)로 분해한다. 따라서 NFD로 변환하면 음절이 jamo 시퀀스로 분해된다.
  • 주의할 점:
    • Compatibility Jamo(호환 자모, U+3131 계열)는 canonical decomposition 대상이 아니며, NFKC/NFKD를 사용해야 호환 자모와의 매핑이 발생할 수 있다.
    • 일부 시스템(예: macOS의 HFS+)은 파일명 등을 NFD로 저장하는 관례를 가졌다. 이로 인해 동일한 시각적 문자열이 서로 다른 바이트 시퀀스로 저장될 수 있다.

자소분리에 따른 실무적 문제 사례

  • 문자열 비교/동등성 검사:
    • 두 문자열이 시각적으로 동일하더라도 NFC/NFD 상태가 다르면 바이트나 코드 포인트 비교에서 다르게 판단된다.
    • 예: “가” (U+AC00)와 “가” (U+1100 U+1161)는 바이트 수준으로 다름.
  • 데이터베이스와 인덱싱:
    • 정규화되지 않은 입력을 그대로 저장하면 동일한 검색어로도 매칭되지 않을 수 있다. 인덱스가 정규화 상태에 의존하면 일관성 문제가 발생한다.
  • 파일 시스템 호환성:
    • macOS(HFS+)와 다른 OS 간 파일명 동기화에서 동일한 파일명이 서로 다른 바이트 시퀀스로 저장되어 충돌 또는 중복으로 보일 수 있다.
  • 검색과 정렬, 토큰화:
    • 토크나이저나 정렬 알고리즘이 코드 포인트 단위로 동작하면 자모 분해로 인해 오류가 발생하거나 의도치 않은 토큰 분리가 일어날 수 있다.
  • 입력기 및 UX:
    • 커서 이동, 삭제 등의 편집 동작이 grapheme cluster(사용자에게 보이는 문자 단위)와 코드 포인트(혹은 jamo 단위)를 혼용하면 사용자에게 이상한 동작으로 보일 수 있다.
  • 해시와 서명:
    • 정규화되지 않은 데이터를 해시하거나 서명하면 동일한 시각적 문자열에 대해 서로 다른 해시값이 생성된다.

권장 실무 방안

  • 일관된 정규화 정책 수립
    • 입력 시점 또는 저장 시점에 일관되게 정규화하는 것이 중요하다. 일반적으로는 NFC로 정규화해 저장하는 것을 권장한다(대부분의 텍스트 처리 라이브러리와 비교 연산이 NFC 기반으로 설계된 경우가 많음).
    • 단, 특정 플랫폼(예: macOS 파일명)과의 상호운용성이 요구되면 해당 플랫폼 규약에 맞춰 처리한다(NFD를 요구하는 경우가 있음).
  • 비교와 검색
    • 사용자 입력과 인덱스 양쪽을 동일한 정규화 규칙으로 정규화한 후 비교한다.
    • 단순 바이트 비교를 피하고, 정규화 후 비교를 수행한다.
  • 저장과 전송
    • 저장(데이터베이스, 파일)에 앞서 정규화 규칙을 적용한다. API를 통해 전송할 때도 동일한 규칙을 적용한다.
  • 정렬과 언어별 규칙
    • 단순 유니코드 코드포인트 정렬 대신 locale-aware collation(예: ICU)을 사용한다. 정규화는 collation 이전에 일관성을 위해 수행되어야 한다.
  • 편집과 사용자 인터페이스
    • 텍스트 조작(커서 이동, 백스페이스 등)은 grapheme cluster(사용자 관점의 문자 단위) 기준으로 구현한다. Unicode Text Segmentation(UTS#29)을 참고한다.
  • 호환 자모 처리
    • 호환 자모와의 상호운용이 필요한 경우(NFKC/NFKD)를 신중히 검토한다. NFKC는 호환성 변환을 적용하므로 시각적 유사성은 높아지지만 의미 정보가 손실될 수 있다.

구현 예제 (간단한 참고)

  • Python
    • unicodedata 라이브러리 사용:
      • unicodedata.normalize(‘NFC’, s)
      • unicodedata.normalize(‘NFD’, s)
  • JavaScript
    • String.prototype.normalize:
      • s.normalize(‘NFC’)
      • s.normalize(‘NFD’)
    • 구형 환경에서는 폴리필이 필요할 수 있음.
  • Java
    • java.text.Normalizer 사용:
      • Normalizer.normalize(s, Normalizer.Form.NFC)
  • ICU
    • ICU 라이브러리는 정규화 및 로케일별 정렬/대조에 권장되는 도구이다.

(참고: 위 API들은 환경별로 예외 처리와 성능 비용이 있으므로 대용량 처리 시 배치화하거나 캐시 등을 고려해야 한다.)

추가 고려사항

  • 성능: 정규화는 비용이 있으므로 대량 데이터 처리 시 사전 프로파일링을 권장한다. 가능하면 입력 시점에 정규화하여 저장 프로세스에서 반복 정규화를 피한다.
  • 저장 공간과 가독성: NFD로 저장하면 코드 포인트 수가 증가할 수 있으므로 필요에 따라 용량 영향 평가가 필요하다.
  • 호환성 테스트: 외부 시스템(파일 공유, API, 데이터 교환 등)과 상호작용할 때는 정규화 방식의 차이로 인한 문제를 사전 테스트한다.
  • Grapheme cluster와의 구분: 사용자에게 보이는 문자 단위(그라펌)를 처리할 때는 정규화와 별개로 UAX #29(Unicode Text Segmentation)를 따르는 것이 바람직하다.

요약

  • NFC는 가능한 합성 형태를, NFD는 가능한 분해 형태를 제공한다.
  • 한글 음절은 완성형 음절(합성)과 자모 분해(초·중·종)로 표현될 수 있으며, 이로 인해 비교·검색·파일명 동기화 등에서 실무적 문제가 발생할 수 있다.
  • 실무에서는 일관된 정규화 정책(대부분 NFC 권장)을 수립하고, 비교·저장·검색 시 정규화를 적용하며 locale-aware collation과 grapheme cluster 처리를 병행하는 것이 바람직하다.
  • 플랫폼별 관행(예: macOS의 NFD 파일명)을 고려하여 상호운용성 요구사항을 충족하도록 처리해야 한다.