lunes, 23 de mayo de 2016

Cómo expresar números negativos en complemento a la base sin que se te vuele el peluquín


Existe una razón por la que el sistema decimal nos resulta tán cómodo, después de todo contamos con diez dedos y pasamos los primeros años de nuestra vida midiendo años en término de ellos. Pero como la amiga CPU necesita ver las cosas en binario, más de una vez al programar en bajo nivel debemos transformar un valor a ceros y unos. Hasta ahí es feo, pero después de un poco de práctica se hace más fácil. Sin embargo expresar números negativos en binario es un par de vueltas más feo que eso. ¿Cómo podemos hacerlo -un poco- más fácil? En lugar de las metodologías tradicionales, les propongo este sistema, el cual requiere hacer algunos cálculos mentales, los que estoy seguro de que su cerebro podrá procesar sin levantar temperatura.
Este es el método que aprendí en el libro de Thomas Floyd (Fundamentos de sistemas digitales) para representar números enteros negativos en complemento a la base en 8 bits. 
Cuando representamos enteros sin signo en 8 bits, la vida es fácil, solo debemos recordar los pesos de cada dígito:

128 64 32 16 8 4 2 1

Se trata de las potencias enteras de dos, que justamente por ello tienen una relación fácil de recordar entre valores consecutivos (dependiendo de dónde se empiece, es el doble o la mitad). Para representar números sin signo rápidamente, podemos emplear el método por inspección (manera elegante de decir medio mentalmente, medio a ojo). 
Pongamos un ejemplo cualquiera: el 96.
1. Antes que nada constatamos que el número a representar entre en el rango admitido de 8 bits sin signo (0;255). El 96 cumple la condición, así que continuamos.
2. Luego vemos si alguno de los pesos binarios de cada posición supera al número. 96 es mayor a todos los pesos salvo al más pesado: 128. Por tanto en 128 va un cero:

128 64 32 16 8 4 2 1
   0  ?  ?  ?  ? ? ? ?  

El resto aun no sabemos (por eso el signo de interrogación).
Si hubiera más valores de pesos que superan al número objetivo, podemos completar con ceros.
3. Cuando hallamos el mayor peso que no supera al número objetivo (o que lo iguala) le ponemos un uno. En nuestro ejemplo 64 es ese número:

128 64 32 16 8 4 2 1
   0  1  ?  ?  ? ? ? ?  

Ahora tenemos que restar (mentalmente en un mundo ideal) 96 - 64. El resultado es 32.
4. Repetimos el paso anterior con el siguiente peso. Si el peso supera al número, va un cero. Si no lo supera, va un uno y restamos el peso del resto del objetivo. Casualmente en este ejemplo nos quedaron 32 y el siguiente peso es 32. Así que va un 1 en el peso 32 y la resta nos da cero. Por lo tanto el resto de los pesos son cero (no queda resto para representar):

128 64 32 16 8 4 2 1
   0  1  1  0  0 0 0 0  


Repitamos el procedimiento para un número distinto, por ejemplo el 172. En este caso 128 es menor a 172, por tanto ponemos un 1 en 128:

128 64 32 16 8 4 2 1
   1  ?  ?  ?  ? ? ? ?  

La parte difícil: restamos mentalmente 172-128... es 44. El siguiente peso (64) es mayor a 44, por ende ponemos un cero:
128 64 32 16 8 4 2 1
   1  0  ?  ?  ? ? ? ?   

El siguiente peso (32) es menor a 44, por ende ponemos un uno:
128 64 32 16 8 4 2 1
   1  0  1  ?  ? ? ? ?   
 y volvemos a restar, 44-32 (esta es más fácil!), nos queda 12. Podemos completar los pesos de 8 y 4 con un 1, y el resto de los pesos con ceros:



128 64 32 16 8 4 2 1
 1  0  1  0  1 1 0 0

¿No es tan difícil, no? Ahora veamos cómo haríamos con números negativos. Me animaría a decir que es más fácil. El único cambio con el procedimiento que hicimos hasta ahora es que el peso de mayor significado es negativo, o sea:


-128 64 32 16 8 4 2 1

Obviamente también cambia el rango. Ahora será (-128;127). Deducir el rango a partir de recordar lo anterior (que el peso 128 es negativo) es bien fácil. Para el menor de los negativos (-128) ponemos un 1 en el único peso negativo:


-128 64 32 16 8 4 2 1
 1   0  0  0  0 0 0 0

y para el mayor de los positivos (127) ponemos unos en todos los pesos positivos:


-128 64 32 16 8 4 2 1
 0   1  1  1  1 1 1 1

Ahora el procedimiento para representar un negativo sería así:
1. Verificar que entre en el rango representable. En 8 bits dijimos es -128;127.
2. Colocar un 1 en el único peso negativo (-128)
3. Agregar un 1 en los pesos positivos necesarios para llegar al número objetivo, siguiendo el mismo mecanismo que con los positivos. Veámoslo con un ejemplo, supongamos el número -37.
Ponemos un 1 en el peso negativo:

-128 64 32 16 8 4 2 1
 1   ?  ?  ?  ? ? ? ?

Tenemos el -128 y queremos llegar a -37. Cada uno que completemos suma al -128 y nos acerca al objetivo. Oh casualidad que al sumar 64 a -128 nos da.... -64. Volvemos a sumar 32 a -64 y nos da -32. ¡Momento que soy lento! Nos pasamos. Queríamos el -37 y llegamos a -32. Así que en 32 va un cero:



-128 64 32 16 8 4 2 1
 1   1  0  ?  ? ? ? ?

Vamos en -64, nos dimos cuenta que no podemos sumarle 32 porque es demasiado. Pero sí sumamos 16, entonces nos queda -48 (esta es la parte difícil, sumar/restar mentalmente):


-128 64 32 16 8 4 2 1
 1   1  0  1  ? ? ? ?

Al -48 le sumamos 8 y nos queda -40:



-128 64 32 16 8 4 2 1
 1   1  0  1  1 ? ? ?

Si le sumamos 4 llegamos a -36... nuevamente es demasiado. Así que en el peso 4 va un cero:



-128 64 32 16 8 4 2 1
 1   1  0  1  1 0 ? ?

Teníamos -40, si ponemos un 1 en los últimos dos pesos (2 y 1) le sumamos 3, y llegamos a -37:



-128 64 32 16 8 4 2 1
 1   1  0  1  1 0 1 1

Objetivo alcanzado! La primera vez puede que se complique, pero luego de un poco de práctica se hace más natural. ¿Probaste hacerlo?


Edit (9/9/16): Tenia un error (arrastrado como vibora) que corregí gracias a la aguda observación de un alumno.
Edit (12/12/16): Otro error más! Por canchero. Corregido gracias a Maximiliano.

14 comentarios:

  1. Profe este sistema para representar numeros negativos es el que usamos para calcular un salto no? Por ejemplo si el salto es 9 posiciones para atras (-9) seria 11110111 (en hexa F7)?

    ResponderBorrar
  2. Hola profesor, buenas noches. Le hago una consulta que está fuera del tema que publicó en el Blog.
    Hay un ejercício que estoy haciendo de un modelo de parcial en donde tengo un vector, conozco la cantidad de elementos(CANT) y la posición inicial (PUNT). Me pide que determine la cantidad de ceros(con esto no tengo problemas) y los elementos del vector que son múltiplos de 4. Me fijé en el SET de Instrucciones y hay 2 mnemónicos, "FDIV" y "IDIV", sinceramente no se como usarlos o si los puedo usar y tengo que resolver el problema haciendo restas sucesivas hasta llegar a cero.

    Desde ya muchas gracias.
    Pablo Manzano

    ResponderBorrar
    Respuestas
    1. Hola Pablo! Lástima que la pregunta no tiene mucha relación con el tema... o si? la forma más eficiente -a mi entender- de determinar si un numero es múltiplo de 4 es hacer dos shift a la derecha. Si las dos veces el LSB que va a parar a Cy es cero, entonces es múltiplo de cuatro.
      Usar las instrucciones de división para esto es factible, pero tienen como contra que operan en 16 bits y sería como matar un mosquito con un martillo... too much.

      Borrar
  3. Profe, una pregunta. Si tengo CANT (la cantidad de elementos de un vector) en memoria y lo voy decrementando con DEC CANT, puedo hacer luego un BEQ para saber si es cero, o los branch evaluan solo los flags de los acumuladores? De ser asi necesitaria antes un TST CANT? Gracias

    ResponderBorrar
    Respuestas
    1. Como la instrucción DEC modifica el flag Z (cero), podés hacer directamente el BEQ en base a como queda el flag después del DEC. No es necesario hacer TST (y hasta diría que está mal si pone al descubierto que no sabés cómo actualiza los flags DEC).

      Borrar
  4. Hola profe!
    Quería comentarle que hay un error, 172-128 es 44!
    Un abrazo!

    Maximiliano

    ResponderBorrar
    Respuestas
    1. Gracias Maximiliano! Eso pasa por hacer cálculos mentales con una mente que no da para eso. Corregido!

      Borrar
  5. Jaja este blog refleja mucho su perfil profe! Mezcla de profesionalidad y humor. Excelente combinación. Gracias profe!

    ResponderBorrar
  6. Hola profe, soy Hernán, curso los miércoles y viernes a la mañana. Le escribo porque tengo algunas dudas. Antes que nada, no tienen que ver con el tema, asique perdón por romper el orden del blog! Jaja. La pregunta es de interrupciónes, cuando vuelvo de la rutina de atención de interrupción, teóricamente se inhabilitan las IRQ. Yo habilite las IRQ con el CLI y luego hice un fin Bra fin para que espere la interrupción. Ahora, cuando vuelva de la primera interrupción, el Ccr flag I será 1, lo cual no se podrá volver a disparar una IRQ. Cómo tendría que hacer? Poner un Loop en cli y luego un Bra Loop? O hacemos como si el flag I siguiera en 0?? Espero que se entienda. Gracias!

    ResponderBorrar
    Respuestas
    1. Hola! Veo tu mensaje algo tarde... tengo problemitos con internet. Si ves la respuesta, buscame en la facultad y charlamos el tema.
      Básicamente, cuando "volvés" de la ISR, no inhabilitás las interrupciones. Eso se inhabilita en forma automática cuando se invoca la ISR en forma. Creo que tu error es usar CLI.

      Abrazo,

      Jaír

      Borrar
  7. Buenas tardes profesor, queria felicitarlo y agradecerle el tiempo dedicado al blog, muy interesante y util.
    Saludos.
    PD: El humor ayuda mucho!

    ResponderBorrar

Aunque no es obligatorio, es de buena educación firmar los comentarios.