‘한 글자’의 길이는 왜 프로그램마다 다를까?

반응형

입력창에 ‘가’를 하나 쓰고 글자 수를 세면 1이다. 같은 내용을 전송할 때는 3바이트가 필요할 수 있다. 화면에 한 글자로 보이는 이모지는 숫자가 더 복잡하다. 길이 제한이나 문자열 자르기를 구현할 때 무엇을 세는지 정하지 않으면 같은 입력을 두고 서로 다른 답이 나온다.

유니코드(Unicode)는 여러 언어의 문자에 번호를 붙여 다루는 표준이다. 코드 포인트(code point)는 유니코드가 부여한 번호 하나를 말한다. 유니코드 변환 형식 8비트(Unicode Transformation Format – 8-bit, UTF-8)는 이 번호를 저장하거나 전송할 바이트로 바꾸는 방식이다. 코드 포인트 하나가 UTF-8에서 1~4바이트를 차지할 수 있다.

‘가’ 하나도 세는 단위에 따라 다르다

한글 음절 ‘가’는 코드 포인트 하나다. UTF-8로 저장하면 3바이트다. 따라서 “길이가 1”과 “길이가 3”은 서로 모순되지 않는다. 앞의 1은 코드 포인트 수, 뒤의 3은 바이트 수다.

화면의 ‘가’ : 보이는 글자 하나
유니코드 코드 포인트: 하나
UTF-8로 저장한 크기: 3바이트

파이썬(Python)의 문자열에서 len(“가”)은 1을 돌려준다. “가”.encode(“utf-8”)는 문자열을 UTF-8 바이트로 바꾸므로 여기에 len을 적용하면 3이다. 바이트 크기로 저장 공간을 제한하면서 화면에 보이는 글자 수라고 안내하면 사용자는 이유를 알 수 없는 초과 오류를 볼 수 있다.

화면에서 하나로 보여도 코드 포인트는 여러 개일 수 있다

‘é’는 미리 합쳐진 코드 포인트 하나로 적을 수도 있고, ‘e’ 뒤에 악센트 결합 문자를 붙여 두 코드 포인트로 적을 수도 있다. 화면에서는 비슷하게 한 글자로 보인다. 문자소 클러스터(grapheme cluster)는 사용자가 화면에서 한 글자로 인식하는 묶음이다. 한 문자소 클러스터에 코드 포인트 여러 개가 들어갈 수 있다.

é                  코드 포인트 하나
e + 결합 악센트     코드 포인트 둘
화면에서는 둘 다 보통 ‘é’ 한 글자로 보임

유니코드 정규화(normalization)는 같은 문자를 나타내는 여러 표현을 정해진 형태로 맞추는 규칙이다. 예를 들어 정규화 형식 C(Normalization Form C, NFC)는 위의 ‘e + 결합 악센트’를 합칠 수 있다. 하지만 입력을 무조건 바꿔도 된다는 뜻은 아니다. 로그인 이름이나 외부 시스템과 공유하는 식별자라면 어느 표현을 같은 값으로 취급할지 먼저 정해야 한다.

잘라낼 위치도 단위에 맞춰야 한다

UTF-8 바이트열의 세 번째 바이트에서 무턱대고 자르면 한 문자의 중간을 끊을 수 있다. 코드 포인트를 기준으로 잘라도 결합 문자와 앞 글자를 갈라 화면에는 어색하게 보일 수 있다. 입력창에서 ‘앞에서 다섯 글자’를 보여 주려면 문자소 클러스터 기준이 사용자 기대에 가깝다. 전송 크기를 제한하려면 바이트 수가 필요하다. 검색과 정렬에서는 또 어떤 문자를 같다고 볼지 규칙이 필요하다.

같은 ‘길이’라는 말이 서로 다른 작업을 가리킨다. 저장 한도인지, 화면 표시인지, 문자열 비교인지 먼저 정하고 그 작업에 맞는 단위로 센다. 문자열 알고리즘의 시간도 입력 길이 n이 무엇을 세는지에 따라 달라진다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글