Nosotros hemos decidido hacerlo en x86. Aunque su conjunto de instrucciones es extenso y complejo, nos brida la ayuda "especial" de poder hacer uso de la API de C. Además, las instrucciones de x86, por el mero hecho de su complejidad intrínseca, hacen que sea posible realizar una misma operación utilizando menos instrucciones que en ensamblador ARM.
El problema que hemos decidido desarrollar, se trata de un verificador de fechas. El usuario introducirá la fecha en el formato "dd mm aaaa" ó 0 para salir. A la hora de verificar la fecha, se tendrán en cuenta las siguientes cuestiones:
1. Que el año sea bisiesto, por lo que si el mes
introducido es febrero, tendremos que comprobar que
tenga 29 días, y no 28, que sería lo normal.
2. Que se trate de un mes de 30 días, 31 días ó 29 días.
Una vez visto el enunciado del problema, pasamos a la codificación del mismo.
Vamos a ver un desglose de las partes más importantes del programa programa, para comprender los entresijos del mismo.
Variables
En primer lugar, vamos a crear las variables que vamos a usar en este ejercicio: fecha, que será un vector de tres posiciones en el que guardaremos el día, el mes y el año (en esa posición) y bis, que será otro vector de tres enteros que nos harán falta más adelante para comprobar si un año es o no es bisiesto.
;EasyCodeName=Module1,1 .Const .Data fecha DD 0, 0, 0 bis DD 4, 400, 100
Programa principal
A continuación presentamos lo que podemos llamar "programa principal". Lo primero que se hace en este caso (start), es saltar a la etiqueta "introducir" para comenzar a leer los datos necesarios.
Bajo la etiqueta "fvalida" lo único que hacemos es mostrar el mensaje avisando que la fecha introducida es válida y volvemos a pedir una nueva fecha.
Comentar también que cuando el usuario introduzca el valor "0" en la fecha, se hará un salto a la etiqueta "fin" con la idea de terminar la ejecución del programa. Cuando se finaliza la ejecución, inicializamos el contenido del registro EAX y salimos.
.Code start: ; comienzo del programa Jmp introducir fvalida: Invoke puts, "fecha valida" Jmp start fin: Invoke puts, "" Invoke system, "pause" Xor Eax, Eax Invoke ExitProcess, Eax ;Fin del programa principal
Introducción de datos
Mediante la API de C y haciendo uso de la pseudoinstrucción INVOKE, vamos a llamar las funciones "puts" y "scanf" de C. Recordar que INVOKE sustituye a las interrupciones al sistema que se hacen cuando programamos en ARM y necesitamos pedir un dato o escribir un texto.
También aprovechamos y comparamos si la fecha introducida es "0". Si esto es así, saltamos a la etiqueta "fin" y acabamos el programa.
introducir: Invoke puts, "" Invoke puts, "Introduce fecha (dd mm aaaa) (0 mm aaaa para salir):" Invoke scanf, "%d %d %d", Addr fecha, Addr fecha + 4, Addr fecha + 8 Cmp D[fecha], 0 ; 0 para salir Je fin Jmp validar
Validación
Podemos decir que éste es el "corazón" de nuestro problema. Como siempre, empezamos inicializando los registros que vamos a usar: EAX, EBX, ECX y EDX; para, posteriormente introducir en el registro EAX el día ([fecha]), en EBX el mes (siguiente elemento del vector fecha [fecha+4]) y en ECX el año (tercer y último elemento del citado vector [fecha + 8])
A continuación vamos a comprobar que los límites sean los correctos. Si el día es menor que 1 o mayor que 31, la fecha no será considerada válida; Si el mes es menor que 1 o mayor que 12, por razones obvias tampoco será considerada la fecha válida. Y finalmente, hemos fijado como intervalo de años posibles aquel comprendido entre 1600 y 9999, por lo que cualquier año introducido que esté fuera de ese rango, será considerado no válido y por consiguiente, la fecha tampoco.
Una vez que hemos pasado la primera "criba" hay que saber que los meses pares de Enero a Julio, tienen 30 días (tomando febrero como una excepción) y que los meses impares de Agosto a Diciembre, tienen 30 días.
Para solucionar este problema que se nos plantea, movemos el mes a un registro auxiliar, que previamente hemos inicializado (dx). Usaremos la operación AND contra el primer bit, y si dx = 1, quiere decir que dicho mes es impar. Con esto sabremos la paridad del mes. Por lo que si lo que hay almacenado en dx es diferente de 1, saltamos a la validación de un mes par (saltamos a mpares), en caso contrario, saltamos a mimpares.
Si una vez que el programa comprueba si los días del mes son válidos, una vez que acaben de ejecutarse las subrutinas que hemos llamado (mimpares, mpares), si estas acaban con éxito, volverán al punto desde donde se produjo el salto, continuará con la ejecución y saltará a la etiqueta fvalida, ya que, si vuelve a ese punto del programa es porque el día del mes ha sido validado con éxito. En otro caso, la subrutina adecuada será la que envíe el flujo del programa hacia otra subrutina para mostrar el error.
validar: Xor Eax, Eax Xor Ebx, Ebx Xor Ecx, Ecx Xor Edx, Edx Mov Eax, [fecha] Mov Ebx, [fecha + 4] Mov Ecx, [fecha + 8] ; validamos limites minimos y maximos Cmp Eax, 1 ; dia < 1 Jl > fnovalida Cmp Eax, 31 ; dia > 31 Jg > fnovalida Cmp Ebx, 1 ; mes < 1 Jl > fnovalida Cmp Ebx, 12 ; mes > 12 Jg > fnovalida Cmp Ecx, 1600 ; año aprox de calendario gregoriano Jl > fnovalida Cmp Ecx, 9999 ; fijamos un año maximo Jg > fnovalida ; los meses pares de enero a julio tiene 30 dias ; excepto febrero Mov dx, bx ; movemos a un registro auxiliar And dx, 1 ; usamos la operacion logica and contra el primer bit Cmp dx, 1 ; si dx=1 es impar Jne > mpares ; validacion de meses pares ; los meses impares de agosto a diciembre tienen 30 dias Cmp dx, 1 Je > mimpares ; validacion de meses impares Jmp fvalida ; si llega aqui es fecha valida
Comprobamos los días del mes
En este trozo de código comprobamos si el día del mes, según su paridad (mes par o impar) tiene un valor exacto. En el caso de "mimpares", comprobamos primero si el mes es menor que Agosto (Cmp Ebx, 8) y si este tiene menos de 30 días, si esto es cierto (si el valor de EBX, que es el mes, es menor que 8) la fecha es válida. Recordar que JL salta si EBX es menor que 8 teniendo en cuenta el signo). En el caso que no se cumpla alguna de las condiciones para que la fecha sea válida, se saltará a la etiqueta "fnovalida" y se volverá a pedir una nueva fecha por teclado.
En este punto debemos tener una consideración especial; el mes de febrero. Cuando se trata de febrero, aunque es un mes par, necesitamos saber si el año de la fecha es o no bisiesto. En ese caso, si es bisiesto, no puede tener más de 29 días, y si no es bisiesto, no puede tener más de 28 días.
Para entender este código, primero debemos entender cuando un año es bisiesto. Como regla general, un año es bisiesto siempre que es divisible por cuatro. Pero hay una excepción, si ese mismo año es divisible por 100 pero no lo es por 400, el año NO será bisiesto aunque cumpla la regla general.
Así que almacenamos en EAX, el valor del año ([fecha+8]) y comparamos si es divisible por cuatro e inicializamos el registro EDX, ¿por qué hacemos esto? porque DIV coloca el resultado de la división en el registro EDX. Una vez que tengo el resto almacenado en EDX (Div D[bis]), comprobamos si es igual a 0, (es divisible por 4) en el caso que sea divisible por 4, salta a "bisiesto", si no es divisible por 4 sigue adelante. (Tener en cuenta que el hecho de que el año sea múltiplo de 4, no nos asegura que el año sea bisiesto, hay que hacer más comprobaciones que vemos a continuación, en el código siguiente)
mimpares: Cmp Ebx, 8 ; vemos si es menor a agosto Jl fvalida ; fecha valida Cmp Eax, 30 ; vemos si tiene mas de 30 dias Jg > fnovalida Jmp fvalida ; si llega aqui es fecha valida mpares: Cmp Ebx, 2 ; vemos si es febrero Je > febrero ; validacion para febrero Cmp Ebx, 7 ; vemos si es mayor a julio Jg fvalida ; fecha valida Cmp Eax, 30 ; vemos si tiene mas de 30 dias Jg > fnovalida Jmp fvalida ; si llega aqui es fecha valida febrero: ; vemos si es bisiesto ; multiplo de 4 y no multiplo de 100 o multiplo de 400 Mov Eax, [fecha + 8] ;dividendo Xor Edx, Edx Div D[bis] ; multiplo de 4 (divisor) Cmp Edx, 0 ; comparamos resto division Je > bisiesto ; si no es bisiesto continua
Comprobamos si el año es bisiesto
Si el año es divisible por 4 (esto lo comprobamos en el código anterior), y no lo es por 100 el año es bisiesto y ya no necesito comprobar nada más.
Si no es divisible por 100 tenemos que comprobar si lo es por 400. En caso de que sea múltiplo de 400 es bisiesto, pero si no lo es, definitivamente el año no sería bisiesto.
Una vez que ya sabemos si el año es bisiesto, solo queda comprobar que no tenga más de 29 días (Cmp Eax, 29). En caso contrario, la fecha no sería válida.
nobisiesto: Mov Eax, [fecha] Cmp Eax, 28 ; vemos si tiene mas de 28 dias Jg > fnovalida Jmp fvalida ; si llega aqui es fecha valida esbisiesto: Mov Eax, [fecha] Cmp Eax, 29 ; vemos si tiene mas de 29 dias Jg > fnovalida Jmp fvalida ; si llega aqui es fecha valida bisiesto: Mov Eax, [fecha + 8] ;dividendo Xor Edx, Edx Div D[bis + 8] ; no multiplo de 100 (divisor) Cmp Edx, 0 ; comparamos resto division Jne esbisiesto ; validamos para año bisiesto, sino continua bisiesto2: Mov Eax, [fecha + 8] ;dividendo Xor Edx, Edx Div D[bis + 4] ; multiplo de 400 (divisor) Cmp Edx, 0 ; comparamos resto division Je esbisiesto ; es año bisiesto Jmp nobisiesto ; validamos para año no bisiesto
Código completo
Finalmente vemos el código en conjunto.
;EasyCodeName=Module1,1 .Const .Data fecha DD 0, 0, 0 bis DD 4, 400, 100 .Code start: ; comienzo del programa Jmp introducir fvalida: Invoke puts, "fecha valida" Jmp start fin: Invoke puts, "" Invoke system, "pause" Xor Eax, Eax Invoke ExitProcess, Eax ;Fin del programa principal ;Subprogramas: introducir: Invoke puts, "" Invoke puts, "Introduce fecha (dd mm aaaa) (0 mm aaaa para salir):" Invoke scanf, "%d %d %d", Addr fecha, Addr fecha + 4, Addr fecha + 8 Cmp D[fecha], 0 ; 0 para salir Je fin Jmp validar validar: Xor Eax, Eax Xor Ebx, Ebx Xor Ecx, Ecx Xor Edx, Edx Mov Eax, [fecha] Mov Ebx, [fecha + 4] Mov Ecx, [fecha + 8] ; validamos limites minimos y maximos Cmp Eax, 1 ; dia < 1 Jl > fnovalida Cmp Eax, 31 ; dia > 31 Jg > fnovalida Cmp Ebx, 1 ; mes < 1 Jl > fnovalida Cmp Ebx, 12 ; mes > 12 Jg > fnovalida Cmp Ecx, 1600 ; año aprox de calendario gregoriano Jl > fnovalida Cmp Ecx, 9999 ; fijamos un año maximo Jg > fnovalida ; los meses pares de enero a julio tiene 30 dias ; excepto febrero Mov dx, bx ; movemos a un registro auxiliar And dx, 1 ; usamos la operacion logica and contra el primer bit Cmp dx, 1 ; si dx=1 es impar Jne > mpares ; validacion de meses pares ; los meses impares de agosto a diciembre tienen 30 dias Cmp dx, 1 Je > mimpares ; validacion de meses impares Jmp fvalida ; si llega aqui es fecha valida mimpares: Cmp Ebx, 8 ; vemos si es menor a agosto Jl fvalida ; fecha valida Cmp Eax, 30 ; vemos si tiene mas de 30 dias Jg > fnovalida Jmp fvalida ; si llega aqui es fecha valida mpares: Cmp Ebx, 2 ; vemos si es febrero Je > febrero ; validacion para febrero Cmp Ebx, 7 ; vemos si es mayor a julio Jg fvalida ; fecha valida Cmp Eax, 30 ; vemos si tiene mas de 30 dias Jg > fnovalida Jmp fvalida ; si llega aqui es fecha valida febrero: ; vemos si es bisiesto ; multiplo de 4 y no multiplo de 100 o multiplo de 400 Mov Eax, [fecha + 8] ;dividendo Xor Edx, Edx Div D[bis] ; multiplo de 4 (divisor) Cmp Edx, 0 ; comparamos resto division Je > bisiesto ; si no es bisiesto continua nobisiesto: Mov Eax, [fecha] Cmp Eax, 28 ; vemos si tiene mas de 28 dias Jg > fnovalida Jmp fvalida ; si llega aqui es fecha valida esbisiesto: Mov Eax, [fecha] Cmp Eax, 29 ; vemos si tiene mas de 29 dias Jg > fnovalida Jmp fvalida ; si llega aqui es fecha valida bisiesto: Mov Eax, [fecha + 8] ;dividendo Xor Edx, Edx Div D[bis + 8] ; no multiplo de 100 (divisor) Cmp Edx, 0 ; comparamos resto division Jne esbisiesto ; validamos para año bisiesto, sino continua bisiesto2: Mov Eax, [fecha + 8] ;dividendo Xor Edx, Edx Div D[bis + 4] ; multiplo de 400 (divisor) Cmp Edx, 0 ; comparamos resto division Je esbisiesto ; es año bisiesto Jmp nobisiesto ; validamos para año no bisiesto fnovalida: Invoke puts, "Fecha no valida" Jmp start