Qué es un par sustituto (Surrogate Pair)
Este artículo se lee en unos 4 minutos.
Introduces un solo emoji y se cuenta como «2 caracteres». El responsable es el par sustituto.Cuando se intenta manejar el rango de representación de Unicode con el diseño antiguo de UTF-16, hay caracteres que necesitan dos «mitades» para ser expresados. A esa combinación de mitades se le llama par sustituto. Es un concepto fundamental que aparece inevitablemente al trabajar con emojis y ciertos caracteres CJK.
Definición
Un par sustituto es el mecanismo de UTF-16 para representar puntos de código Unicode superiores a U+FFFF mediante dos unidades de 16 bits. La primera mitad se llama «sustituto alto» (High Surrogate, U+D800-U+DBFF) y la segunda «sustituto bajo» (Low Surrogate, U+DC00-U+DFFF); ambas aparecen siempre en pareja.
Por qué es necesario
Unicode posee un espacio de puntos de código enorme, desde U+0000 hasta U+10FFFF, que requiere 21 bits para representarse. Sin embargo, UTF-16 tiene unidades de 16 bits, por lo que solo puede representar hasta U+FFFF de forma individual. Para manejar caracteres que superan ese límite (la mayoría de los emojis, algunos kanji, escrituras antiguas, símbolos matemáticos, etc.), se introdujo el mecanismo de combinar un sustituto alto y uno bajo.
Relación con los emojis
La mayoría de los puntos de código de los emojis se encuentran a partir de U+1F000, y en UTF-16 se representan como pares sustitutos. Por ejemplo, 🌸 (U+1F338) se almacena en UTF-16 como dos unidades de 16 bits: D83C DF38. Este hecho es la causa directa de que en JavaScript un solo emoji tenga longitud 2.
Comportamiento en JavaScript
El String de JavaScript se representa internamente en UTF-16, por lo que .length cuenta los pares sustitutos como 2.
| Expresión | Resultado | Significado |
|---|---|---|
"あ".length | 1 | Dentro del BMP, no necesita par |
"🌸".length | 2 | Compuesto por un par sustituto |
[..."🌸"].length | 1 | Descomposición por punto de código |
"🌸".codePointAt(0) | 127800 | Valor del punto de código combinado |
Diferencias con UTF-8 / UTF-32
Los pares sustitutos son un mecanismo exclusivo de UTF-16. UTF-8 representa todos los puntos de código con 1 a 4 bytes de longitud variable, y UTF-32 los maneja todos con 4 bytes fijos. En lenguajes y sistemas que usan UTF-8 o UTF-32, el concepto de par sustituto no aparece.
Uso en la práctica
- Validación de longitud de texto: cuando un formulario necesita contar el máximo de caracteres en «unidades que el usuario percibe como un carácter», es necesario entender los pares sustitutos
- Procesamiento de texto: al extraer subcadenas, invertir texto o hacer coincidencias con expresiones regulares, hay que tratar el par sustituto como una sola unidad
- Nombres de archivo y bases de datos: sistemas antiguos que no manejan correctamente los pares sustitutos pueden corromper datos que contienen emojis
- Conteo de caracteres en redes sociales: las diferencias en la lógica de conteo entre plataformas surgen de cómo tratan los pares sustitutos y los clusters de grafemas
Cómo contar correctamente
Si quieres contar en la unidad que el usuario percibe como «1 carácter», necesitas contar por clusters de grafemas, no por pares sustitutos. En JavaScript moderno, Intl.Segmenter está disponible de forma estándar y trata correctamente los emojis y las secuencias ZWJ como una sola unidad.
Errores comunes
- ❌ «Todos los emojis se cuentan como 2 caracteres» → ✅ Los emojis por debajo de U+FFFF (algunos símbolos antiguos) no necesitan par y cuentan como 1
- ❌ «Los pares sustitutos también se usan en UTF-8» → ✅ Es un mecanismo exclusivo de UTF-16
- ❌ «Un sustituto alto solo es un carácter válido» → ✅ Aislado es una cadena Unicode inválida (Unpaired Surrogate)