Microsoft no sabe Unicode

I [entity] Unicode

El otro día estaba hablando con el DBA de SQL Server de donde trabajo, revisando una base que habíamos diseñado con los chicos del equipo de arquitectura y se dio una conversación mas o menos como la siguiente:

DBA: ¿Porque nvarchar en lugar de varchar? *

Yo: Es porque guardamos caracteres no-ascii en esos campos, así que necesitamos que fueran unicode.

DBA: Pero eso va a ocupar el doble de espacio.

Yo: ¿Por que? Si creamos la base de datos en latin-1 debería guardarlos en un solo carácter. **

DBA:  No, unicode siempre guarda todo usando 2 bytes.

Yo:  No, unicode no dice como guardar cosas, unicode le da un numero a cada letra y nada mas, mira, lee esto.

* El prefijo “n” en los tipos de datos de SQL Server denota unicode.
** Lo que había especificado, descubrí después, es el collation, es decir como compara las cadenas de texto cuando se hacen joins u order by.

El texto que le pase es The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) de Joel Spolsky, pero lo que me asombro fue esa ultima frase “unicode siempre guarda todo usando 2 bytes”, después de un poco de investigación por parte de ambos encontramos esto en MSDN:

The Unicode specification addresses this problem by using 2 bytes to encode each character.

- Using Unicode Data

En ese mismo momento hice Plop!

Plop!

La documentación de Microsoft se equivoca: Unicode no dice como representar los caracteres.

El texto, como todo dato que se guarda en una computadora moderna, se representa con ceros y unos. Estos ceros y unos, llamados bits, se unen de a ocho para formar un byte, el byte representa la unidad mínima de datos que se puede leer (esto es una limitación creada por los distintos canales de entrada y salida de datos, como la red o el disco rígido). Ahora un byte, con sus ocho bits, solamente puede representar 256 combinaciones, desde 00000000 hasta 11111111. 256 combinaciones no son suficientes para representar todos los caracteres; nosotros en el alfabeto tenemos 27 letras incluyendo la ñ, pero hay otros caracteres, como el punto, la coma, etc. Si a esto le sumamos otras letras no propias del castellano (como las de los idiomas orientales), tenemos miles de caracteres.

Un poco de historia: En la primer época de la informática, con la única influencia cultural del ingles, se utilizaba ASCII para representar el texto: Una forma de identificar un numero cuya representación entraba en un byte con una letra o carácter, hoy en día es bastante común ver esta tabla pegada en cualquier pared de una habitación donde halla programadores:

ASCII table

Pero en cuanto la computadora se empezó a usar en otros entornos, y el guardar textos de otras culturas se hizo necesario, como por ejemplo textos en castellano con caracteres como la eñe, este modelo hizo agua. Se solvento la situación usando codepages, así un documento creado en Argentina se guardaba en el Code Page 850 (también conocido como “Multilingual (Latin-1) Western European languages”), y otro creado en Grecia usaba el Code Page 737.

Estos Code Pages utilizaban esas 256 combinaciones para representar distintas letras, así el numero que en el codepage 737 significaba β en el codepage 850 significaba Ö. Los primeros 128 números siempre eran los mismos que en la tabla ASCII, mientras que los siguientes 128 se utilizaban para letras que variaban de codepage en codepage.

Aunque precaria, esta solución funciono durante bastante tiempo, el problema que no pudo afrontar es el intercambiar archivos entre distintas culturas (Algo muy común en Internet): Si yo enviaba mi archivo a Rusia quien tratara de abrirlo iba a ver todo mi texto como algo incomprensible, dado que lo iba a estar tratando de interpretar en caracteres con un codepage distinto en el que yo lo cree.

Ahí es donde entra unicode: Definir como representar todas las letras del mundo, y de otros mundos también, de forma univoca y con una forma de identificar cada letra. Ese es el objetivo de unicode:

Fundamentally, computers just deal with numbers. They store letters and other characters by assigning a number for each one. Before Unicode was invented, there were hundreds of different encoding systems for assigning these numbers. No single encoding could contain enough characters: for example, the European Union alone requires several different encodings to cover all its languages. Even for a single language like English no single encoding was adequate for all the letters, punctuation, and technical symbols in common use.

These encoding systems also conflict with one another. That is, two encodings can use the same number for two different characters, or use different numbers for the same character. Any given computer (especially servers) needs to support many different encodings; yet whenever data is passed between different encodings or platforms, that data always runs the risk of corruption.

- What is Unicode?

Unicode no define como guardar o representar esas letras, para eso existe un proyecto paralelo llamado Universal Character Set que si propone distintas formas de guardarlas.

Con eso en mente, repasemos la frase de MSDN:

The Unicode specification addresses this problem by using 2 bytes to encode each character.

2 bytes no son suficientes para la ambición del proyecto unicode, existen miles y miles de letras mas que las 65.536 que 2 bytes permiten representar.

Y en luz de todo esto surge el concepto de encodings: Formas de representar los distintos caracteres. Ejemplos de encoding son utf-8, latin-1 y ucs2.

Los encodings como UTF-8 o UCS2 nacieron con grandes ambiciones: Representar todos y cada uno de los caracteres que definiera el proyecto unicode. UCS2 encontró su techo pronto, al ser de un largo fijo, 2 bytes, una ves que supero los 65.536 caracteres se quedo corto. En cambio UTF-8 propone un esquema variable, donde un carácter puede ocupar 1 byte (Como todos los caracteres que componen ASCII) y (Como todo el resto de las letras) ocupan mas.

Otros encodings, como latin-1 ocupan siempre 1 byte, solamente se limitan a algunos caracteres que pueden representar con 256 opciones.

Cada uno de los encodings tienen sus ventajas y desventajas: Los encodings variables como UTF-8 permiten representar cadenas de texto en muchos lenguajes siendo eficientes en el espacio que ocupan (tanto sea en disco como en una comunicación por la red), pero son mas lentos de interpretar que un encodings como UCS2 en el cual sabemos de antemano que cada 2 bytes tenemos un carácter. Revisando un poco mas encontré este otro articulo de MSDN donde correctamente dicen que lo que usan es UCS2 y no “unicode”. Entiendo la elección de un encoding fijo para interpretar las cadenas de texto internamente, es igual a la que hace Python (en CPython al menos) para manejar todos los objetos del tipo de datos unicode como UCS2 en memoria.

Hoy en día vivimos en un escenario bastante complejo. El hecho de tener varias opciones para representar el texto nos hace no solo tener que elegir una, si no saber la que otros eligieron, me explico:

Si dos sistemas tienen que intercambiar texto entre si, deben también tener que intercambiar en que encoding esta ese texto.

Y es ahi donde empiezan los problemas que solemos encontrar: ¿Que pasa si un sistema no me dice en que encoding esta el texto que me envía?, ¿O si me lo dice en varios lugares y difieren entre si? ¿Y si lo que me dice esta mal?

Un ejemplo claro son los sitios web. Una pagina cualquiera tiene tres lugares donde puede definir en que encoding esta el texto que presenta:

En primer lugar esta el header de Content-Type, que al pedir la pagina puede decirnos el encoding, como por ejemplo “Content-Type: text/html; charset=ISO-8859-1”.

En segundo lugar esta el header XML, en caso de ser un XHTML es muy común que la primer linea del documento sea algo como “<?xml version=”1.0″ encoding=”iso-8859-1″?>”.

En tercer y ultimo lugar esta el header META, dentro del head del HTML: “content = “text/html; charset=iso-8859-1″>”

En ese caso todos dicen el mismo encoding “iso-8859-1″ (también conocido como latin-1), pero si nos imaginamos un caso donde digan algo distinto: ¿a cual debemos creerle? Es un dilema difícil, este problema en particular los web browsers lo solucionan con un algoritmo que intenta adivinar el encoding, el ejemplo de Mozilla Labs (Firefox, Camino, etc) se puede ver online aprovechando que es open source, y ese mismo algoritmo fue portado a Python por Mark Pilgrim, de quien hable en mi post pasado.

En otros usos es mas complicado aun, hay protocolos o documentos que no nos informan del encoding, y no queda claro como abrirlos. Algunos programas caen en preguntarle al usuario, claro que para alguien no muy técnico es poco claro el tener que elegir algo así, mientras que otros programas hacen fallbacks, es decir que tratan de abrir los archivos con un encoding, si ese no funciona intentan con otro, y así sucesivamente.

Otro dia seguramente haga un post con aplicando estos conceptos a programar, por ahora recomiendo la charla Unicode (pa’ empezar nomás) de Facundo Batista, y los otros textos que linkie en este articulo, al menos para Python.