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 idiv
te 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 div
usando 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, 63
porque 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.