Saltar al contenido

Lenguaje ensamblador – ¿Cómo hacer Modulo?

Luego de mucho batallar ya encontramos la solución de este rompecabezas que agunos lectores de nuestro espacio tienen. Si tienes algún detalle que compartir no dejes de compartir tu información.

Solución:

Si su módulo/divisor es una constante conocida y le preocupa el rendimiento, vea esto y esto. Incluso es posible un inverso multiplicativo para valores invariantes de bucle que no se conocen hasta el tiempo de ejecución, por ejemplo, consulte https://libdivide.com/ (pero sin la generación de código JIT, eso es menos eficiente que codificar solo los pasos necesarios para uno constante.)

Nunca usar div para potencias conocidas de 2: es mucho más lento que and para el resto, o desplazamiento a la derecha para dividir. Mire la salida del compilador C para ver ejemplos de división firmada o no firmada por potencias de 2, por ejemplo, en el explorador del compilador Godbolt. Si sabe que una entrada de tiempo de ejecución es una potencia de 2, use lea eax, [esi-1] ; and eax, edi o algo asi para hacer x & (y-1). Modulo 256 es aún más eficiente: movzx eax, cl tiene latencia cero en las CPU Intel recientes (eliminación de movimiento), siempre que los dos registros estén separados.


En el caso simple/general: valor desconocido en tiempo de ejecución

Él DIV instrucción (y su equivalente IDIV para números con signo) da tanto el cociente como el resto. Para unsigned, el resto y el módulo son lo mismo. para firmado idivte da el resto (no módulo) que puede ser negativo:
p.ej -5 / 2 = -2 rem -1. La semántica de división x86 coincide exactamente con la de C99 % operador.

DIV r32 divide un número de 64 bits en EDX:EAX por un operando de 32 bits (en cualquier registro o memoria) y almacena el cociente en EAX y el resto en EDX. Falla por desbordamiento del cociente.

Ejemplo de 32 bits sin firmar (funciona en cualquier modo)

mov eax, 1234          ; dividend low half
mov edx, 0             ; dividend high half = 0.  prefer  xor edx,edx

mov ebx, 10            ; divisor can be any register or memory

div ebx       ; Divides 1234 by 10.
        ; EDX =   4 = 1234 % 10  remainder
        ; EAX = 123 = 1234 / 10  quotient

En ensamblaje de 16 bits puedes hacer div bx para dividir un operando de 32 bits en DX:AX por BX. Consulte los manuales del desarrollador de software de arquitecturas de Intel para obtener más información.

Normalmente siempre usa xor edx,edx antes sin firmar div para extender EAX a cero en EDX:EAX. Así es como se hace la división “normal” de 32 bits / 32 bits => 32 bits.

Para la división firmada, usar cdq antes de idiv para señal-extender EAX en EDX:EAX. Vea también ¿Por qué EDX debe ser 0 antes de usar la instrucción DIV?. Para otros tamaños de operandos, utilice cbw (AL->AX), cwd (AX->DX:AX), cdq (EAX->EDX:EAX), o cqo (RAX->RDX:RAX) para establecer la mitad superior en 0 o -1 según el bit de signo de la mitad inferior.

div / idiv están disponibles en tamaños de operandos de 8, 16, 32 y (en modo de 64 bits) de 64 bits. El tamaño del operando de 64 bits es mucho más lento que el de 32 bits o menor en las CPU Intel actuales, pero las CPU AMD solo se preocupan por la magnitud real de los números, independientemente del tamaño del operando.

Tenga en cuenta que el tamaño del operando de 8 bits es especial: las entradas/salidas implícitas están en AH:AL (también conocido como AX), no en DL:AL. Consulte el ensamblaje 8086 en DOSBox: ¿Error con la instrucción idiv? para un ejemplo.

Ejemplo de división de 64 bits con signo (requiere modo de 64 bits)

   mov    rax,  0x8000000000000000   ; INT64_MIN = -9223372036854775808
   mov    ecx,  10           ; implicit zero-extension is fine for positive numbers

   cqo                       ; sign-extend into RDX, in this case = -1 = 0xFF...FF
   idiv   rcx
       ; quotient  = RAX = -922337203685477580 = 0xf333333333333334
       ; remainder = RDX = -8                  = 0xfffffffffffffff8

Limitaciones / errores comunes

div dword 10 no es codificable en código de máquina (por lo que su ensamblador informará un error sobre operandos no válidos).

A diferencia de con mul/imul (donde normalmente debería usar 2 operandos más rápidos imul r32, r/m32 o 3 operandos imul r32, r/m32, imm8/32 en lugar de eso, no pierda el tiempo escribiendo un resultado de la mitad alta), no hay un código de operación más nuevo para la división por una división o resto inmediato, o de 32 bits/32 bits => 32 bits sin la entrada de dividendo de la mitad alta.

La división es tan lenta y (con suerte) rara que no se molestaron en agregar una forma de evitar EAX y EDX, o usar un método inmediato.


div e idiv fallarán si el cociente no cabe en un registro (AL/AX/EAX/RAX, mismo ancho que el dividendo). Esto incluye la división por cero, pero también ocurrirá con un EDX distinto de cero y un divisor más pequeño. Esta es la razón por la que los compiladores de C solo se extienden a cero o se extienden con signo en lugar de dividir un valor de 32 bits en DX: AX.

y también por qué INT_MIN / -1 es un comportamiento indefinido de C: desborda el cociente con signo en los sistemas de complemento a 2 como x86. Consulte ¿Por qué la división de enteros por -1 (uno negativo) da como resultado FPE? para ver un ejemplo de x86 frente a ARM. x86 idiv de hecho falla en este caso.

La excepción x86 es #DE – excepción de división. En los sistemas Unix/Linux, el kernel envía una señal de excepción aritmética SIGFPE a los procesos que provocan una excepción #DE. (¿En qué plataformas la división de enteros por cero activa una excepción de punto flotante?)

Para divusando un dividendo con high_half < divisor es seguro. p.ej 0x11:23 / 0x12 es menos que 0xff entonces cabe en un cociente de 8 bits.

La división de precisión extendida de un número enorme entre un número pequeño se puede implementar utilizando el resto de un fragmento como el dividendo de la mitad superior (EDX) para el siguiente fragmento. Esta es probablemente la razón por la que eligieron resto = cociente EDX = EAX en lugar de al revés.

Si calcula módulo una potencia de dos, usar AND bit a bit es más simple y generalmente más rápido que realizar una división. Si b es una potencia de dos, a % b == a & (b - 1).

Por ejemplo, tomemos un valor en el registro EAX, módulo 64.
La forma más sencilla sería AND EAX, 63porque 63 es 111111 en binario.

Los dígitos más altos enmascarados no nos interesan. ¡Pruébalo!

Análogamente, en lugar de usar MUL o DIV con potencias de dos, el cambio de bits es el camino a seguir. Sin embargo, ¡cuidado con los enteros con signo!

Al final de todo puedes encontrar los comentarios de otros creadores, tú incluso eres capaz mostrar el tuyo si te apetece.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *