Programación en Lenguaje Ensamblador

-El Verdadero Lenguaje de las Máquinas-

+++ Positivo y Negativo – – –

–Números Binarios Con y Sin Signo–

Hasta ahora, la entrada de este blog que ha sido mas leida es la de ¿Cuantos bits tiene un Byte?. En esa entrada se deja en claro lo que es un byte. Una celda de 8 bits que puede almacenar cualquier número entero entre 0 y 255.

Sin embargo, cada uno de estos valores puede ser interpretado de 2 maneras diferentes dependiendo del contexto en que se trabaje.

Números sin signo.- Estos son los de siempre. Cuenta de cero a 255.

Números con signo.- Con los mismos 8 bits de un byte, podemos representar los números desde el -128 al 127. Si se fijan, estos son 256 valores diferentes.

Todos estos conceptos son suficientemente complejos para que se les dedique un examen completo en una carrera de Ingeniería en Computación. Sin embargo, en este dibujo que recuerda a un Yin-Yang chino pueden verse la mayoría de los conceptos que tienen que ver con esto de los números enteros con y sin signo:


binyang

Este círculo se lee exactamente igual que un reloj analógico. (De manecillas) Se parte del punto mas alto donde está el valor 0 y se recorre toda la circunferencia hasta llegar a donde empezamos. De hecho, al igual que el reloj, la cuenta que lleva un byte vuelve a comenzar al dar una vuelta completa. Este fenómeno se llama WRAP-AROUND y es la base de la aritmética de enteros signados, (que no es lo mismo que santiguados). Este fenómeno era muy común en los juegos de principios de los años 80 como Asteroids, en donde si uno salía por un lado de la pantalla, magicamente aparecía por el lado de la pantalla opuesto.

Sin embargo, las similitudes con el reloj convencional terminan aquí. Pues esta rueda puede moverse en sentido opuesto a como lo hacen las manecillas. Pues del mismo modo que si tenemos 255 y le sumamos uno el contador se reinicia en cero, si tenemos cero y restamos uno, el contador marcará 255. Esto tiene perfecto sentido en los números sin signo, representados aquí en color gris tanto en decimal como en Hexadecimal. Entonces, para dar una vuelta completa al circulo, hay que dar 256 pasos. Pero eso es solo porque esta representación es de 1 byte.

Números con signo.- Cuando usamos signo, la mitad de los números son positivos y la mitad negativos. Aunque hay 2 casos especiales:

Cero Positivo.- Aunque matemáticamente el cero no es ni positivo ni negativo, y puede dividirse entre cualquier número. Dentro de un CPU un cero se considera positivo y par. Pero esto es válido solo para las representaciones de números enteros, pues en Punto Flotante pueden representarse ceros positivos (porque tienen SIDA) y negativos para su uso en cálculo infinitesimal. El cero siempre tiene todos los bits puestos en cero sin importar si se trata de un registro de 8, 16, 32, 64, etc. En el diagrama, el cero se encuentra en la parte mas alta del círculo del lado positivo (azul).

El Gran Negativo.- El -128 es el número negativo con mayor valor absoluto que puede representarse en 8 bits. Para compensar el hecho de que los negativos no tienen cero, los positivos no tienen el +128. El número negativo con mayor valor absoluto puede ser:

-128 en 8 bits,

-32768 en 16 bits,

-2,147,483,648 en 32 bits

y -9,223,372,036,854,775,808 en 64 bits.

En realidad no importa el ancho del registro, el Gran Negativo siempre se representa con todos los bits en cero excepto el bit mas significativo. En el diagrama, el Gran Negativo se encuentra en la parte mas baja del círculo, del lado negativo (rojo).

Lo interesante es que la computadora no sabe ni le importa si el valor en el registro tiene signo o no. Si en la parte baja del acumulador hay un A0h el CPU no sabe si se trata de un 160 sin signo o un -96 con signo. Mas adelante veremos como el CPU evalúa un resultado con el registro EFLAGS. Los números entre el 0 y el 127 con signo se manejan igual a los que no tienen signo.

Bit Signo.- Es el bit mas alto del registro. Si está en uno el valor es negativo y se está en cero es positivo. Podría decirse que ese es el ‘bit del mal’. Pero ojo, si queremos convertir un número positivo a negativo, o viceversa, hace falta mucho mas que solo cambiar el valor de este bit. Hay que ‘negarlo’ con la instrucción NEG.

NOT.- Esta operación es muy simple, enciende todos los bits apagados y apaga todos los bits entendidos. En el círculo, esta operación funciona como un espejo,pues solo hay que trazar una linea horizontal desde el valor que queremos hacerle el NOT hasta el otro lado de la rueda. Pero Ojo, los números no son simétricos. La suma de cualquier número mas su valor NOT es igual a todos los bits activos, en este caso -1 con signo y 255 con signo.

NEG.- La instrucción NEG cambia el signo de un valor entero. Si es positivo lo hace negativo y el negativo a positivo. Para que no se les olvide, el NEG es como una opreración de cambio de sexo (u operación jarocha como diríamos en México).Primero hace un NOT y luego suma un uno. Esto se ve en el círculo donde están las flechas azul y roja y la palabra NEG. Se traza una linea hasta el otro lado de la rueda y se avanza una unidad para cambiar el signo. Pero hay 2 excepciones: El Cero y en Gran Negativo. Si le aplicamos el NEG a cualquiera de estos 2 valores no sucederá nada. Tan solo se activará la bandera CARRY en el registro EFLAGS.


rango entero

Por último, hay que dejar claro que para el CPU hay una gran diferencia entre el hecho de que un número sea mayor o menor que otro y que esté por encima o por debajo de otro. En este otro dibujo vemos el mismo círculo de la primera ilustración en forma ‘desenrrollada’ y podemos ver lo complicado que resulta representar números con signo de manera no circular.

El concepto de MAYOR y MENOR solo aplica para los números con signo. Mientras que ARRIBA y ABAJO es solo para los que no tienen signo. Por ejemplo, el -1 es menos que 100 pero a la vez -1 está por encima del 100 porque -1 es 0FFh mientras 100 es 64h. Esto tiene una ventaja, si queremos ver si un resultado está entre 0 y 50, en lugar de hacer 2 comparaciones (resultado < 0 y resultado > 50) hacemos solo una, donde vemos si el resultado está por debajo del 50. Pero esto ya es asunto del registro EFLAGS.

De acuerdo, ya me extendí demasiado. Para acabar solo queda decir que no hay que confundir el Carry con el Overflow. Supongo que ya debería de hablar de como toma decisiones la computadora. Pero por ahora a insultar:

Hace unas horas eché un vistazo al circo de pulgas. Es increible como llevan casi un mes tratando de unir todos los archivos de sus sprites lameados con alguna aplicación. Al parecer alguien se queja de que la única aplicación que encontró para hacer esto solo los ordena de modo vertical. ¡Estos lamers no saben que en esa disposición es mas sencillo su almacenamiento y recuperación en la memoria! Quieren que el programa acomode los sprites en una matriz bidimensional donde todos los sprites quedan en celdas del mismo tamaño. De hecho en el foro viene una secuencia de animación de un Ha-Do-Ken. (Suerte que esta vez no le dijeron ‘abuken’) y cada cuadro de animación es por lo menos 9 veces mas grande que el sprite que contiene. Lo que significa que, como no creo que sepan nada de compresión en tiempo real, van a gastar 9 veces mas memoria solo en esa animación. En otro thread, al parecer están planeando una orgía. Pero eso ya no tiene nada que ver con la programación. Bueno, ahí le dejo porque tengo que planear nuevas bromas crueles para este blog.

abril 16, 2009 - Posted by | Uncategorized | , , , , , , , ,

21 comentarios »

  1. Eit, quede apendejado con esto:

    en lugar de hacer 2 comparaciones (resultado 50) hacemos solo una, donde vemos si el resultado está por debajo del 50

    Que chistoso saliste, pues el rango de comparacion es entre [0,50] alli si lo puedes hacer en una solo comparacion pero que pasaria si fuera entre [10,50] (resultado 50)?

    Bueno de hecho tambien lo puedes hacer en una solo comparacion, pero te lo dejo de tarea.

    Saludos

    Comentarios por blackpig | abril 20, 2009 | Responder

    • Eso se resuelve con las leyes de DeMorgan. En lugar de probar si el numero es mayor de 10 AND menor que 50, pruebas una sola vez si NO es mayor que 10 OR no es menor que 50. Dependiendo del numero puede resolverse en una o dos comparaciones. Pero es mas rapido que probar 2 veces. Si el numero que quieres probar es el resultado de una operacion aritmetica ni siquiera tienes que hacer comparaciones. Solo 2 saltos condicionales de los cuales solo uno se ejecuta:

      Comentarios por asm86 | abril 20, 2009 | Responder

    • En realidad solo hay que restar 10 al resultado del acumulador. De ese modo, es lo mismo que si probaramos que el resultado esta entre 0 y 40 y la logica seria la misma

      Comentarios por asm86 | abril 21, 2009 | Responder

  2. hey black pig… como saber si un numero esta entre 10 y 50 con una sola comparacion?? interesante!

    sub ax, 10
    js .menor_a_diez
    sub ax, 40
    jns .mayor_a_cincuenta

    pero siguen siendo dos comparaciones!

    Comentarios por Puerco | abril 20, 2009 | Responder

    • mejor seria asi:

      sub eax, 10
      cmp eax, 40
      ja fuera_de_rango
      ;aqui solo llegamos si EAX esta entre 10 y 50

      fuera_de_rango:
      ;aqui sigue el codigo

      Comentarios por asm86 | abril 21, 2009 | Responder

  3. Oye Mario, todos nos inspirabamos cuando probabamos codigos que funcionaban en nuestras maquinas… que te parece el algoritmo de bresenham? explicado como bien sabes hacer…

    saludos!

    Comentarios por Puerco | abril 20, 2009 | Responder

    • Buen algoritmo, aunque en el siglo XXI ya se han desarrollado algoritmos aun mas rapidos como el del punto medio o el de Nicholl-Lee-Nicholl que dibuja una linea desde los extremos hasta el centro. Lo explicare en cuanto toque el tema de como escribir pixeles en la memoria de video en Windows.

      Por cierto, el algoritmo de Bresenham es optimo en C. Pero el de punto medio se presta mas para ser programado en ensamblador, pues calcula los puntos de la linea unicamente con sumas enteras y desplazamientos binarios. Ni siquiera es necesario hacer comparaciones porque todo se hace con el registro Banderas. De hecho, es mas codigo para leer las coordenadas de entrada que para dibujar la linea.

      Comentarios por asm86 | abril 21, 2009 | Responder

  4. Otra cosa Mario.. me preguntaba si aceptarias articulos de 3eros en tu blog??? algo asi como autor invitado del mes…

    saludos

    Comentarios por Puerco | abril 20, 2009 | Responder

    • En cuanto averigue como hacerlo todo el que tenga algo que decir en este blog sera bienvenido. Aunque creo que los blogs de WordPress pueden tener tambien foros de discusion. Seria emocionante ver un debate mas o menos serio en uno de esos foros. Inclusive ahi los programadores podrian retarse a duelo de albures.

      Comentarios por asm86 | abril 21, 2009 | Responder

  5. Asi, es mario muy buena respuesta, solo comentaba este detalle para complementar con lo que has estado enseñando, cada vez me sorprendes con tus dibujos en paint.

    Saludos Uik,Uiiiiiikkk

    Comentarios por blackpig | abril 21, 2009 | Responder

  6. Mario estoy en un dilema!

    entiendo que un cmp se puede sustituir por una resta sub…
    y verificar si el resultado es positivo o negativo por ejemplo:

    cmp ax, 40
    ja .mayor_a_cuarenta

    seria lo mismo que

    sub ax, 40
    jns .mayor_a_cuarenta ; salta si el resultado es positivo

    y estariamos ahorrando un ciclo de reloj…

    pero ahora viene lo bueno:

    si sustituimos esta resta en tu codigo quedaria asi:

    sub eax, 10
    sub eax, 40
    jns .mayor_a_cuarenta ; sustitucion literal
    ja fuera_del_rango ; salto original en tu codigo

    como no me interesa saber si es mayor a 40 despues de haberle restado 10 voy a eliminar esa instruccion quedando asi:

    sub eax, 10
    sub eax, 40
    ja fuera_del_rango

    ahora… resulta extraño hacer dos restas antes de una comparacion por lo que podemos compactarlas en solo una,
    queda asi:

    sub eax, 50
    ja fuera_del_rango

    aunque esto deja de tener sentido.. creo que el truco esta en el ZF y el CF del registro de banderas… se comporta algo diferente cuando usas un CMP o un SUB, jeje

    bueno… esto es como pensar en voz alta… o mas bien.. pensando con tecladasos..

    creo que de ahora en adelante seria bueno mencionar exactamente que flags del registro de banderas son afectadas y cuantos ciclos de reloj tarda cada instruccion.

    Saludos!!!

    Comentarios por Puerco | abril 22, 2009 | Responder

    • En realidad las banderas responden diferente en la primera resta que en la segunda. Al restar 10, si el valor esta entre 0 y 9 va a irse a 246 a 255 por el poder del Wrap-Arround. Mientras que el rango entre 10 y 50 se quedara entre 0 y 40. Entonces, si restaramos 40 luego de eso, bastaria con ver si el resultado esta o no por encima de 40. O lo que es lo mismo, luego de hacer una resta, si el resultado no hace wrap-around.
      En cambio, si se resta 50 desde el principio, todos los valores entre 0 y 50 acabarian en el rango 205 a 255. Si enseguida probaramos si queda por arriba, tambien se contarian dentro del rango los valores entre 0 y 10. Pero no estoy tan seguro, (acabo de decir algo que las leyes del apareamiento humano prohiben terminantemente: Demostrar inseguridad) lo mejor es escribir notas mas serias sobre las banderas y los saltos en un programa.

      Comentarios por asm86 | abril 22, 2009 | Responder

  7. Encontre esta informacion http://www.gui.uva.es/udigital/ap06.html

    donde especifican los tiempos de ejecucion de las instrucciones

    cmp registro, inmediato
    sub registro, inmediato

    y ambas parecen tomar 1 ciclo de reloj apartir de los 486.

    ademas segun esta definicion del CMP:

    CMP destiny, source

    This instruction subtracts the source operator from the destiny operator but without this one storing the result of the operation, and it only affects the state of the flags.

    es lo mismo un

    cmp ax, 10
    ja .mayor

    que un

    sub ax, 10
    ja .mayor

    excepto por que el contenido de ax se destruye.. ambas podrian tomar 2 ciclos de reloj en el 486 segun el documento anexo

    jeje ensamblador es lo maximo!…

    seria bueno hablar de optimizacion de codigo para velocidad y para espacio en disco… aunque en estos tiempos habra gente que no se preocupa ni por lo uno, menos por lo otro jaja

    Saludos!

    Comentarios por Puerco | abril 22, 2009 | Responder

  8. son unos idiotas y tienen mala informacion

    Comentarios por ciara | septiembre 9, 2009 | Responder

    • Pues si consideras que tengo mala informacion seria muy bueno que pusieras aqui mismo la informacion correcta. Me ayudarias a mucho a mejorar este blog si estas en lo cierto, y si no lo estas, nunca estaran de mas unas cuantas carcajadas. Sea como sea te invito a que publiques la ‘buena informacion’ en estos mismos comentarios.

      Comentarios por asm86 | septiembre 9, 2009 | Responder

    • Lo de idiota si te lo creo, pero que tengamos mala informacion, esta como que incompleto tu comentario. Necesitamos que nos pases la buena informacion.

      Saludos

      Comentarios por b1ackpig | septiembre 10, 2009 | Responder

  9. apesta colo quen mas imagenes

    Comentarios por yerko | diciembre 17, 2009 | Responder

    • Trabajo en eso, si alguien puede recomendarme una camara digital silenciosa y facil de ocultar me ayudaria mucho para meterle mas imagenes a este blog. Supongo que eso sera mejor que simples screenshots o mis famosos dibujitos en PAINT.

      Comentarios por asm86 | diciembre 17, 2009 | Responder

  10. Estan chidos los dibujitos en PAINT

    Comentarios por b1ackpig | diciembre 18, 2009 | Responder

  11. hola, como le puedo hacer para validar un numero ingresado desde el teclado, este debe ser un numero de 2 bytes sin signo, es decir el maximo valor q puede ingresar el usuario es 65535…

    Comentarios por Arlen | diciembre 28, 2009 | Responder

    • Para asegurarte de que un numero decimal es valido debes de probar de que todos los simbolos ascii que lo componen esten entre 48 y 57 (30h y 39h en hexadecimal), si existe un valor ascii fuera de este rango el numero es invalido. En el caso de windows puedes configurar un campo de texto para que solo te acepte numeros decimales como entrada.

      Ahora toma en cuenta que una cosa es validar y otra procesar. Si quieres convertir pasar de ascii a binario necesitas estos pasos:

      Escribir el numero en formato BCD.- Luego de la validacion eliminas los 4 bits mas altos de cada byte. Esto se hace con un AND entre el numero y la constante 0Fh
      Empaquetar BCD.- La parte mas dificil. Tienes que meter cada par de cifras en un byte de modo que cada cifra quede en 4 bits. Esto lo haces con instrucciones como SHL y OR. Debes de usar 10 bytes inicializados en cero para esto.
      Cargar a la FPU.- Cargas ese numero con FBLD a la unidad de punto flotante. La FPU convierte automaticamente entre binario entero, BCD y flotante IEEE de 32, 64 y 80 bits. Para sacar el valor como un entero binario usas la instruccion FIST y le pasas como operando el lugar donde vas a guardar el entero. Una vez hecho esto ya puedes manejarlo de la manera normal.

      Para ver si un numero es mayor de 65535 solo tienes que subirlo al acumulador con un MOV EAX, [NUMERO] y hacer un AND EAX, FFFF0000H. Si EAX no es igual a cero entonces el valor es mayor que 65535 y no puede representarse en 16 bits.

      Otra manera mas comun es tomar cada digito, multiplicarlo por su respectiva potencia de 10 y sumar todos los valores en un solo registro. Si estas trabajando con ensamblador de 16 bits, comprobar si es mayor de 65535 requiere revisar la bandera de acarreo.

      Comentarios por asm86 | diciembre 28, 2009 | Responder


Deja un comentario