¿De dónde vienen los mojibakes? Fundamentos de codificaciones


Este artículo explora los conceptos básicos detrás de la codificación de caracteres y luego profundiza en los detalles técnicos de los sistemas de codificación.


Si solo tiene un conocimiento básico de la codificación de caracteres y desea comprender mejor los elementos esenciales, las diferencias entre los sistemas de codificación, por qué a veces terminamos con texto sin sentido y los principios detrás de la diferente arquitectura del sistema de codificación, siga leyendo.


Entender la codificación de caracteres en detalle requiere una lectura extensa y una buena cantidad de tiempo. Traté de ahorrarle algo de ese esfuerzo reuniéndolo todo en un solo lugar mientras proporciono lo que creo que es un fondo bastante completo del tema.


Voy a repasar cómo funcionan las codificaciones de un solo byte (ASCII, Windows-1251, etc.), la historia de cómo surgió Unicode, las codificaciones basadas en Unicode UTF-8, UTF-16 y cómo difieren, el características específicas, compatibilidad y falta de ellas entre varias codificaciones, principios de codificación de caracteres y una guía práctica sobre cómo se codifican y decodifican los caracteres.


Si bien la codificación de caracteres puede no ser un tema de vanguardia, es útil comprender cómo funciona ahora y cómo funcionó en el pasado sin perder mucho tiempo.


Historia de unicode


Creo que es mejor comenzar nuestra historia desde el momento en que las computadoras no eran tan avanzadas ni tan comunes como una parte de nuestras vidas como lo son ahora. Los desarrolladores e ingenieros que intentaban crear estándares en ese momento no tenían idea de que las computadoras e Internet serían tan populares y dominantes como ellos. Cuando eso sucedió, el mundo necesitaba codificaciones de caracteres.


Pero, ¿cómo podría tener una computadora para almacenar caracteres o letras cuando solo entendía unos y ceros? De esta necesidad surgió la primera codificación ASCII de 1 byte, que aunque no necesariamente fue la primera codificación, fue la más utilizada y estableció el punto de referencia. Por lo tanto, es un buen estándar de uso.


¿Pero qué es ASCII? El código ASCII consta de 8 bits. Algunos cálculos aritméticos fáciles muestran que este juego de caracteres contiene 256 símbolos (ocho bits, ceros y unos 2⁸ = 256).


Los primeros 7 bits - 128 símbolos (2⁷ = 128) en el conjunto se usaron para letras latinas, caracteres de control (como saltos de línea, pestañas, etc.) y símbolos gramaticales. Las otras partes eran para idiomas nacionales. De esta manera, los primeros 128 caracteres son siempre los mismos, y si desea codificar su idioma nativo, ayúdese con los símbolos restantes.


Esto dio lugar a una panoplia de codificaciones nacionales. Termina con una situación como esta: digamos que está en Rusia creando un archivo de texto que, de forma predeterminada, utilizará Windows-1251 (la codificación rusa utilizada en Windows). Y envía su documento a alguien fuera de Rusia, digamos en los Estados Unidos. Incluso si el destinatario sabe ruso, no tendrá suerte cuando abra el documento en su computadora (con el software de procesamiento de texto usando ASCII como código predeterminado) porque verá caracteres extraños ilegibles (mojibake) en lugar de letras rusas . Más precisamente, cualquier letra en inglés aparecerá bien, porque los primeros 128 símbolos en Windows-1251 y ASCII son idénticos, pero donde haya texto en ruso, el software de procesamiento de texto de nuestro destinatario usará la codificación incorrecta a menos que el usuario haya configurado manualmente el carácter correcto codificación


El problema con los estándares del código de carácter nacional es obvio. Y, finalmente, estos códigos nacionales comenzaron a multiplicarse, Internet comenzó a explotar y todos querían escribir en su idioma nacional sin producir estos mojibakes indescifrables.


Había dos opciones en este punto: usar una codificación para cada país o crear un mapa de caracteres universal para representar a todos los personajes del planeta.


Una breve cartilla sobre ASCII


Puede parecer demasiado elemental, pero si vamos a ser minuciosos tenemos que cubrir todas las bases.



Hay 3 grupos de columnas en la tabla ASCII:


  • el valor decimal del caracter
  • El valor hexadecimal del personaje
  • el glifo del personaje en sí

Digamos que queremos codificar la palabra "ok" en ASCII. La letra "o" tiene un valor decimal de 111 y 6F en hexadecimal. En binario sería - 01101111. La letra "k" es la posición 107 en decimal y 6B en hexadecimal, o - 01101011 en binario. Entonces la palabra "OK" en ASCII se vería como 01101111 01101011. El proceso de decodificación sería lo opuesto. Comenzamos con 8 bits, los traducimos a codificación decimal y terminamos con el número de caracteres, y buscamos en la tabla el símbolo correspondiente.


Unicode


De lo anterior, debería ser bastante obvio por qué se necesitaba un solo mapa de caracteres común. ¿Pero cómo se vería? La respuesta es Unicode, que en realidad no es una codificación, sino un conjunto de caracteres. Consiste en 1,114,112 posiciones, o puntos de código, la mayoría de los cuales todavía están vacíos, por lo que no es probable que el conjunto deba expandirse.


El estándar Unicode consta de 17 planos con 65.536 puntos de código cada uno. Cada plano contiene un grupo de símbolos. El plano cero es el plano multilingüe básico donde encontramos los caracteres más utilizados en todos los alfabetos modernos. El segundo plano contiene caracteres de idiomas muertos. Incluso hay dos aviones reservados para uso privado. La mayoría de los aviones todavía están vacíos.


Unicode tiene puntos de código para 0 a 10FFFF (en hexadecimal).


Los caracteres están codificados en formato hexadecimal precedido por una "U +". Entonces, por ejemplo, el primer plano básico incluye los caracteres U + 0000 a U + FFFF (0 a 65,535), y el bloque 17 contiene U + 100000 a U + 10FFFF (1,048,576 a 1,114,111).


Ahora, en lugar de una colección de numerosas codificaciones, tenemos una tabla que abarca todos los símbolos y caracteres que podríamos necesitar. Pero no está exento de fallas. Si bien cada carácter se codificó previamente por un byte, ahora se puede codificar utilizando diferentes números de bytes. Por ejemplo, solía necesitar solo un byte para codificar todas las letras del alfabeto inglés. Por ejemplo, la letra latina "o" en Unicode es U + 006F. En otras palabras, el mismo número que en ASCII: 6F en hexadecimal y 111 en binario. Pero para codificar el símbolo "U + 103D5" (el número persa "100"), necesitamos 103D5 en hexadecimal y 66.517 en decimal, y ahora necesitamos tres bytes.


Esta complejidad debe abordarse mediante codificaciones Unicode como UTF-8 y UTF-16. Y más adelante los veremos.


Utf-8


UTF-8 es una codificación Unicode del sistema de codificación de ancho variable que se puede utilizar para mostrar cualquier símbolo Unicode.


¿Qué queremos decir cuando hablamos de ancho variable? En primer lugar, debemos entender que la unidad estructural (atómica) en la codificación es un byte. La codificación de ancho variable significa que un carácter puede codificarse utilizando diferentes números de unidades o bytes. Por ejemplo, las letras latinas están codificadas con un byte y las letras cirílicas con dos.


Antes de continuar, un poco a un lado con respecto a la compatibilidad entre ASCII y UTF.


El hecho de que las letras latinas y los caracteres de control de teclas, como saltos de línea, tabulaciones, etc. contener un byte hace que la codificación UTF sea compatible con ASCII. En otras palabras, la escritura latina y los caracteres de control se encuentran exactamente en los mismos puntos de código en ASCII y UTF y están codificados usando un byte en ambos, y por lo tanto son compatibles con versiones anteriores.


Usemos la letra "o" de nuestro ejemplo ASCII de antes. Recuerde que su posición en la tabla ASCII es 111, o 01101111 en binario. En la tabla Unicode, es U + 006F, o 01101111. Y ahora como UTF es un sistema de codificación de ancho variable "o" sería un byte. En otras palabras, "o" se representaría de la misma manera en ambos. Y lo mismo para los caracteres 0 - 128. Entonces, si su documento contiene letras en inglés, no notaría una diferencia si lo abriera usando UTF-8, UTF-16 o ASCII, y solo notaría una diferencia si comenzara a trabajar con codificaciones nacionales.


Veamos cómo aparecería la frase mixta inglés / ruso "Hola mundo" en tres sistemas de codificación diferentes: Windows-1251 (codificación rusa), ISO-8859-1 (sistema de codificación para idiomas de Europa occidental), UTF-8 (Unicode) . Este ejemplo es revelador porque tenemos una frase en dos idiomas diferentes.



Ahora consideremos cómo funcionan estos sistemas de codificación y cómo podemos traducir una línea de texto de una codificación a otra, y qué sucede si los caracteres se muestran incorrectamente, o si simplemente no podemos hacer esto debido a las diferencias en los sistemas.


Supongamos que nuestra frase original fue escrita con codificación Windows-1251. Cuando miramos la tabla de arriba podemos ver, traduciendo de decimal o hexadecimal a decimal, que obtenemos la siguiente codificación en binario usando Windows-1251.


01001000 01100101 01101100 01101100 01101111 00100000 11101100 11101000 11110000


Así que ahora tenemos la frase "Hola mundo" en la codificación de Windows-1251.


Ahora imagine que tenemos un archivo de texto pero no sabemos en qué sistema de codificación se guardó el texto. Asumimos que está codificado en ISO-8859-1 y lo abrimos en nuestro procesador de textos usando este sistema de codificación. Como vimos anteriormente, algunos de los caracteres parecen estar bien, ya que existen en este sistema de codificación, e incluso están en los mismos puntos de código, pero los caracteres en la palabra rusa "mundo" no funcionan tan bien. Estos caracteres no existen en el sistema de codificación, y en sus lugares o puntos de código, en ISO-8859-1 encontramos caracteres completamente diferentes. Entonces "m" es el punto de código 236, "y" es 232, y "p" es 240. Pero en ISO-8859-1 estos puntos de código corresponden a "ì" (236), "è" (232) y " ð ”(240).


Por lo tanto, nuestra frase de lenguaje mixto "Hello World" codificada en Windows-1251 y leída en ISO-8859-1 se verá como "Hello ìèð". Tenemos compatibilidad parcial y no podemos mostrar una frase codificada en un sistema correctamente en el otro, porque los símbolos que necesitamos simplemente no existen en la segunda codificación.


Necesitamos una codificación Unicode; en nuestro caso, usaremos UTF-8 como ejemplo. Ya hemos discutido que los caracteres pueden tomar entre 1 y 4 bytes en UTF-8, pero otra ventaja es que UTF, a diferencia de los dos sistemas de codificación anteriores, no está restringido a 256 símbolos, sino que contiene todos los símbolos en el conjunto de caracteres Unicode .


Funciona de la siguiente manera: el primer bit de cada carácter codificado no corresponde al glifo o símbolo en sí, sino a un byte específico. Entonces, si el primer bit es cero, sabemos que el símbolo codificado usa solo un byte, lo que hace que el conjunto sea compatible con ASCII. Si observamos detenidamente la tabla de símbolos ASCII, vemos que los primeros 128 símbolos (el alfabeto inglés, los caracteres de control y los signos de puntuación) se expresan en binario, todos comienzan con un valor de bit de 0 (tenga en cuenta que si traduce caracteres a binario usando un convertidor en línea o cualquier cosa similar, el primer bit de orden superior cero puede descartarse, lo que puede ser un poco confuso).


01001000: el valor del primer bit es 0, por lo que 1 byte codifica 1 carácter -> "H".


01100101: el valor del primer bit es 0, por lo que 1 byte codifica 1 carácter-> "e".


Si el valor del primer bit no es cero, el símbolo se codificará en varios bytes.


Una codificación de dos bytes tendrá 110 para los primeros tres valores de bit.


11010000 10111100: los bits de marcador son 110 y 10, por lo que utilizamos 2 bytes para codificar 1 carácter. El segundo byte en este caso siempre comienza con "10". Por lo tanto, omitimos los bits de control (los bits principales que están resaltados en rojo y verde) y miramos el resto del código (10000111100), y lo convertimos a hexadecimal (043) -> U + 043C que nos da la "m" rusa en Unicode.


Los bits iniciales para un carácter de tres bytes son 1110.


11101000 10000111 101010101: sumamos todos los bits, excepto los bits de control, y encontramos que en hexadecimal tenemos 103B5, U + 103D5, el antiguo número persa cien (10000001111010101).


Las codificaciones de caracteres de cuatro bytes comienzan con los bits iniciales 11110.


11110100 10001111 10111111 10111111 - U + 10FFFF, que es el último carácter disponible en el conjunto Unicode (10000111111111111111111).


Ahora, podemos escribir fácilmente nuestra frase en varios idiomas en codificación UTF-8.


Utf-16


UTF-16 es otra codificación de ancho variable. La principal diferencia entre UTF-16 y UTF-8 es que UTF-16 usa 2 bytes (16 bits) por unidad de código en lugar de 1 bye (8 bits). En otras palabras, cualquier carácter Unicode codificado en UTF-16 puede tener dos o cuatro bytes. Para simplificar las cosas, me referiré a estos dos bytes como una unidad de código. Entonces, en UTF-16, cualquier carácter puede representarse utilizando una o dos unidades de código.


Comencemos con los símbolos codificados usando una unidad de código. Podemos calcular fácilmente que hay 65.535 (216) caracteres con una unidad de código, que se alinea completamente con el plano multilingüe básico de Unicode. Todos los caracteres en este plano estarán representados por una unidad de código (dos bytes) en UTF-16.


Letra latina "o" - 00000000 01101111.


Letra cirílica "M" - 00000100 00011100.


Ahora consideremos caracteres fuera del plano multilingüe básico. Estos requieren dos unidades de código (4 bytes) y están codificados de una manera un poco más complicada.


Primero, necesitamos definir el concepto de un par sustituto. Un par sustituto son dos unidades de código utilizadas para codificar un solo carácter (totalizando 4 bytes). El conjunto de caracteres Unicode reserva un rango especial D800 a DFFF para pares sustitutos. Esto significa que al convertir un par sustituto a bytes en hexadecimal, terminamos con un punto de código en este rango que es un par sustituto en lugar de un carácter separado.


Para codificar un símbolo en el rango de 10000 - 10FFFF (es decir, caracteres que requieren más de una unidad de código) procedemos de la siguiente manera:


  1. Reste 10000 (hexadecimal) del punto de código (este es el punto de código más bajo en el rango de 10000 - 10FFFF).


  2. Terminamos con un número de hasta 20 bits no mayor que FFFF.


  3. Los 10 bits de orden superior con los que terminamos se agregan a D800 (el punto de código más bajo en el rango de pares sustitutos en Unicode).


  4. Los siguientes 10 bits se agregan a DC00 (también del rango de pares sustitutos).


  5. A continuación, terminamos con 2 unidades de código sustituto de 16 bits, cuyos primeros 6 bits definen la unidad como parte de un par sustituto.


  6. El décimo bit en cada sustituto define el orden del par. Si tenemos un "1" es el sustituto principal o alto, y si tenemos un "0" es el sustituto final o bajo.



Esto tendrá un poco más de sentido cuando se ilustra con el siguiente ejemplo.


Codifiquemos y luego decodifiquemos el número persa cien (U + 103D5):


  1. 103D5 - 10000 = 3D5.


  2. 3D5 = 0000000000 1111010101 (los 10 bits más altos son cero, y cuando se convierten a hexadecimales terminamos con "0" (los primeros diez) y 3D5 (los segundos diez)).


  3. 0 + D800 = D800 (1101100000000000) los primeros 6 bits nos dicen que este punto de código cae en el rango del par sustituto, el décimo bit (desde la derecha) tiene un valor "0", por lo que este es el sustituto alto.


  4. 3D5 + DC00 = DFD5 (1101111111010101) los primeros 6 bits nos dicen que este punto de código cae en el rango del par sustituto, el décimo bit (desde la derecha) es un "1", por lo que sabemos que este es el sustituto bajo.


  5. El carácter resultante codificado en UTF-16 se ve como - 1101100000000000 1101111111010101.



Ahora decodifiquemos el personaje. Digamos que tenemos el siguiente punto de código: 1101100000100010 1101111010001000:


  1. Convertimos a hexadecimal = D822 DE88 (ambos puntos de código caen en el rango de pares sustitutos, por lo que sabemos que estamos tratando con un par sustituto).


  2. 1101100000100010 - el décimo bit (desde la derecha) es un "0", por lo que este es el sustituto alto.


  3. 1101111010001000: el décimo bit (desde la derecha) es un "1", por lo que este es el sustituto bajo.


  4. Ignoramos los 6 bits que identifican esto es como un sustituto y quedamos con 0000100010 1010001000 (8A88).


  5. Agregamos 10000 (el punto de código más bajo en el rango sustituto) 8A88 + 10000 = 18A88.


  6. Observamos la tabla Unicode para U + 18A88 = Tangut Component-649.



¡Felicitaciones a todos los que leen hasta aquí!


Espero que esto haya sido informativo sin dejarte demasiado aburrido.


También te puede resultar útil:

El conjunto de caracteres unicode


Estrategias para la localización de contenido: basada en IP o navegador


Sobre el traductor


Alconost es un proveedor global de servicios de localización para aplicaciones , juegos , videos y sitios web en más de 70 idiomas. Ofrecemos traducciones de lingüistas nativos, pruebas lingüísticas, flujo de trabajo basado en la nube, localización continua, gestión de proyectos 24/7 y trabajamos con cualquier formato de recursos de cadena. También hacemos videos e imágenes publicitarias y educativas, teasers, explicadores y trailers para Google Play y App Store.

Source: https://habr.com/ru/post/480688/


All Articles