Apéndice A: Codificación ASCII

El origen

Estamos acostumbrados a que los ordenadores manejen cualquier tipo de información: números, textos, gráficos, sonidos, fórmulas... Sin embargo, en realidad, los ordenadores sólo pueden manejar números, y más concretamente, sólo ceros y unos.

De hecho, si profundizamos más, incluso esto es una convención, y en lo más profundo, lo único que encontraremos en un ordenador son células básicas que pueden contener y manipular dos estados. A uno de esos estados se le asigna valor lógico cero, y al otro un uno.

Los técnicos electrónicos llaman a cada uno de estas células biestable, precisamente, porque pueden tener dos estados. A cada uno de esos estados se le llama bit. Un bit es la unidad mínima de información.

Desde los primeros ordenadores, acceder a bits como unidad ya resultaba poco práctico, así que los bits se agruparon formando unidades mayores.

En la práctica, nosotros hacemos lo mismo con los números, para manejar números mayores de nueve usamos dos dígitos, de modo que cada uno de ellos tiene un valor diferente según la posición que ocupe. Por ejemplo, en el número 23, el 2 tiene un peso 10 veces mayor que el 3, ya que ocupa una posición más a la izquierda. Si usamos tres dígitos, el tercero por la derecha tiene un peso 100 veces mayor que el primero, y así suscesivamente.

Los primeros ordenadores usaban unidades de cuatro bits, nibbles. El motivo era, sencillamente, simplificar el acceso a la información por parte de los humanos. Para almacenar un dígito en base 10, que son los que usamos nosotros, se necesitan al menos cuatro dígitos binarios.

Veamos esto. Análogamente a lo que pasa con nuestros números en base 10, cada posición contando desde la derecha será dos veces mayor que la anterior. El primer dígito, desde la derecha, tiene peso 1, el segundo 2, el tercero 4, el cuarto 8, etc.

Estas relaciones de peso son potencias de la base de numeración elegida. En base 10 son potencias de 10: 100 (=1), 101 (=10), 102 (=100), 103 (=1000), 104 (=10000), etc. En base 2 son potencias de dos: 20 (=1), 21 (=2), 22 (=4), 23 (=8), 24 (=16), etc.

Así, con tres bits tenemos que el número mayor que podemos codificar es "111", es decir:

1*22+1*21+1*20 =
  = 1*4+1*2+1*1 = 4+2+1 = 7

Esto es insuficiente para codificar números entre 0 y 9, nos faltan dos valores, por lo tanto, necesitamos otro bit. Con cuatro podemos llegar hasta:

1*23+1*22+1*21+1*20 =
  = 1*8+1*4+1*2+1*1 = 8+4+2+1 = 15

Con esto, en realidad, nos sobran seis valores, pero podemos ignorarlos (de momento).

Esta forma de codificación se denomina BCD, es decir, Decimal Codificado en Binario. La tabla de valores BCD es la siguiente:

Tabla BCD
Código binario Dígito decimal
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9

Durante un tiempo, los ordenadores trabajaban con palabras de cuatro bits, siempre se ha llamado palabras a las agrupaciones de bits, y según la tecnología ha ido avanzando, las palabras han ido teniendo más bits.

Para simplificar y sobre todo, para conseguir mejores resultados, el acceso a la memoria también se hace mediante palabras. El procesador no accede a bits de memoria, sino a palabras, y la memoria se organiza por palabras. A cada una de esas palabras se le asigna una dirección: una dirección de memoria.

De modo que en un procesador de cuatro bits, cada dirección de memoria contiene un nibble, y en cada nibble se pueden almacenar un dígito decimal en la forma de dígitos BCD o una instrucción del proceador.

Entre los avances más destacados de los procesadores están el de usar la misma memoria para almacenar los datos y los programas. El procesador puede leer una instrucción de programa desde la memoria, ejecutarla, pasar a la siguiente instrucción, y repetir el proceso.

Siguiendo la misma idea, se puede acceder a varias direcciones contiguas de memoria para almacenar números mayores o instrucciones más complejas (16 instrucciones son pocas incluso para un ordenador primitivo), con dos nibbles tenemos posibilidad de almancenar 256 instrucciones.

Las palabras de cuatro bits se quedaron pequeñas pronto, y rápidamente se pasó a palabras y a procesadores de 8 bits. La unidad de 8 bits se conoce como octeto o byte, y este tamaño tuvo tanto éxito que sigue siendo hoy en día la unidad básica para acceder a memoria.

Procesadores posteriores usan palabras que agrupaban bytes, los de 16 bits usan dos bytes, los de 32 usan cuatro bytes, y los de 64 ocho bytes. Sin embargo, se sigue usando una dirección de memoria para cada byte.

Los primeros procesadores eran en realidad máquinas calculadoras programables, es decir, sólo manejaban números y se usaban para realizar cálculos numéricos complejos o repetitivos. Pero según fue aumentando la complejidad de las máquinas, surgieron nuevas posibilidades, como proporcionar salidas más cómodas para las personas.

Pero los ordenadores siguen manipulado sólo números, por lo que había que inventar algo para que pudieran manejar letras y palabras. La solución es simple, y consiste en codificar cada letra mediante un valor numérico. De este modo nació el código ASCII.

El primer código ASCII almacenaba cada letra en un byte, pero usaba sólo 7 bits, dejando el octavo para añadir un bit de control de errores, que permite detectar fallos en las transmisiones de datos. Este bit se llama bit de paridad, y funciona del modo siguiente:

Para cada código ASCII se suman los bits con valor 1, si se usa un control de paridad par la suma de los bits en cada byte debe ser par (contando el bit de paridad), si se usa un control de paridad impar, la suma debe ser impar.

De todos modos, con siete bits se pueden codificar 128 caracteres, que según las personas que diseñaron el código, eran más que suficientes. Esto permite codificar los 10 digitos numéricos, los 25 caracteres del alfabeto inglés en mayúsculas, otros 25 para las minúsculas, 32 para caracteres destinados a símbolos, paréntesis, y signos de puntuación, el espacio. El resto, hasta 127 (33 caracteres), se usaron para codificar caracteres no imprimibles, que permiten formatear texto: retornos de línea, avances y retrocesos de caracteres, caracteres destinados a protrocolos de transmisión, etc.

Tabla ASCII

Tabla ASCII correspondiente a los primeros 32 caracteres y al último. Estos son los no imprimibles:

Tabla ASCII (Caracteres no imprimibles)
Código binarioCódigo decimalAbreviaturaNombre
000000000NULCaracter Nulo (NULL)
000000011SOHInicio de Encabezado (Start Of Header)
000000102STXInicio de Texto (Start Text)
000000113ETXFin de Texto (End Text)
000001004EOTFin de Transmisión (End Of Transmision)
000001015ENQPregunta (Enquiry)
000001106ACKReconocimiento (Acknowledgement)
000001117BELTimbre (Bell)
000010008BSRetroceso (Back Space)
000010019HTTabulación horizontal (Horizontal Tab)
0000101010LFAvance de Línea (Line feed)
0000101111VTTabulación Vertical (Vertical Tab)
0000110012FFSalto de página (Form feed)
0000110113CRRetorno de carro (Carriage return)
0000111014SOSalida de turno (Shift Out)
0000111115SIEntrada de turno (Shift In)
0001000016DLEEscape de enlace de datos (Data Link Escape)
0001000117DC1Device Control 1 — oft. XON
0001001018DC2Device Control 2
0001001119DC3Device Control 3 — oft. XOFF
0001010020DC4Device Control 4
0001010121NAKReconocimiento negativo (Negative Acknowledgement)
0001011022SYNSincronismo sin usar (Synchronous Idle)
0001011123ETBFin de transmisión de bloque (End of Trans. Block)
0001100024CANCancelar (Cancel)
0001100125EMFin de soporte (End of Medium)
0001101026SUBSustituto (Substitute)
0001101127ESCEscape
0001110028FSSeparador de fichero (File Separator)
0001110129GSSeparador de grupo (Group Separator)
0001111030RSSeparador de registro (Record Separator)
0001111131USSeparador de unidad (Unit Separator)
01111111127DELBorrar (Delete)

Tabla ASCII correspondiente a los caracteres imprimibles:

Tabla ASCII (Caracteres imprimibles)
BinarioDecimalCarácterBinarioDecimalCarácterBinarioDecimalCarácter
0010000032espacio0100000064@0110000096`
0010000133!0100000165A0110000197a
0010001034"0100001066B0110001098b
0010001135#0100001167C0110001199c
0010010036$0100010068D01100100100d
0010010137%0100010169E01100101101e
0010011038&0100011070F01100110102f
0010011139'0100011171G01100111103g
0010100040(0100100072H01101000104h
0010100141)0100100173I01101001105i
0010101042*0100101074J01101010106j
0010101143+0100101175K01101011107k
0010110044,0100110076L01101100108l
0010110145-0100110177M01101101109m
0010111046.0100111078N01101110110n
0010111147/0100111179O01101111111o
001100004800101000080P01110000112p
001100014910101000181Q01110001113q
001100105020101001082R01110010114r
001100115130101001183S01110011115s
001101005240101010084T01110100116t
001101015350101010185U01110101117u
001101105460101011086V01110110118v
001101115570101011187W01110111119w
001110005680101100088X01111000120x
001110015790101100189Y01111001121y
0011101058:0101101090Z01111010122z
0011101159;0101101191[01111011123{
0011110060<0101110092\01111100124|
0011110161=0101110193]01111101125}
0011111062>0101111094^01111110126~
0011111163?0101111195_

Las letras son números

Bueno, creo que ahora queda más claro por qué se puede usar un valor entero como un carácter o como un número. Dentro de un ordenador no existe diferencia entre ambas cosas.

El valor 65 puede ser un número entero, o, si se interpreta como un carácter, puede ser la letra 'A'.

Manejar signos

Pero aún nos queda un detalle importante: el signo. Cuando hemos hablado de variables de tipo char hemos comentado que pueden ser con y sin signo. Veamos cómo se las arregla el ordenador con los signos.

Lo primero que podemos decir del signo es que hay dos posibilidades: puede ser positivo o negativo. Esto parece hecho a la medida de un bit, de modo que podemos usar un único bit para indicar el signo, de modo que un valor 0 indica que se trata de un número positivo y un 1 de un número negativo.

Esta es la primera solución, podemos usar el bit más a la izquierda para codificar el signo. Con un número de ocho bits, eso nos deja siete para codificar el valor absoluto, es decir, 127 valores posibles positivos y otros tantos negativos.

Pero esta solución tiene dos inconvenientes:

  1. Tenemos dos codificaciones diferentes para el cero. No es un inconveniente pequeño, ya que dificulta las comparaciones y crea muchos casos particulares.
  2. La aritmética se complica en cuanto a sumas y restas. Veremos que esto es sólo en comparación con otros sistemas de codificación.

Existe una solución mejor que mantiene la codificación del bit de signo, pero que elimina estos dos inconvenientes. Se trata de la codificación conocida como "complemento a dos".

Pero antes de ver qué es el complemento a dos, veamos qué es el complemento a uno. El complemento a uno consiste, en tomar un número en binario y cambiar los ceros por unos y los unos por ceros.

El complemento a uno de 01001010 es 10110101.

El complemento a dos consiste en hacer el complemento a uno y sumar 1 al resultado.

Ya sabemos sumar, espero, con números en base diez. Con números en base dos es igual de fácil. La tabla de sumar en binario es:

  • 0 + 0 = 0
  • 0 + 1 = 1
  • 1 + 0 = 1
  • 1 + 1 = 10, es decir: 0 y nos llevamos 1
  • 1 + 1 + 1 = 11, es decir 1 y nos llevamos 1

Por ejemplo:

  111111
  01101011
+ 00110110
----------
  10100001

Sigamos con el complemento a dos. Para el ejemplo anterior, el complemento a dos de 01001010 sería 10110101+1:

        1
  10110101
+ 00000001
----------
  10110110

Lo primero que vemos es que cuando se hace el complemento a dos de cualquier número, el signo cambia. Otra cosa que podemos ver es que el complemento a dos del cero es cero.

Complemento a uno de 00000000 es 11111111, y sumando 1 tenemos:

 11111111
  11111111
+ 00000001
----------
 100000000

El uno de la izquierda no cuenta, ya que sólo tenemos ocho bits, el noveno se pierde, y el resultado es cero.

Pero lo mejor de todo es que la suma de cualquier número con su complemento a dos es siempre cero:

 1111111
  01001010
+ 10110110
----------
 100000000

Esto es una gran ventaja, ya que podemos usar esta propiedad para decir que el complemento a dos equivale a cambiar el signo de un número, ya que la suma de un número y su complemento es cero, y el complemento de cero es cero.

Esto además nos da otra pequeña ventaja. Al tener una sóla combinación para el cero, tenemos un valor extra, de modo que el valor positivo máximo es 127 (01111111), pero para los negativos podemos llegar hasta el -128 (10000000).

Así, si usamos una variable char sin signo para almacenar números, podremos manejar valores entre 0 y 255. Si usamos variables char con signo, los valores posibles estarán entre -128 y 127.

Comentarios de los usuarios (10)

Anónimo
2014-06-05 18:02:08

Hola. Me estoy iniciando en la programación en C++ y la verdad es que tengo muchísimas dudas. Ya leí bastante capítulos pero cuando veo los comentarios de la gente y como resuelve los problemas yo no comprendo nada. Por momentos me da ganas de dejar esto porque pienso que si no lo entiendo es que no es lo mio. Son bastantes datos. Por poner un ejemplo, algo que no entiendo es cuando haces:

#include <stdio.h>

#include <math.h>

int main()

{

double x = 0.2345;

printf( "acos( %f ) = %f\n", x, acos(x) );

return 0;

}

No sé porque pones el operador binario % al lado de la letra f, si yo el ejemplo que leí lo entendí muy bien porque lo usaste en la división 17/7 y si se usa el % el resultado es el resto de esa división. Pero viendo lo que hiciste arriba ya me perdí del todo. Gracias de antemano por la ayuda

Sergi
2014-07-21 22:30:21
int residuo = 9 % 5;

Aquí, % es un operador binario que da el residuo de una división.

printf( "acos( %f ) = %f\n", x, acos(x) );

En este caso, % no es un operador binario, sino que es un carácter, como la letra 'a'.

En la función printf (y en scanf), % se utiliza para decir que ahí va una variable. "%f" es para poner un número con decimales (float, double), que pones después.

printf("Peso: %f", 51.031); // En la pantalla verás: Peso: 51.031
printf("%"); // En la pantalla verás: %

No sé si me explico, pero sería, resumido, que aquí % está dentro de una cadena de caracteres. Del mismo modo que:

char c = '%'

no se refiere al operador %, lo de antes tampoco.

Anonimo
2015-06-21 02:07:07

La neta el pendejo eres tu, por que la diferencia entre ser un programador bueno y un programador mierda es conocer la teoría y saber como es que la computadora funciona internamente, ademas no es tan difícil de entender y si no lo entiendes es mejor que toques tu guitarrita por que no esto para ti la programación no es para las personas que quieren que las cosas digeridas y facilitas es parte del desarrollo de un razonamiento lógico.

Anonimo
2015-06-21 02:09:32

La neta el pendejo eres tu, por que la diferencia entre ser un programador bueno y un programador mierda es conocer la teoría y saber como es que la computadora funciona internamente, ademas no es tan difícil de entender y si no lo entiendes es mejor que toques tu guitarrita por que esto no es para ti, la programación no es para las personas que quieren las cosas digeridas y facilitas es parte del desarrollo de un razonamiento lógico.

Anónimo
2015-11-09 15:48:25

NECESITO AYUDA PARA CREAR UN PROGRAMA EL CUAL ME IMPRIMA UN LETRERO EL CUAL DIGA 'HOLA' Y DESPUES UNA INICIAL. CON VARIABLE 'CHAR'. SOLO TENGO ESTO PERO NOSE PORQUE NO ME IMPRIMA LA LETRA:

#include <iostream.h>

#include <conio.h>

#include <stdio.h>

int main()

{

char letrero; //Definición de una variable tipo caracter

char nombre[$]; //Definición de variable tipo caracter numero 2

char total; //Definición de variable tipo caracter numero 3

cout<<"Dame el letrero "; //Lectura de la variable tipo caracter

cin>>letrero;

total=letrero + nombre;

cout<<total;

cin>>total;

getch();

return 0;

}

Angie
2015-11-12 22:30:22

Buenas tardes. Me gusto su pagina, muy interesante, aporta muchas cosas a las que era ignorante. Mil gracias

Y necesito una ayuda !!!

No me quiere ejecutar el siguiente programa, no encuentro el error, le agradecería que me colaborara

Nose si es necesario incluir la biblioteca de include<stdlib.h>

?

#include<stdio.h>

#include<stdlib.h>

main ()

{

int opcion;

char i;

printf( "Seleccione como quiere el abecedario\n" );

printf( "\n\t 1. Ascendente \n\t 2. Descendente\n" );

scanf( "%i", &opcion );

swintch ( opcion )

{

case 1: i='A';

while ( i<='Z' )

{

printf( "%c", i );

i ++;

}

break;

case 2;

for ( i='Z'; i>='A'; i--)

{

printf( "%c", i );

i++;

}

break;

default: printf( "La opcion es incorrecta\n" );

}

system( "cls" );

}

Steven R. Davidson
2015-11-12 22:58:10

Hola Angie,

Sí necesitas incluir <stdlib.h> porque usas la función 'system()'.

El error de compilación es al escribir:

case 2;

cuando debería ser:

case 2:

Es decir, has escrito un punto y coma en lugar de dos puntos.

Espero que esto te oriente.

Steven

Anónimo
2016-02-04 21:46:03

¿el complemento de 01001010 es igual a : 10110101?

Andres
2016-05-09 16:19:14

Me interesa muchisimo el curso de c++ ,yo e aprendido algo en la escuela pero esto es mejor

juan
2017-03-14 04:06:00

cuales son las funciones que aceptan las tildes y a la letra ñ en c++